VmTerminalApp: Implement error activity

Screenshot is attached to the bug

Bug: 376796062
Test: Manually. Also checked that back key on error activity doesn't \
go back to the main activity.

Change-Id: I82713b130a9d25b673b9c9b984077a0d55e4cd92
diff --git a/android/TerminalApp/AndroidManifest.xml b/android/TerminalApp/AndroidManifest.xml
index 1af6c8a..b74b8b0 100644
--- a/android/TerminalApp/AndroidManifest.xml
+++ b/android/TerminalApp/AndroidManifest.xml
@@ -46,14 +46,11 @@
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
-        <activity android:name=".SettingsActivity">
-        </activity>
-        <activity android:name=".SettingsDiskResizeActivity">
-        </activity>
-        <activity android:name=".SettingsPortForwardingActivity">
-        </activity>
-        <activity android:name=".SettingsRecoveryActivity">
-        </activity>
+        <activity android:name=".SettingsActivity" />
+        <activity android:name=".SettingsDiskResizeActivity" />
+        <activity android:name=".SettingsPortForwardingActivity" />
+        <activity android:name=".SettingsRecoveryActivity" />
+        <activity android:name=".ErrorActivity" />
         <property
             android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
             android:value="true" />
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/ErrorActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/ErrorActivity.java
new file mode 100644
index 0000000..ee1f1ad
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/ErrorActivity.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.virtualization.terminal;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public class ErrorActivity extends BaseActivity {
+    public static final String EXTRA_CAUSE = "cause";
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.activity_error);
+
+        View button = findViewById(R.id.recovery);
+        button.setOnClickListener((event) -> launchRecoveryActivity());
+    }
+
+    @Override
+    protected void onNewIntent(@NonNull Intent intent) {
+        super.onNewIntent(intent);
+        setIntent(intent);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        Intent intent = getIntent();
+        Exception e = intent.getParcelableExtra(EXTRA_CAUSE, Exception.class);
+        TextView cause = findViewById(R.id.cause);
+        if (e != null) {
+            cause.setText(getString(R.string.error_code, e.toString()));
+        } else {
+            cause.setText(null);
+        }
+    }
+
+    private void launchRecoveryActivity() {
+        Intent intent = new Intent(this, SettingsRecoveryActivity.class);
+        startActivity(intent);
+    }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
index 10451ec..0c35823 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -28,6 +28,7 @@
 import android.graphics.fonts.FontStyle;
 import android.net.Uri;
 import android.net.http.SslError;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.ConditionVariable;
 import android.os.Environment;
@@ -35,6 +36,7 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.Log;
+import android.view.KeyEvent;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
@@ -46,7 +48,6 @@
 import android.webkit.WebResourceRequest;
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
-import android.widget.Toast;
 
 import androidx.activity.result.ActivityResult;
 import androidx.activity.result.ActivityResultLauncher;
@@ -142,6 +143,17 @@
         }
     }
 
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        if (Build.isDebuggable() && event.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) {
+            if (event.getAction() == KeyEvent.ACTION_UP) {
+                launchErrorActivity(new Exception("Debug: KeyEvent.KEYCODE_UNKNOWN"));
+            }
+            return true;
+        }
+        return super.dispatchKeyEvent(event);
+    }
+
     private void requestStoragePermissions(
             Context context, ActivityResultLauncher<Intent> activityResultLauncher) {
         Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
@@ -379,16 +391,13 @@
 
     @Override
     public void onVmStop() {
-        Toast.makeText(this, R.string.vm_stop_message, Toast.LENGTH_SHORT).show();
         Log.i(TAG, "onVmStop()");
-        finish();
     }
 
     @Override
     public void onVmError() {
-        Toast.makeText(this, R.string.vm_error_message, Toast.LENGTH_SHORT).show();
         Log.i(TAG, "onVmError()");
-        finish();
+        launchErrorActivity(new Exception("onVmError"));
     }
 
     @Override
@@ -435,6 +444,13 @@
         }
     }
 
+    private void launchErrorActivity(Exception e) {
+        Intent intent = new Intent(this, ErrorActivity.class);
+        intent.putExtra(ErrorActivity.EXTRA_CAUSE, e);
+        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+        this.startActivity(intent);
+    }
+
     private boolean installIfNecessary() {
         // If payload from external storage exists(only for debuggable build) or there is no
         // installed image, launch installer activity.
diff --git a/android/TerminalApp/res/layout/activity_error.xml b/android/TerminalApp/res/layout/activity_error.xml
new file mode 100644
index 0000000..1b5026e
--- /dev/null
+++ b/android/TerminalApp/res/layout/activity_error.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--  Copyright 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true"
+    tools:context=".ErrorActivity">
+
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/error_title"
+        android:layout_marginVertical="24dp"
+        android:layout_marginHorizontal="24dp"
+        android:layout_alignParentTop="true"
+        android:hyphenationFrequency="normal"
+        android:textSize="48sp" />
+
+    <TextView
+        android:id="@+id/desc"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/error_desc"
+        android:lineSpacingExtra="5sp"
+        android:layout_marginTop="20dp"
+        android:layout_marginHorizontal="48dp"
+        android:layout_below="@id/title"
+        android:textSize="20sp" />
+
+    <TextView
+        android:id="@+id/cause"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:lineSpacingExtra="5sp"
+        android:layout_marginTop="24dp"
+        android:layout_marginHorizontal="60dp"
+        android:layout_below="@id/desc"
+        android:textSize="14sp" />
+
+    <Button
+        android:id="@+id/recovery"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentEnd="true"
+        android:layout_marginBottom="32dp"
+        android:layout_marginHorizontal="40dp"
+        android:backgroundTint="?attr/colorPrimaryDark"
+        android:text="@string/settings_recovery_title" />
+
+</RelativeLayout>
diff --git a/android/TerminalApp/res/values/strings.xml b/android/TerminalApp/res/values/strings.xml
index c89fcfa..0ae3b5c 100644
--- a/android/TerminalApp/res/values/strings.xml
+++ b/android/TerminalApp/res/values/strings.xml
@@ -96,6 +96,13 @@
     <!-- Dialog button cancel for resetting the terminal [CHAR LIMIT=16] -->
     <string name="settings_recovery_reset_dialog_cancel">Cancel</string>
 
+    <!-- Error page that shows error page [CHAR LIMIT=none] -->
+    <string name="error_title">Unrecoverable Error</string>
+    <!-- Error page that shows error page [CHAR LIMIT=none] -->
+    <string name="error_desc">Failed to recover from an error.\nYou can try restart the app, or try one of recovery option.</string>
+    <!-- Error page that shows detailed error code (error reason) for bugreport. [CHAR LIMIT=none] -->
+    <string name="error_code">Error code: <xliff:g id="error_code" example="ACCESS_DENIED">%s</xliff:g></string>
+
     <!-- Notification action button for settings [CHAR LIMIT=20] -->
     <string name="service_notification_settings">Settings</string>
     <!-- Notification title for foreground service notification [CHAR LIMIT=none] -->