diff options
author | Jacek Surazski <jaceks@google.com> | 2009-05-18 12:09:59 +0200 |
---|---|---|
committer | Jacek Surazski <jaceks@google.com> | 2009-05-20 10:52:04 +0200 |
commit | f5b9c72022f574417862e064cc0fdd8ea2d846dc (patch) | |
tree | a4b41387ce0aab4cb12c145f2c7a7bc3d6e1e7a2 /services | |
parent | 5bc21aa0671d83e406b46e0431816dea8d9ca5cb (diff) | |
download | frameworks_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')
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); |