From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Dmitry Muhomor <muhomor.dmitry@gmail.com>
Date: Mon, 8 Aug 2022 19:03:37 +0300
Subject: [PATCH] add an option to show the details of an application error to
 the user

Adds a "Show details" item to crash and ANR (app not responding) dialogs that takes the user to a
SystemUI activity which shows the error details and allows to copy them to the clipboard or to
export them via the standard sharing UI.
---
 .../android/app/ApplicationErrorReport.java   |  14 +-
 core/res/res/layout/app_anr_dialog.xml        |   4 +-
 core/res/res/layout/app_error_dialog.xml      |   4 +-
 core/res/res/values/strings.xml               |   3 +
 core/res/res/values/symbols.xml               |   3 +
 packages/SystemUI/AndroidManifest.xml         |  12 ++
 packages/SystemUI/res/values/strings.xml      |   5 +
 .../android/systemui/ErrorReportActivity.kt   | 159 ++++++++++++++++++
 .../java/com/android/server/am/AppErrors.java |   1 +
 9 files changed, 199 insertions(+), 6 deletions(-)
 create mode 100644 packages/SystemUI/src/com/android/systemui/ErrorReportActivity.kt

diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java
index a0b3dc0452ac..d6b9a837c46b 100644
--- a/core/java/android/app/ApplicationErrorReport.java
+++ b/core/java/android/app/ApplicationErrorReport.java
@@ -98,6 +98,9 @@ public class ApplicationErrorReport implements Parcelable {
      */
     public String packageName;
 
+    /** @hide */
+    public long packageVersion;
+
     /**
      * Package name of the application which installed the application this
      * report pertains to.
@@ -162,13 +165,18 @@ public class ApplicationErrorReport implements Parcelable {
             String packageName, int appFlags) {
         // check if error reporting is enabled in secure settings
         int enabled = Settings.Global.getInt(context.getContentResolver(),
-                Settings.Global.SEND_ACTION_APP_ERROR, 0);
+                Settings.Global.SEND_ACTION_APP_ERROR, 1);
         if (enabled == 0) {
             return null;
         }
 
         PackageManager pm = context.getPackageManager();
 
+        ComponentName systemUiReceiver = getErrorReportReceiver(pm, packageName, "com.android.systemui");
+        if (systemUiReceiver != null) {
+            return systemUiReceiver;
+        }
+
         // look for receiver in the installer package
         String candidate = null;
         ComponentName result = null;
@@ -233,6 +241,7 @@ public class ApplicationErrorReport implements Parcelable {
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(type);
         dest.writeString(packageName);
+        dest.writeLong(packageVersion);
         dest.writeString(installerPackageName);
         dest.writeString(processName);
         dest.writeLong(time);
@@ -260,6 +269,7 @@ public class ApplicationErrorReport implements Parcelable {
     public void readFromParcel(Parcel in) {
         type = in.readInt();
         packageName = in.readString();
+        packageVersion = in.readLong();
         installerPackageName = in.readString();
         processName = in.readString();
         time = in.readLong();
@@ -686,7 +696,7 @@ public class ApplicationErrorReport implements Parcelable {
      */
     public void dump(Printer pw, String prefix) {
         pw.println(prefix + "type: " + type);
-        pw.println(prefix + "packageName: " + packageName);
+        pw.println(prefix + "packageName: " + packageName + ":" + packageVersion);
         pw.println(prefix + "installerPackageName: " + installerPackageName);
         pw.println(prefix + "processName: " + processName);
         pw.println(prefix + "time: " + time);
diff --git a/core/res/res/layout/app_anr_dialog.xml b/core/res/res/layout/app_anr_dialog.xml
index 5ad0f4c0f6cc..ad3a2d2991de 100644
--- a/core/res/res/layout/app_anr_dialog.xml
+++ b/core/res/res/layout/app_anr_dialog.xml
@@ -41,8 +41,8 @@
             android:id="@+id/aerr_report"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:text="@string/aerr_report"
-            android:drawableStart="@drawable/ic_feedback"
+            android:text="@string/aerr_show_details"
+            android:drawableStart="@drawable/ic_info_outline_24"
             style="@style/aerr_list_item" />
 
 </LinearLayout>
diff --git a/core/res/res/layout/app_error_dialog.xml b/core/res/res/layout/app_error_dialog.xml
index c3b149a1e295..a47b82018377 100644
--- a/core/res/res/layout/app_error_dialog.xml
+++ b/core/res/res/layout/app_error_dialog.xml
@@ -52,8 +52,8 @@
         android:id="@+id/aerr_report"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:text="@string/aerr_report"
-        android:drawableStart="@drawable/ic_feedback"
+        android:text="@string/aerr_show_details"
+        android:drawableStart="@drawable/ic_info_outline_24"
         style="@style/aerr_list_item" />
 
     <Button
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 4a8624222ae8..b7d43121903b 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6071,4 +6071,7 @@ ul.</string>
     <string name="ui_translation_accessibility_translated_text"><xliff:g id="message" example="Hello">%1$s</xliff:g> Translated.</string>
     <!-- Accessibility message announced to notify the user when the system has finished translating the content displayed on the screen to a different language after the user requested translation. [CHAR LIMIT=NONE] -->
     <string name="ui_translation_accessibility_translation_finished">Message translated from <xliff:g id="from_language" example="English">%1$s</xliff:g> to <xliff:g id="to_language" example="French">%2$s</xliff:g>.</string>
+
+    <!-- Button that opens the screen with details of an application error -->
+    <string name="aerr_show_details">Show details</string>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 06715613e90c..53276355483a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4495,4 +4495,7 @@
   <java-symbol type="array" name="config_builtInDisplayIsRoundArray" />
   <java-symbol type="array" name="config_serviceStateLocationAllowedPackages" />
   <java-symbol type="dimen" name="status_bar_height_default" />
+
+  <!-- Button that opens the screen with details of an application error -->
+  <java-symbol type="string" name="aerr_show_details" />
 </resources>
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 3210af7bdebc..a89494c3ae25 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -871,5 +871,17 @@
             </intent-filter>
         </receiver>
 
+        <activity
+            android:name=".ErrorReportActivity"
+            android:exported="true"
+            android:theme="@android:style/Theme.DeviceDefault.DayNight"
+            android:documentLaunchMode="always"
+            android:process=":ui"
+        >
+            <intent-filter>
+                <action android:name="android.intent.action.APP_ERROR" />
+            </intent-filter>
+        </activity>
+
     </application>
 </manifest>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 48ff6d535874..90ac8317a40e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2355,4 +2355,9 @@
 
     <!-- Title for User Switch dialog. [CHAR LIMIT=20] -->
     <string name="qs_user_switch_dialog_title">Select user</string>
+
+    <string name="error_report_title">Error in %1$s</string>
+    <string name="copy_to_clipboard">Copy to clipboard</string>
+    <string name="copied_to_clipboard">Copied to clipboard</string>
+    <string name="error_share">Share</string>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/ErrorReportActivity.kt b/packages/SystemUI/src/com/android/systemui/ErrorReportActivity.kt
new file mode 100644
index 000000000000..94630b044fbe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ErrorReportActivity.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2022 GrapheneOS
+ *
+ * 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.systemui
+
+import android.app.Activity
+import android.app.ApplicationErrorReport
+import android.content.ClipData
+import android.content.ClipDescription
+import android.content.ClipboardManager
+import android.content.Intent
+import android.graphics.Typeface
+import android.os.Build
+import android.os.Bundle
+import android.util.StringBuilderPrinter
+import android.util.TypedValue
+import android.view.Gravity
+import android.view.View
+import android.widget.Button
+import android.widget.LinearLayout
+import android.widget.LinearLayout.LayoutParams
+import android.widget.ScrollView
+import android.widget.TextView
+import android.widget.Toast
+
+class ErrorReportActivity : Activity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        val title: String
+        val reportText: String
+        try {
+            val report = intent.getParcelableExtra<ApplicationErrorReport>(Intent.EXTRA_BUG_REPORT)!!
+            val pm = packageManager
+            val ai = pm.getApplicationInfo(report.packageName, 0)
+            title = getString(R.string.error_report_title, ai.loadLabel(pm))
+
+            reportText = errorReportToText(report)
+        } catch (e: Exception) {
+            e.printStackTrace()
+            finishAndRemoveTask()
+            return
+        }
+
+        setTitle(title)
+
+        val textView = TextView(this).apply {
+            typeface = Typeface.MONOSPACE
+            text = reportText
+            textSize = 12f
+            setTextIsSelectable(true)
+            // default color is too light
+            val color = if (resources.configuration.isNightModeActive) 0xff_d0_d0_d0 else 0xff_00_00_00
+            setTextColor(color.toInt())
+        }
+
+        val scroller = ScrollView(this).apply {
+            isScrollbarFadingEnabled = false
+            scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
+            addView(textView)
+        }
+
+        val formattedReportText = "```\n" + reportText + "\n```"
+        val clipData = ClipData.newPlainText(title, formattedReportText)
+
+        val btnCopy = Button(this).apply {
+            setText(R.string.copy_to_clipboard)
+            setOnClickListener { _ ->
+                val cm = getSystemService(ClipboardManager::class.java)
+                cm.setPrimaryClip(clipData)
+                Toast.makeText(this@ErrorReportActivity, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
+            }
+        }
+
+        val btnShare = Button(this).apply {
+            setText(R.string.error_share)
+            setOnClickListener { _ ->
+                val i = Intent(Intent.ACTION_SEND)
+                i.clipData = clipData
+                i.type = ClipDescription.MIMETYPE_TEXT_PLAIN
+                i.putExtra(Intent.EXTRA_SUBJECT, title)
+                i.putExtra(Intent.EXTRA_TEXT, formattedReportText)
+                startActivity(Intent.createChooser(i, title))
+            }
+        }
+
+        val buttonLayout = LinearLayout(this).apply {
+            orientation = LinearLayout.HORIZONTAL
+            gravity = Gravity.CENTER
+            addView(btnCopy)
+            addView(btnShare)
+        }
+
+        val pad = px(16)
+
+        val layout = LinearLayout(this).apply {
+            orientation = LinearLayout.VERTICAL
+            addView(scroller, LayoutParams(LayoutParams.MATCH_PARENT, 0, 1f))
+            addView(buttonLayout)
+            setPadding(pad, pad, pad, pad)
+        }
+
+        setContentView(layout)
+    }
+
+    fun px(dp: Int) = TypedValue.applyDimension(
+            TypedValue.COMPLEX_UNIT_PX, dp.toFloat(), resources.displayMetrics).toInt()
+
+    fun errorReportToText(r: ApplicationErrorReport) =
+
+"""type: ${reportTypeToString(r.type)}
+osVersion: ${Build.FINGERPRINT}
+package: ${r.packageName}:${r.packageVersion}
+process: ${r.processName}
+
+${reportInfoToString(r)}"""
+
+    fun reportInfoToString(r: ApplicationErrorReport): String {
+        if (r.type == ApplicationErrorReport.TYPE_CRASH) {
+            return r.crashInfo.stackTrace
+        }
+
+        val sb = StringBuilder()
+        val printer = StringBuilderPrinter(sb)
+
+        when (r.type) {
+            ApplicationErrorReport.TYPE_ANR ->
+                r.anrInfo.dump(printer, "")
+            ApplicationErrorReport.TYPE_BATTERY ->
+                r.batteryInfo.dump(printer, "")
+            ApplicationErrorReport.TYPE_RUNNING_SERVICE ->
+                r.runningServiceInfo.dump(printer, "")
+        }
+
+        return sb.toString()
+    }
+
+    fun reportTypeToString(type: Int) = when (type) {
+        ApplicationErrorReport.TYPE_CRASH -> "crash"
+        ApplicationErrorReport.TYPE_ANR -> "ANR"
+        ApplicationErrorReport.TYPE_BATTERY -> "battery"
+        ApplicationErrorReport.TYPE_RUNNING_SERVICE -> "running_service"
+        else -> "unknown ($type)"
+    }
+}
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 0bf0fe2be246..6277ba58a269 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -814,6 +814,7 @@ class AppErrors {
 
         ApplicationErrorReport report = new ApplicationErrorReport();
         report.packageName = r.info.packageName;
+        report.packageVersion = r.info.longVersionCode;
         report.installerPackageName = errState.getErrorReportReceiver().getPackageName();
         report.processName = r.processName;
         report.time = timeMillis;
