Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f5b9c720 authored by Jacek Surazski's avatar Jacek Surazski
Browse files

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.
parent 5bc21aa0
Loading
Loading
Loading
Loading
+295 −0
Original line number Diff line number Diff line
/*
 * 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;
        }
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -1757,6 +1757,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. -->
+114 −1
Original line number Diff line number Diff line
@@ -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;
+18 −3
Original line number Diff line number Diff line
@@ -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),
        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);
+40 −6
Original line number Diff line number Diff line
@@ -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);
                }
            }
        }
    };
}
Loading