summaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
authorJacek Surazski <jaceks@google.com>2009-05-18 12:09:59 +0200
committerJacek Surazski <jaceks@google.com>2009-05-20 10:52:04 +0200
commitf5b9c72022f574417862e064cc0fdd8ea2d846dc (patch)
treea4b41387ce0aab4cb12c145f2c7a7bc3d6e1e7a2 /services
parent5bc21aa0671d83e406b46e0431816dea8d9ca5cb (diff)
downloadframeworks_base-f5b9c72022f574417862e064cc0fdd8ea2d846dc.zip
frameworks_base-f5b9c72022f574417862e064cc0fdd8ea2d846dc.tar.gz
frameworks_base-f5b9c72022f574417862e064cc0fdd8ea2d846dc.tar.bz2
ActivityManagerService sends bug reports on crashes and ANRs
If an installerPackageName was specified when the app was installed, looks for a receiver of ACTION_APP_ERROR in that package. If found, this is the bug report receiver and the crash/ANR dialog will get a "Report" button. If pressed, a bug report will be delivered.
Diffstat (limited to 'services')
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java115
-rw-r--r--services/java/com/android/server/am/AppErrorDialog.java21
-rw-r--r--services/java/com/android/server/am/AppNotRespondingDialog.java46
-rw-r--r--services/java/com/android/server/am/ProcessRecord.java13
4 files changed, 184 insertions, 11 deletions
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index fd37cc2..f2959e3 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -30,6 +30,7 @@ import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.ActivityThread;
import android.app.AlertDialog;
+import android.app.ApplicationErrorReport;
import android.app.Dialog;
import android.app.IActivityWatcher;
import android.app.IApplicationThread;
@@ -41,6 +42,7 @@ import android.app.IThumbnailReceiver;
import android.app.Instrumentation;
import android.app.PendingIntent;
import android.app.ResultInfo;
+import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -78,10 +80,14 @@ import android.os.SystemClock;
import android.os.SystemProperties;
import android.provider.Checkin;
import android.provider.Settings;
+import android.server.data.CrashData;
+import android.server.data.StackTraceElementData;
+import android.server.data.ThrowableData;
import android.text.TextUtils;
import android.util.Config;
import android.util.EventLog;
import android.util.Log;
+import android.util.LogPrinter;
import android.util.PrintWriterPrinter;
import android.util.SparseArray;
import android.view.Gravity;
@@ -92,10 +98,13 @@ import android.view.WindowManagerPolicy;
import dalvik.system.Zygote;
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
+import java.io.IOException;
import java.io.PrintWriter;
import java.lang.IllegalStateException;
import java.lang.ref.WeakReference;
@@ -7809,6 +7818,30 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
return handleAppCrashLocked(app);
}
+ private ComponentName getErrorReportReceiver(ProcessRecord app) {
+ IPackageManager pm = ActivityThread.getPackageManager();
+ try {
+ // was an installer package name specified when this app was
+ // installed?
+ String installerPackageName = pm.getInstallerPackageName(app.info.packageName);
+ if (installerPackageName == null) {
+ return null;
+ }
+
+ // is there an Activity in this package that handles ACTION_APP_ERROR?
+ Intent intent = new Intent(Intent.ACTION_APP_ERROR);
+ ResolveInfo info = pm.resolveIntentForPackage(intent, null, 0, installerPackageName);
+ if (info == null || info.activityInfo == null) {
+ return null;
+ }
+
+ return new ComponentName(installerPackageName, info.activityInfo.name);
+ } catch (RemoteException e) {
+ // will return null and no error report will be delivered
+ }
+ return null;
+ }
+
void makeAppNotRespondingLocked(ProcessRecord app,
String tag, String shortMsg, String longMsg, byte[] crashData) {
app.notResponding = true;
@@ -7927,6 +7960,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
}
void startAppProblemLocked(ProcessRecord app) {
+ app.errorReportReceiver = getErrorReportReceiver(app);
skipCurrentReceiverLocked(app);
}
@@ -7959,7 +7993,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
public int handleApplicationError(IBinder app, int flags,
String tag, String shortMsg, String longMsg, byte[] crashData) {
AppErrorResult result = new AppErrorResult();
-
ProcessRecord r = null;
synchronized (this) {
if (app != null) {
@@ -8048,16 +8081,96 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
int res = result.get();
+ Intent appErrorIntent = null;
synchronized (this) {
if (r != null) {
mProcessCrashTimes.put(r.info.processName, r.info.uid,
SystemClock.uptimeMillis());
}
+ if (res == AppErrorDialog.FORCE_QUIT_AND_REPORT) {
+ appErrorIntent = createAppErrorIntentLocked(r);
+ res = AppErrorDialog.FORCE_QUIT;
+ }
+ }
+
+ if (appErrorIntent != null) {
+ try {
+ mContext.startActivity(appErrorIntent);
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "bug report receiver dissappeared", e);
+ }
}
return res;
}
+ Intent createAppErrorIntentLocked(ProcessRecord r) {
+ ApplicationErrorReport report = createAppErrorReportLocked(r);
+ if (report == null) {
+ return null;
+ }
+ Intent result = new Intent(Intent.ACTION_APP_ERROR);
+ result.setComponent(r.errorReportReceiver);
+ result.putExtra(Intent.EXTRA_BUG_REPORT, report);
+ result.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return result;
+ }
+
+ ApplicationErrorReport createAppErrorReportLocked(ProcessRecord r) {
+ if (r.errorReportReceiver == null) {
+ return null;
+ }
+
+ if (!r.crashing && !r.notResponding) {
+ return null;
+ }
+
+ try {
+ ApplicationErrorReport report = new ApplicationErrorReport();
+ report.packageName = r.info.packageName;
+ report.installerPackageName = r.errorReportReceiver.getPackageName();
+ report.processName = r.processName;
+
+ if (r.crashing) {
+ report.type = ApplicationErrorReport.TYPE_CRASH;
+ report.crashInfo = new ApplicationErrorReport.CrashInfo();
+
+ ByteArrayInputStream byteStream = new ByteArrayInputStream(
+ r.crashingReport.crashData);
+ DataInputStream dataStream = new DataInputStream(byteStream);
+ CrashData crashData = new CrashData(dataStream);
+ ThrowableData throwData = crashData.getThrowableData();
+
+ report.time = crashData.getTime();
+ report.crashInfo.stackTrace = throwData.toString();
+
+ // extract the source of the exception, useful for report
+ // clustering
+ while (throwData.getCause() != null) {
+ throwData = throwData.getCause();
+ }
+ StackTraceElementData trace = throwData.getStackTrace()[0];
+ report.crashInfo.exceptionClassName = throwData.getType();
+ report.crashInfo.throwFileName = trace.getFileName();
+ report.crashInfo.throwClassName = trace.getClassName();
+ report.crashInfo.throwMethodName = trace.getMethodName();
+ } else if (r.notResponding) {
+ report.type = ApplicationErrorReport.TYPE_ANR;
+ report.anrInfo = new ApplicationErrorReport.AnrInfo();
+
+ report.anrInfo.activity = r.notRespondingReport.tag;
+ report.anrInfo.cause = r.notRespondingReport.shortMsg;
+ report.anrInfo.info = r.notRespondingReport.longMsg;
+ }
+
+ return report;
+ } catch (IOException e) {
+ // we don't send it
+ }
+
+ return null;
+ }
+
public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState() {
// assume our apps are happy - lazy create the list
List<ActivityManager.ProcessErrorStateInfo> errList = null;
diff --git a/services/java/com/android/server/am/AppErrorDialog.java b/services/java/com/android/server/am/AppErrorDialog.java
index 3fcfad0..33894d6 100644
--- a/services/java/com/android/server/am/AppErrorDialog.java
+++ b/services/java/com/android/server/am/AppErrorDialog.java
@@ -19,17 +19,22 @@ package com.android.server.am;
import static android.view.WindowManager.LayoutParams.FLAG_SYSTEM_ERROR;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.res.Resources;
import android.os.Handler;
import android.os.Message;
+import android.util.Log;
class AppErrorDialog extends BaseErrorDialog {
+ private final static String TAG = "AppErrorDialog";
+
private final AppErrorResult mResult;
private final ProcessRecord mProc;
// Event 'what' codes
static final int FORCE_QUIT = 0;
static final int DEBUG = 1;
+ static final int FORCE_QUIT_AND_REPORT = 2;
// 5-minute timeout, then we automatically dismiss the crash dialog
static final long DISMISS_TIMEOUT = 1000 * 60 * 5;
@@ -58,12 +63,22 @@ class AppErrorDialog extends BaseErrorDialog {
setCancelable(false);
- setButton(res.getText(com.android.internal.R.string.force_close),
- mHandler.obtainMessage(FORCE_QUIT));
+ setButton(DialogInterface.BUTTON_POSITIVE,
+ res.getText(com.android.internal.R.string.force_close),
+ mHandler.obtainMessage(FORCE_QUIT));
+
if ((flags&1) != 0) {
- setButton(res.getText(com.android.internal.R.string.debug),
+ setButton(DialogInterface.BUTTON_NEUTRAL,
+ res.getText(com.android.internal.R.string.debug),
mHandler.obtainMessage(DEBUG));
}
+
+ if (app.errorReportReceiver != null) {
+ setButton(DialogInterface.BUTTON_NEGATIVE,
+ res.getText(com.android.internal.R.string.report),
+ mHandler.obtainMessage(FORCE_QUIT_AND_REPORT));
+ }
+
setTitle(res.getText(com.android.internal.R.string.aerr_title));
getWindow().addFlags(FLAG_SYSTEM_ERROR);
getWindow().setTitle("Application Error: " + app.info.processName);
diff --git a/services/java/com/android/server/am/AppNotRespondingDialog.java b/services/java/com/android/server/am/AppNotRespondingDialog.java
index 7390ed0..03c2a04 100644
--- a/services/java/com/android/server/am/AppNotRespondingDialog.java
+++ b/services/java/com/android/server/am/AppNotRespondingDialog.java
@@ -18,7 +18,10 @@ package com.android.server.am;
import static android.view.WindowManager.LayoutParams.FLAG_SYSTEM_ERROR;
+import android.content.ActivityNotFoundException;
import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
import android.content.res.Resources;
import android.os.Handler;
import android.os.Message;
@@ -26,6 +29,13 @@ import android.os.Process;
import android.util.Log;
class AppNotRespondingDialog extends BaseErrorDialog {
+ private static final String TAG = "AppNotRespondingDialog";
+
+ // Event 'what' codes
+ static final int FORCE_CLOSE = 1;
+ static final int WAIT = 2;
+ static final int WAIT_AND_REPORT = 3;
+
private final ActivityManagerService mService;
private final ProcessRecord mProc;
@@ -67,10 +77,19 @@ class AppNotRespondingDialog extends BaseErrorDialog {
? res.getString(resid, name1.toString(), name2.toString())
: res.getString(resid, name1.toString()));
- setButton(res.getText(com.android.internal.R.string.force_close),
- mHandler.obtainMessage(1));
- setButton2(res.getText(com.android.internal.R.string.wait),
- mHandler.obtainMessage(2));
+ setButton(DialogInterface.BUTTON_POSITIVE,
+ res.getText(com.android.internal.R.string.force_close),
+ mHandler.obtainMessage(FORCE_CLOSE));
+ setButton(DialogInterface.BUTTON_NEUTRAL,
+ res.getText(com.android.internal.R.string.wait),
+ mHandler.obtainMessage(WAIT));
+
+ if (app.errorReportReceiver != null) {
+ setButton(DialogInterface.BUTTON_NEGATIVE,
+ res.getText(com.android.internal.R.string.report),
+ mHandler.obtainMessage(WAIT_AND_REPORT));
+ }
+
setTitle(res.getText(com.android.internal.R.string.anr_title));
getWindow().addFlags(FLAG_SYSTEM_ERROR);
getWindow().setTitle("Application Not Responding: " + app.info.processName);
@@ -81,16 +100,23 @@ class AppNotRespondingDialog extends BaseErrorDialog {
private final Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
+ Intent appErrorIntent = null;
switch (msg.what) {
- case 1:
+ case FORCE_CLOSE:
// Kill the application.
mService.killAppAtUsersRequest(mProc,
AppNotRespondingDialog.this, true);
break;
- case 2:
+ case WAIT_AND_REPORT:
+ case WAIT:
// Continue waiting for the application.
synchronized (mService) {
ProcessRecord app = mProc;
+
+ if (msg.what == WAIT_AND_REPORT) {
+ appErrorIntent = mService.createAppErrorIntentLocked(app);
+ }
+
app.notResponding = false;
app.notRespondingReport = null;
if (app.anrDialog == AppNotRespondingDialog.this) {
@@ -99,6 +125,14 @@ class AppNotRespondingDialog extends BaseErrorDialog {
}
break;
}
+
+ if (appErrorIntent != null) {
+ try {
+ getContext().startActivity(appErrorIntent);
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "bug report receiver dissappeared", e);
+ }
+ }
}
};
}
diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java
index 68aebc3..419dadf 100644
--- a/services/java/com/android/server/am/ProcessRecord.java
+++ b/services/java/com/android/server/am/ProcessRecord.java
@@ -107,6 +107,10 @@ class ProcessRecord implements Watchdog.PssRequestor {
ActivityManager.ProcessErrorStateInfo crashingReport;
ActivityManager.ProcessErrorStateInfo notRespondingReport;
+ // Who will be notified of the error. This is usually an activity in the
+ // app that installed the package.
+ ComponentName errorReportReceiver;
+
void dump(PrintWriter pw, String prefix) {
if (info.className != null) {
pw.print(prefix); pw.print("class="); pw.println(info.className);
@@ -157,7 +161,14 @@ class ProcessRecord implements Watchdog.PssRequestor {
pw.print(" "); pw.print(crashDialog);
pw.print(" notResponding="); pw.print(notResponding);
pw.print(" " ); pw.print(anrDialog);
- pw.print(" bad="); pw.println(bad);
+ pw.print(" bad="); pw.print(bad);
+
+ // crashing or notResponding is always set before errorReportReceiver
+ if (errorReportReceiver != null) {
+ pw.print(" errorReportReceiver=");
+ pw.print(errorReportReceiver.flattenToShortString());
+ }
+ pw.println();
}
if (activities.size() > 0) {
pw.print(prefix); pw.print("activities="); pw.println(activities);