diff options
author | Android (Google) Code Review <android-gerrit@google.com> | 2009-05-20 08:54:40 -0700 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-05-20 08:54:40 -0700 |
commit | cad616ff92ff67bcfbbaefd6407c0f7f5e7549e7 (patch) | |
tree | e58f9af09e29016b0e9a1f7cb2eccfb6a488db92 | |
parent | ed73bad62e111fab940360ba6ca7f8dae63e1b1e (diff) | |
parent | 97dd7ac8ede4eec057977dd579f236519782be7c (diff) | |
download | frameworks_base-cad616ff92ff67bcfbbaefd6407c0f7f5e7549e7.zip frameworks_base-cad616ff92ff67bcfbbaefd6407c0f7f5e7549e7.tar.gz frameworks_base-cad616ff92ff67bcfbbaefd6407c0f7f5e7549e7.tar.bz2 |
am 97dd7ac8: Merge change 1860 into donut
Merge commit '97dd7ac8ede4eec057977dd579f236519782be7c'
* commit '97dd7ac8ede4eec057977dd579f236519782be7c':
ActivityManagerService sends bug reports on crashes and ANRs
6 files changed, 481 insertions, 11 deletions
diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java new file mode 100644 index 0000000..72cbff4 --- /dev/null +++ b/core/java/android/app/ApplicationErrorReport.java @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2008 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 android.app; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; +import android.util.Printer; + +/** + * Describes an application error. + * + * A report has a type, which is one of + * <ul> + * <li> {@link #TYPE_CRASH} application crash. Information about the crash + * is stored in {@link #crashInfo}. + * <li> {@link #TYPE_ANR} application not responding. Information about the + * ANR is stored in {@link #anrInfo}. + * <li> {@link #TYPE_NONE} uninitialized instance of {@link ApplicationErrorReport}. + * </ul> + * + * @hide + */ + +public class ApplicationErrorReport implements Parcelable { + /** + * Uninitialized error report. + */ + public static final int TYPE_NONE = 0; + + /** + * An error report about an application crash. + */ + public static final int TYPE_CRASH = 1; + + /** + * An error report about an application that's not responding. + */ + public static final int TYPE_ANR = 2; + + /** + * Type of this report. Can be one of {@link #TYPE_NONE}, + * {@link #TYPE_CRASH} or {@link #TYPE_ANR}. + */ + public int type; + + /** + * Package name of the application. + */ + public String packageName; + + /** + * Package name of the application which installed the application this + * report pertains to. + * This identifies which Market the application came from. + */ + public String installerPackageName; + + /** + * Process name of the application. + */ + public String processName; + + /** + * Time at which the error occurred. + */ + public long time; + + /** + * If this report is of type {@link #TYPE_CRASH}, contains an instance + * of CrashInfo describing the crash; otherwise null. + */ + public CrashInfo crashInfo; + + /** + * If this report is of type {@link #TYPE_ANR}, contains an instance + * of AnrInfo describing the ANR; otherwise null. + */ + public AnrInfo anrInfo; + + /** + * Create an uninitialized instance of {@link ApplicationErrorReport}. + */ + public ApplicationErrorReport() { + } + + /** + * Create an instance of {@link ApplicationErrorReport} initialized from + * a parcel. + */ + ApplicationErrorReport(Parcel in) { + type = in.readInt(); + packageName = in.readString(); + installerPackageName = in.readString(); + processName = in.readString(); + time = in.readLong(); + + switch (type) { + case TYPE_CRASH: + crashInfo = new CrashInfo(in); + break; + case TYPE_ANR: + anrInfo = new AnrInfo(in); + break; + } + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(type); + dest.writeString(packageName); + dest.writeString(installerPackageName); + dest.writeString(processName); + dest.writeLong(time); + + switch (type) { + case TYPE_CRASH: + crashInfo.writeToParcel(dest, flags); + break; + case TYPE_ANR: + anrInfo.writeToParcel(dest, flags); + break; + } + } + + /** + * Describes an application crash. + */ + public static class CrashInfo { + /** + * Class name of the exception that caused the crash. + */ + public String exceptionClassName; + + /** + * File which the exception was thrown from. + */ + public String throwFileName; + + /** + * Class which the exception was thrown from. + */ + public String throwClassName; + + /** + * Method which the exception was thrown from. + */ + public String throwMethodName; + + /** + * Stack trace. + */ + public String stackTrace; + + /** + * Create an uninitialized instance of CrashInfo. + */ + public CrashInfo() { + } + + /** + * Create an instance of CrashInfo initialized from a Parcel. + */ + public CrashInfo(Parcel in) { + exceptionClassName = in.readString(); + throwFileName = in.readString(); + throwClassName = in.readString(); + throwMethodName = in.readString(); + stackTrace = in.readString(); + } + + /** + * Save a CrashInfo instance to a parcel. + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(exceptionClassName); + dest.writeString(throwFileName); + dest.writeString(throwClassName); + dest.writeString(throwMethodName); + dest.writeString(stackTrace); + } + + /** + * Dump a CrashInfo instance to a Printer. + */ + public void dump(Printer pw, String prefix) { + pw.println(prefix + "exceptionClassName: " + exceptionClassName); + pw.println(prefix + "throwFileName: " + throwFileName); + pw.println(prefix + "throwClassName: " + throwClassName); + pw.println(prefix + "throwMethodName: " + throwMethodName); + pw.println(prefix + "stackTrace: " + stackTrace); + } + } + + /** + * Describes an application not responding error. + */ + public static class AnrInfo { + /** + * Activity name. + */ + public String activity; + + /** + * Description of the operation that timed out. + */ + public String cause; + + /** + * Additional info, including CPU stats. + */ + public String info; + + /** + * Create an uninitialized instance of AnrInfo. + */ + public AnrInfo() { + } + + /** + * Create an instance of AnrInfo initialized from a Parcel. + */ + public AnrInfo(Parcel in) { + activity = in.readString(); + cause = in.readString(); + info = in.readString(); + } + + /** + * Save an AnrInfo instance to a parcel. + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(activity); + dest.writeString(cause); + dest.writeString(info); + } + + /** + * Dump an AnrInfo instance to a Printer. + */ + public void dump(Printer pw, String prefix) { + pw.println(prefix + "activity: " + activity); + pw.println(prefix + "cause: " + cause); + pw.println(prefix + "info: " + info); + } + } + + public static final Parcelable.Creator<ApplicationErrorReport> CREATOR + = new Parcelable.Creator<ApplicationErrorReport>() { + public ApplicationErrorReport createFromParcel(Parcel source) { + return new ApplicationErrorReport(source); + } + + public ApplicationErrorReport[] newArray(int size) { + return new ApplicationErrorReport[size]; + } + }; + + public int describeContents() { + return 0; + } + + /** + * Dump the report to a Printer. + */ + public void dump(Printer pw, String prefix) { + pw.println(prefix + "type: " + type); + pw.println(prefix + "packageName: " + packageName); + pw.println(prefix + "installerPackageName: " + installerPackageName); + pw.println(prefix + "processName: " + processName); + pw.println(prefix + "time: " + time); + + switch (type) { + case TYPE_CRASH: + crashInfo.dump(pw, prefix); + break; + case TYPE_ANR: + anrInfo.dump(pw, prefix); + break; + } + } +} diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 855089d..270a078 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1761,6 +1761,8 @@ <string name="anr_process">Process <xliff:g id="process">%1$s</xliff:g> is not responding.</string> <!-- Button allowing the user to close an application that is not responding. This will kill the application. --> <string name="force_close">Force close</string> + <!-- Button allowing the user to send a bug report for application which has encountered an error. --> + <string name="report">Report</string> <!-- Button allowing the user to choose to wait for an application that is not responding to become responsive again. --> <string name="wait">Wait</string> <!-- Button allowing a developer to connect a debugger to an application that is not responding. --> 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); |