boolean handleAppCrashLocked(
      ProcessRecord app,
      String reason,
      String shortMsg,
      String longMsg,
      String stackTrace,
      AppErrorDialog.Data data) {
    long now = SystemClock.uptimeMillis();

    Long crashTime;
    Long crashTimePersistent;
    if (!app.isolated) {
      crashTime = mProcessCrashTimes.get(app.info.processName, app.uid);
      crashTimePersistent = mProcessCrashTimesPersistent.get(app.info.processName, app.uid);
    } else {
      crashTime = crashTimePersistent = null;
    }
    if (crashTime != null && now < crashTime + ProcessList.MIN_CRASH_INTERVAL) {
      // This process loses!
      Slog.w(TAG, "Process " + app.info.processName + " has crashed too many times: killing!");
      EventLog.writeEvent(
          EventLogTags.AM_PROCESS_CRASHED_TOO_MUCH, app.userId, app.info.processName, app.uid);
      mService.mStackSupervisor.handleAppCrashLocked(app);
      if (!app.persistent) {
        // We don't want to start this process again until the user
        // explicitly does so...  but for persistent process, we really
        // need to keep it running.  If a persistent process is actually
        // repeatedly crashing, then badness for everyone.
        EventLog.writeEvent(EventLogTags.AM_PROC_BAD, app.userId, app.uid, app.info.processName);
        if (!app.isolated) {
          // XXX We don't have a way to mark isolated processes
          // as bad, since they don't have a peristent identity.
          mBadProcesses.put(
              app.info.processName,
              app.uid,
              new BadProcessInfo(now, shortMsg, longMsg, stackTrace));
          mProcessCrashTimes.remove(app.info.processName, app.uid);
        }
        app.bad = true;
        app.removed = true;
        // Don't let services in this process be restarted and potentially
        // annoy the user repeatedly.  Unless it is persistent, since those
        // processes run critical code.
        mService.removeProcessLocked(app, false, false, "crash");
        mService.mStackSupervisor.resumeFocusedStackTopActivityLocked();
        return false;
      }
      mService.mStackSupervisor.resumeFocusedStackTopActivityLocked();
    } else {
      TaskRecord affectedTask =
          mService.mStackSupervisor.finishTopRunningActivityLocked(app, reason);
      if (data != null) {
        data.task = affectedTask;
      }
      if (data != null
          && crashTimePersistent != null
          && now < crashTimePersistent + ProcessList.MIN_CRASH_INTERVAL) {
        data.repeating = true;
      }
    }

    // Bump up the crash count of any services currently running in the proc.
    for (int i = app.services.size() - 1; i >= 0; i--) {
      // Any services running in the application need to be placed
      // back in the pending list.
      ServiceRecord sr = app.services.valueAt(i);
      sr.crashCount++;
    }

    // If the crashing process is what we consider to be the "home process" and it has been
    // replaced by a third-party app, clear the package preferred activities from packages
    // with a home activity running in the process to prevent a repeatedly crashing app
    // from blocking the user to manually clear the list.
    final ArrayList<ActivityRecord> activities = app.activities;
    if (app == mService.mHomeProcess
        && activities.size() > 0
        && (mService.mHomeProcess.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
      for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
        final ActivityRecord r = activities.get(activityNdx);
        if (r.isHomeActivity()) {
          Log.i(TAG, "Clearing package preferred activities from " + r.packageName);
          try {
            ActivityThread.getPackageManager().clearPackagePreferredActivities(r.packageName);
          } catch (RemoteException c) {
            // pm is in same process, this will never happen.
          }
        }
      }
    }

    if (!app.isolated) {
      // XXX Can't keep track of crash times for isolated processes,
      // because they don't have a perisistent identity.
      mProcessCrashTimes.put(app.info.processName, app.uid, now);
      mProcessCrashTimesPersistent.put(app.info.processName, app.uid, now);
    }

    if (app.crashHandler != null) mService.mHandler.post(app.crashHandler);
    return true;
  }
  void crashApplicationInner(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) {
    long timeMillis = System.currentTimeMillis();
    String shortMsg = crashInfo.exceptionClassName;
    String longMsg = crashInfo.exceptionMessage;
    String stackTrace = crashInfo.stackTrace;
    if (shortMsg != null && longMsg != null) {
      longMsg = shortMsg + ": " + longMsg;
    } else if (shortMsg != null) {
      longMsg = shortMsg;
    }

    AppErrorResult result = new AppErrorResult();
    TaskRecord task;
    synchronized (mService) {
      /**
       * If crash is handled by instance of {@link android.app.IActivityController}, finish now and
       * don't show the app error dialog.
       */
      if (handleAppCrashInActivityController(
          r, crashInfo, shortMsg, longMsg, stackTrace, timeMillis)) {
        return;
      }

      /**
       * If this process was running instrumentation, finish now - it will be handled in {@link
       * ActivityManagerService#handleAppDiedLocked}.
       */
      if (r != null && r.instrumentationClass != null) {
        return;
      }

      // Log crash in battery stats.
      if (r != null) {
        mService.mBatteryStatsService.noteProcessCrash(r.processName, r.uid);
      }

      AppErrorDialog.Data data = new AppErrorDialog.Data();
      data.result = result;
      data.proc = r;

      // If we can't identify the process or it's already exceeded its crash quota,
      // quit right away without showing a crash dialog.
      if (r == null || !makeAppCrashingLocked(r, shortMsg, longMsg, stackTrace, data)) {
        return;
      }

      Message msg = Message.obtain();
      msg.what = ActivityManagerService.SHOW_ERROR_UI_MSG;

      task = data.task;
      msg.obj = data;
      mService.mUiHandler.sendMessage(msg);
    }

    int res = result.get();

    Intent appErrorIntent = null;
    MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_CRASH, res);
    if (res == AppErrorDialog.TIMEOUT || res == AppErrorDialog.CANCEL) {
      res = AppErrorDialog.FORCE_QUIT;
    }
    synchronized (mService) {
      if (res == AppErrorDialog.MUTE) {
        stopReportingCrashesLocked(r);
      }
      if (res == AppErrorDialog.RESTART) {
        mService.removeProcessLocked(r, false, true, "crash");
        if (task != null) {
          try {
            mService.startActivityFromRecents(task.taskId, ActivityOptions.makeBasic().toBundle());
          } catch (IllegalArgumentException e) {
            // Hmm, that didn't work, app might have crashed before creating a
            // recents entry. Let's see if we have a safe-to-restart intent.
            if (task.intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
              mService.startActivityInPackage(
                  task.mCallingUid,
                  task.mCallingPackage,
                  task.intent,
                  null,
                  null,
                  null,
                  0,
                  0,
                  ActivityOptions.makeBasic().toBundle(),
                  task.userId,
                  null,
                  null);
            }
          }
        }
      }
      if (res == AppErrorDialog.FORCE_QUIT) {
        long orig = Binder.clearCallingIdentity();
        try {
          // Kill it with fire!
          mService.mStackSupervisor.handleAppCrashLocked(r);
          if (!r.persistent) {
            mService.removeProcessLocked(r, false, false, "crash");
            mService.mStackSupervisor.resumeFocusedStackTopActivityLocked();
          }
        } finally {
          Binder.restoreCallingIdentity(orig);
        }
      }
      if (res == AppErrorDialog.FORCE_QUIT_AND_REPORT) {
        appErrorIntent = createAppErrorIntentLocked(r, timeMillis, crashInfo);
      }
      if (r != null && !r.isolated && res != AppErrorDialog.RESTART) {
        // XXX Can't keep track of crash time for isolated processes,
        // since they don't have a persistent identity.
        mProcessCrashTimes.put(r.info.processName, r.uid, SystemClock.uptimeMillis());
      }
    }

    if (appErrorIntent != null) {
      try {
        mContext.startActivityAsUser(appErrorIntent, new UserHandle(r.userId));
      } catch (ActivityNotFoundException e) {
        Slog.w(TAG, "bug report receiver dissappeared", e);
      }
    }
  }