1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.server.am; 18 19 import com.android.internal.app.ProcessMap; 20 import com.android.internal.logging.MetricsLogger; 21 import com.android.internal.logging.nano.MetricsProto; 22 import com.android.internal.os.ProcessCpuTracker; 23 import com.android.server.RescueParty; 24 import com.android.server.Watchdog; 25 26 import android.app.ActivityManager; 27 import android.app.ActivityOptions; 28 import android.app.ActivityThread; 29 import android.app.AppOpsManager; 30 import android.app.ApplicationErrorReport; 31 import android.app.Dialog; 32 import android.content.ActivityNotFoundException; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.content.pm.ApplicationInfo; 36 import android.net.Uri; 37 import android.os.Binder; 38 import android.os.Message; 39 import android.os.Process; 40 import android.os.RemoteException; 41 import android.os.SystemClock; 42 import android.os.SystemProperties; 43 import android.os.UserHandle; 44 import android.provider.Settings; 45 import android.util.ArrayMap; 46 import android.util.ArraySet; 47 import android.util.EventLog; 48 import android.util.Log; 49 import android.util.Slog; 50 import android.util.StatsLog; 51 import android.util.SparseArray; 52 import android.util.TimeUtils; 53 import android.util.proto.ProtoOutputStream; 54 55 import java.io.File; 56 import java.io.FileDescriptor; 57 import java.io.PrintWriter; 58 import java.util.ArrayList; 59 import java.util.Collections; 60 import java.util.HashMap; 61 import java.util.Set; 62 63 import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST; 64 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR; 65 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; 66 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; 67 import static com.android.server.am.ActivityManagerService.MY_PID; 68 import static com.android.server.am.ActivityManagerService.SYSTEM_DEBUGGABLE; 69 70 /** 71 * Controls error conditions in applications. 72 */ 73 class AppErrors { 74 75 private static final String TAG = TAG_WITH_CLASS_NAME ? "AppErrors" : TAG_AM; 76 77 private final ActivityManagerService mService; 78 private final Context mContext; 79 80 private ArraySet<String> mAppsNotReportingCrashes; 81 82 /** 83 * The last time that various processes have crashed since they were last explicitly started. 84 */ 85 private final ProcessMap<Long> mProcessCrashTimes = new ProcessMap<>(); 86 87 /** 88 * The last time that various processes have crashed (not reset even when explicitly started). 89 */ 90 private final ProcessMap<Long> mProcessCrashTimesPersistent = new ProcessMap<>(); 91 92 /** 93 * Set of applications that we consider to be bad, and will reject 94 * incoming broadcasts from (which the user has no control over). 95 * Processes are added to this set when they have crashed twice within 96 * a minimum amount of time; they are removed from it when they are 97 * later restarted (hopefully due to some user action). The value is the 98 * time it was added to the list. 99 */ 100 private final ProcessMap<BadProcessInfo> mBadProcesses = new ProcessMap<>(); 101 102 AppErrors(Context context, ActivityManagerService service)103 AppErrors(Context context, ActivityManagerService service) { 104 context.assertRuntimeOverlayThemable(); 105 mService = service; 106 mContext = context; 107 } 108 writeToProto(ProtoOutputStream proto, long fieldId, String dumpPackage)109 void writeToProto(ProtoOutputStream proto, long fieldId, String dumpPackage) { 110 if (mProcessCrashTimes.getMap().isEmpty() && mBadProcesses.getMap().isEmpty()) { 111 return; 112 } 113 114 final long token = proto.start(fieldId); 115 final long now = SystemClock.uptimeMillis(); 116 proto.write(AppErrorsProto.NOW_UPTIME_MS, now); 117 118 if (!mProcessCrashTimes.getMap().isEmpty()) { 119 final ArrayMap<String, SparseArray<Long>> pmap = mProcessCrashTimes.getMap(); 120 final int procCount = pmap.size(); 121 for (int ip = 0; ip < procCount; ip++) { 122 final long ctoken = proto.start(AppErrorsProto.PROCESS_CRASH_TIMES); 123 final String pname = pmap.keyAt(ip); 124 final SparseArray<Long> uids = pmap.valueAt(ip); 125 final int uidCount = uids.size(); 126 127 proto.write(AppErrorsProto.ProcessCrashTime.PROCESS_NAME, pname); 128 for (int i = 0; i < uidCount; i++) { 129 final int puid = uids.keyAt(i); 130 final ProcessRecord r = mService.mProcessNames.get(pname, puid); 131 if (dumpPackage != null && (r == null || !r.pkgList.containsKey(dumpPackage))) { 132 continue; 133 } 134 final long etoken = proto.start(AppErrorsProto.ProcessCrashTime.ENTRIES); 135 proto.write(AppErrorsProto.ProcessCrashTime.Entry.UID, puid); 136 proto.write(AppErrorsProto.ProcessCrashTime.Entry.LAST_CRASHED_AT_MS, 137 uids.valueAt(i)); 138 proto.end(etoken); 139 } 140 proto.end(ctoken); 141 } 142 143 } 144 145 if (!mBadProcesses.getMap().isEmpty()) { 146 final ArrayMap<String, SparseArray<BadProcessInfo>> pmap = mBadProcesses.getMap(); 147 final int processCount = pmap.size(); 148 for (int ip = 0; ip < processCount; ip++) { 149 final long btoken = proto.start(AppErrorsProto.BAD_PROCESSES); 150 final String pname = pmap.keyAt(ip); 151 final SparseArray<BadProcessInfo> uids = pmap.valueAt(ip); 152 final int uidCount = uids.size(); 153 154 proto.write(AppErrorsProto.BadProcess.PROCESS_NAME, pname); 155 for (int i = 0; i < uidCount; i++) { 156 final int puid = uids.keyAt(i); 157 final ProcessRecord r = mService.mProcessNames.get(pname, puid); 158 if (dumpPackage != null && (r == null 159 || !r.pkgList.containsKey(dumpPackage))) { 160 continue; 161 } 162 final BadProcessInfo info = uids.valueAt(i); 163 final long etoken = proto.start(AppErrorsProto.BadProcess.ENTRIES); 164 proto.write(AppErrorsProto.BadProcess.Entry.UID, puid); 165 proto.write(AppErrorsProto.BadProcess.Entry.CRASHED_AT_MS, info.time); 166 proto.write(AppErrorsProto.BadProcess.Entry.SHORT_MSG, info.shortMsg); 167 proto.write(AppErrorsProto.BadProcess.Entry.LONG_MSG, info.longMsg); 168 proto.write(AppErrorsProto.BadProcess.Entry.STACK, info.stack); 169 proto.end(etoken); 170 } 171 proto.end(btoken); 172 } 173 } 174 175 proto.end(token); 176 } 177 dumpLocked(FileDescriptor fd, PrintWriter pw, boolean needSep, String dumpPackage)178 boolean dumpLocked(FileDescriptor fd, PrintWriter pw, boolean needSep, String dumpPackage) { 179 if (!mProcessCrashTimes.getMap().isEmpty()) { 180 boolean printed = false; 181 final long now = SystemClock.uptimeMillis(); 182 final ArrayMap<String, SparseArray<Long>> pmap = mProcessCrashTimes.getMap(); 183 final int processCount = pmap.size(); 184 for (int ip = 0; ip < processCount; ip++) { 185 final String pname = pmap.keyAt(ip); 186 final SparseArray<Long> uids = pmap.valueAt(ip); 187 final int uidCount = uids.size(); 188 for (int i = 0; i < uidCount; i++) { 189 final int puid = uids.keyAt(i); 190 final ProcessRecord r = mService.mProcessNames.get(pname, puid); 191 if (dumpPackage != null && (r == null 192 || !r.pkgList.containsKey(dumpPackage))) { 193 continue; 194 } 195 if (!printed) { 196 if (needSep) pw.println(); 197 needSep = true; 198 pw.println(" Time since processes crashed:"); 199 printed = true; 200 } 201 pw.print(" Process "); pw.print(pname); 202 pw.print(" uid "); pw.print(puid); 203 pw.print(": last crashed "); 204 TimeUtils.formatDuration(now-uids.valueAt(i), pw); 205 pw.println(" ago"); 206 } 207 } 208 } 209 210 if (!mBadProcesses.getMap().isEmpty()) { 211 boolean printed = false; 212 final ArrayMap<String, SparseArray<BadProcessInfo>> pmap = mBadProcesses.getMap(); 213 final int processCount = pmap.size(); 214 for (int ip = 0; ip < processCount; ip++) { 215 final String pname = pmap.keyAt(ip); 216 final SparseArray<BadProcessInfo> uids = pmap.valueAt(ip); 217 final int uidCount = uids.size(); 218 for (int i = 0; i < uidCount; i++) { 219 final int puid = uids.keyAt(i); 220 final ProcessRecord r = mService.mProcessNames.get(pname, puid); 221 if (dumpPackage != null && (r == null 222 || !r.pkgList.containsKey(dumpPackage))) { 223 continue; 224 } 225 if (!printed) { 226 if (needSep) pw.println(); 227 needSep = true; 228 pw.println(" Bad processes:"); 229 printed = true; 230 } 231 final BadProcessInfo info = uids.valueAt(i); 232 pw.print(" Bad process "); pw.print(pname); 233 pw.print(" uid "); pw.print(puid); 234 pw.print(": crashed at time "); pw.println(info.time); 235 if (info.shortMsg != null) { 236 pw.print(" Short msg: "); pw.println(info.shortMsg); 237 } 238 if (info.longMsg != null) { 239 pw.print(" Long msg: "); pw.println(info.longMsg); 240 } 241 if (info.stack != null) { 242 pw.println(" Stack:"); 243 int lastPos = 0; 244 for (int pos = 0; pos < info.stack.length(); pos++) { 245 if (info.stack.charAt(pos) == '\n') { 246 pw.print(" "); 247 pw.write(info.stack, lastPos, pos-lastPos); 248 pw.println(); 249 lastPos = pos+1; 250 } 251 } 252 if (lastPos < info.stack.length()) { 253 pw.print(" "); 254 pw.write(info.stack, lastPos, info.stack.length()-lastPos); 255 pw.println(); 256 } 257 } 258 } 259 } 260 } 261 return needSep; 262 } 263 isBadProcessLocked(ApplicationInfo info)264 boolean isBadProcessLocked(ApplicationInfo info) { 265 return mBadProcesses.get(info.processName, info.uid) != null; 266 } 267 clearBadProcessLocked(ApplicationInfo info)268 void clearBadProcessLocked(ApplicationInfo info) { 269 mBadProcesses.remove(info.processName, info.uid); 270 } 271 resetProcessCrashTimeLocked(ApplicationInfo info)272 void resetProcessCrashTimeLocked(ApplicationInfo info) { 273 mProcessCrashTimes.remove(info.processName, info.uid); 274 } 275 resetProcessCrashTimeLocked(boolean resetEntireUser, int appId, int userId)276 void resetProcessCrashTimeLocked(boolean resetEntireUser, int appId, int userId) { 277 final ArrayMap<String, SparseArray<Long>> pmap = mProcessCrashTimes.getMap(); 278 for (int ip = pmap.size() - 1; ip >= 0; ip--) { 279 SparseArray<Long> ba = pmap.valueAt(ip); 280 for (int i = ba.size() - 1; i >= 0; i--) { 281 boolean remove = false; 282 final int entUid = ba.keyAt(i); 283 if (!resetEntireUser) { 284 if (userId == UserHandle.USER_ALL) { 285 if (UserHandle.getAppId(entUid) == appId) { 286 remove = true; 287 } 288 } else { 289 if (entUid == UserHandle.getUid(userId, appId)) { 290 remove = true; 291 } 292 } 293 } else if (UserHandle.getUserId(entUid) == userId) { 294 remove = true; 295 } 296 if (remove) { 297 ba.removeAt(i); 298 } 299 } 300 if (ba.size() == 0) { 301 pmap.removeAt(ip); 302 } 303 } 304 } 305 loadAppsNotReportingCrashesFromConfigLocked(String appsNotReportingCrashesConfig)306 void loadAppsNotReportingCrashesFromConfigLocked(String appsNotReportingCrashesConfig) { 307 if (appsNotReportingCrashesConfig != null) { 308 final String[] split = appsNotReportingCrashesConfig.split(","); 309 if (split.length > 0) { 310 mAppsNotReportingCrashes = new ArraySet<>(); 311 Collections.addAll(mAppsNotReportingCrashes, split); 312 } 313 } 314 } 315 killAppAtUserRequestLocked(ProcessRecord app, Dialog fromDialog)316 void killAppAtUserRequestLocked(ProcessRecord app, Dialog fromDialog) { 317 app.crashing = false; 318 app.crashingReport = null; 319 app.notResponding = false; 320 app.notRespondingReport = null; 321 if (app.anrDialog == fromDialog) { 322 app.anrDialog = null; 323 } 324 if (app.waitDialog == fromDialog) { 325 app.waitDialog = null; 326 } 327 if (app.pid > 0 && app.pid != MY_PID) { 328 handleAppCrashLocked(app, "user-terminated" /*reason*/, 329 null /*shortMsg*/, null /*longMsg*/, null /*stackTrace*/, null /*data*/); 330 app.kill("user request after error", true); 331 } 332 } 333 334 /** 335 * Induce a crash in the given app. 336 * 337 * @param uid if nonnegative, the required matching uid of the target to crash 338 * @param initialPid fast-path match for the target to crash 339 * @param packageName fallback match if the stated pid is not found or doesn't match uid 340 * @param userId If nonnegative, required to identify a match by package name 341 * @param message 342 */ scheduleAppCrashLocked(int uid, int initialPid, String packageName, int userId, String message)343 void scheduleAppCrashLocked(int uid, int initialPid, String packageName, int userId, 344 String message) { 345 ProcessRecord proc = null; 346 347 // Figure out which process to kill. We don't trust that initialPid 348 // still has any relation to current pids, so must scan through the 349 // list. 350 351 synchronized (mService.mPidsSelfLocked) { 352 for (int i=0; i<mService.mPidsSelfLocked.size(); i++) { 353 ProcessRecord p = mService.mPidsSelfLocked.valueAt(i); 354 if (uid >= 0 && p.uid != uid) { 355 continue; 356 } 357 if (p.pid == initialPid) { 358 proc = p; 359 break; 360 } 361 if (p.pkgList.containsKey(packageName) 362 && (userId < 0 || p.userId == userId)) { 363 proc = p; 364 } 365 } 366 } 367 368 if (proc == null) { 369 Slog.w(TAG, "crashApplication: nothing for uid=" + uid 370 + " initialPid=" + initialPid 371 + " packageName=" + packageName 372 + " userId=" + userId); 373 return; 374 } 375 376 proc.scheduleCrash(message); 377 } 378 379 /** 380 * Bring up the "unexpected error" dialog box for a crashing app. 381 * Deal with edge cases (intercepts from instrumented applications, 382 * ActivityController, error intent receivers, that sort of thing). 383 * @param r the application crashing 384 * @param crashInfo describing the failure 385 */ crashApplication(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo)386 void crashApplication(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) { 387 final int callingPid = Binder.getCallingPid(); 388 final int callingUid = Binder.getCallingUid(); 389 390 final long origId = Binder.clearCallingIdentity(); 391 try { 392 crashApplicationInner(r, crashInfo, callingPid, callingUid); 393 } finally { 394 Binder.restoreCallingIdentity(origId); 395 } 396 } 397 crashApplicationInner(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo, int callingPid, int callingUid)398 void crashApplicationInner(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo, 399 int callingPid, int callingUid) { 400 long timeMillis = System.currentTimeMillis(); 401 String shortMsg = crashInfo.exceptionClassName; 402 String longMsg = crashInfo.exceptionMessage; 403 String stackTrace = crashInfo.stackTrace; 404 if (shortMsg != null && longMsg != null) { 405 longMsg = shortMsg + ": " + longMsg; 406 } else if (shortMsg != null) { 407 longMsg = shortMsg; 408 } 409 410 // If a persistent app is stuck in a crash loop, the device isn't very 411 // usable, so we want to consider sending out a rescue party. 412 if (r != null && r.persistent) { 413 RescueParty.notePersistentAppCrash(mContext, r.uid); 414 } 415 416 AppErrorResult result = new AppErrorResult(); 417 TaskRecord task; 418 synchronized (mService) { 419 /** 420 * If crash is handled by instance of {@link android.app.IActivityController}, 421 * finish now and don't show the app error dialog. 422 */ 423 if (handleAppCrashInActivityController(r, crashInfo, shortMsg, longMsg, stackTrace, 424 timeMillis, callingPid, callingUid)) { 425 return; 426 } 427 428 /** 429 * If this process was running instrumentation, finish now - it will be handled in 430 * {@link ActivityManagerService#handleAppDiedLocked}. 431 */ 432 if (r != null && r.instr != null) { 433 return; 434 } 435 436 // Log crash in battery stats. 437 if (r != null) { 438 mService.mBatteryStatsService.noteProcessCrash(r.processName, r.uid); 439 } 440 441 AppErrorDialog.Data data = new AppErrorDialog.Data(); 442 data.result = result; 443 data.proc = r; 444 445 // If we can't identify the process or it's already exceeded its crash quota, 446 // quit right away without showing a crash dialog. 447 if (r == null || !makeAppCrashingLocked(r, shortMsg, longMsg, stackTrace, data)) { 448 return; 449 } 450 451 final Message msg = Message.obtain(); 452 msg.what = ActivityManagerService.SHOW_ERROR_UI_MSG; 453 454 task = data.task; 455 msg.obj = data; 456 mService.mUiHandler.sendMessage(msg); 457 } 458 459 int res = result.get(); 460 461 Intent appErrorIntent = null; 462 MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_CRASH, res); 463 if (res == AppErrorDialog.TIMEOUT || res == AppErrorDialog.CANCEL) { 464 res = AppErrorDialog.FORCE_QUIT; 465 } 466 synchronized (mService) { 467 if (res == AppErrorDialog.MUTE) { 468 stopReportingCrashesLocked(r); 469 } 470 if (res == AppErrorDialog.RESTART) { 471 mService.removeProcessLocked(r, false, true, "crash"); 472 if (task != null) { 473 try { 474 mService.startActivityFromRecents(task.taskId, 475 ActivityOptions.makeBasic().toBundle()); 476 } catch (IllegalArgumentException e) { 477 // Hmm, that didn't work, app might have crashed before creating a 478 // recents entry. Let's see if we have a safe-to-restart intent. 479 final Set<String> cats = task.intent != null 480 ? task.intent.getCategories() : null; 481 if (cats != null && cats.contains(Intent.CATEGORY_LAUNCHER)) { 482 mService.getActivityStartController().startActivityInPackage( 483 task.mCallingUid, callingPid, callingUid, task.mCallingPackage, 484 task.intent, null, null, null, 0, 0, 485 new SafeActivityOptions(ActivityOptions.makeBasic()), 486 task.userId, null, 487 "AppErrors", false /*validateIncomingUser*/); 488 } 489 } 490 } 491 } 492 if (res == AppErrorDialog.FORCE_QUIT) { 493 long orig = Binder.clearCallingIdentity(); 494 try { 495 // Kill it with fire! 496 mService.mStackSupervisor.handleAppCrashLocked(r); 497 if (!r.persistent) { 498 mService.removeProcessLocked(r, false, false, "crash"); 499 mService.mStackSupervisor.resumeFocusedStackTopActivityLocked(); 500 } 501 } finally { 502 Binder.restoreCallingIdentity(orig); 503 } 504 } 505 if (res == AppErrorDialog.APP_INFO) { 506 appErrorIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 507 appErrorIntent.setData(Uri.parse("package:" + r.info.packageName)); 508 appErrorIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 509 } 510 if (res == AppErrorDialog.FORCE_QUIT_AND_REPORT) { 511 appErrorIntent = createAppErrorIntentLocked(r, timeMillis, crashInfo); 512 } 513 if (r != null && !r.isolated && res != AppErrorDialog.RESTART) { 514 // XXX Can't keep track of crash time for isolated processes, 515 // since they don't have a persistent identity. 516 mProcessCrashTimes.put(r.info.processName, r.uid, 517 SystemClock.uptimeMillis()); 518 } 519 } 520 521 if (appErrorIntent != null) { 522 try { 523 mContext.startActivityAsUser(appErrorIntent, new UserHandle(r.userId)); 524 } catch (ActivityNotFoundException e) { 525 Slog.w(TAG, "bug report receiver dissappeared", e); 526 } 527 } 528 } 529 handleAppCrashInActivityController(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo, String shortMsg, String longMsg, String stackTrace, long timeMillis, int callingPid, int callingUid)530 private boolean handleAppCrashInActivityController(ProcessRecord r, 531 ApplicationErrorReport.CrashInfo crashInfo, 532 String shortMsg, String longMsg, 533 String stackTrace, long timeMillis, 534 int callingPid, int callingUid) { 535 if (mService.mController == null) { 536 return false; 537 } 538 539 try { 540 String name = r != null ? r.processName : null; 541 int pid = r != null ? r.pid : callingPid; 542 int uid = r != null ? r.info.uid : callingUid; 543 if (!mService.mController.appCrashed(name, pid, 544 shortMsg, longMsg, timeMillis, crashInfo.stackTrace)) { 545 if ("1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0")) 546 && "Native crash".equals(crashInfo.exceptionClassName)) { 547 Slog.w(TAG, "Skip killing native crashed app " + name 548 + "(" + pid + ") during testing"); 549 } else { 550 Slog.w(TAG, "Force-killing crashed app " + name 551 + " at watcher's request"); 552 if (r != null) { 553 if (!makeAppCrashingLocked(r, shortMsg, longMsg, stackTrace, null)) 554 { 555 r.kill("crash", true); 556 } 557 } else { 558 // Huh. 559 Process.killProcess(pid); 560 ActivityManagerService.killProcessGroup(uid, pid); 561 } 562 } 563 return true; 564 } 565 } catch (RemoteException e) { 566 mService.mController = null; 567 Watchdog.getInstance().setActivityController(null); 568 } 569 return false; 570 } 571 makeAppCrashingLocked(ProcessRecord app, String shortMsg, String longMsg, String stackTrace, AppErrorDialog.Data data)572 private boolean makeAppCrashingLocked(ProcessRecord app, 573 String shortMsg, String longMsg, String stackTrace, AppErrorDialog.Data data) { 574 app.crashing = true; 575 app.crashingReport = generateProcessError(app, 576 ActivityManager.ProcessErrorStateInfo.CRASHED, null, shortMsg, longMsg, stackTrace); 577 startAppProblemLocked(app); 578 app.stopFreezingAllLocked(); 579 return handleAppCrashLocked(app, "force-crash" /*reason*/, shortMsg, longMsg, stackTrace, 580 data); 581 } 582 startAppProblemLocked(ProcessRecord app)583 void startAppProblemLocked(ProcessRecord app) { 584 // If this app is not running under the current user, then we 585 // can't give it a report button because that would require 586 // launching the report UI under a different user. 587 app.errorReportReceiver = null; 588 589 for (int userId : mService.mUserController.getCurrentProfileIds()) { 590 if (app.userId == userId) { 591 app.errorReportReceiver = ApplicationErrorReport.getErrorReportReceiver( 592 mContext, app.info.packageName, app.info.flags); 593 } 594 } 595 mService.skipCurrentReceiverLocked(app); 596 } 597 598 /** 599 * Generate a process error record, suitable for attachment to a ProcessRecord. 600 * 601 * @param app The ProcessRecord in which the error occurred. 602 * @param condition Crashing, Application Not Responding, etc. Values are defined in 603 * ActivityManager.ProcessErrorStateInfo 604 * @param activity The activity associated with the crash, if known. 605 * @param shortMsg Short message describing the crash. 606 * @param longMsg Long message describing the crash. 607 * @param stackTrace Full crash stack trace, may be null. 608 * 609 * @return Returns a fully-formed ProcessErrorStateInfo record. 610 */ generateProcessError(ProcessRecord app, int condition, String activity, String shortMsg, String longMsg, String stackTrace)611 private ActivityManager.ProcessErrorStateInfo generateProcessError(ProcessRecord app, 612 int condition, String activity, String shortMsg, String longMsg, String stackTrace) { 613 ActivityManager.ProcessErrorStateInfo report = new ActivityManager.ProcessErrorStateInfo(); 614 615 report.condition = condition; 616 report.processName = app.processName; 617 report.pid = app.pid; 618 report.uid = app.info.uid; 619 report.tag = activity; 620 report.shortMsg = shortMsg; 621 report.longMsg = longMsg; 622 report.stackTrace = stackTrace; 623 624 return report; 625 } 626 createAppErrorIntentLocked(ProcessRecord r, long timeMillis, ApplicationErrorReport.CrashInfo crashInfo)627 Intent createAppErrorIntentLocked(ProcessRecord r, 628 long timeMillis, ApplicationErrorReport.CrashInfo crashInfo) { 629 ApplicationErrorReport report = createAppErrorReportLocked(r, timeMillis, crashInfo); 630 if (report == null) { 631 return null; 632 } 633 Intent result = new Intent(Intent.ACTION_APP_ERROR); 634 result.setComponent(r.errorReportReceiver); 635 result.putExtra(Intent.EXTRA_BUG_REPORT, report); 636 result.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 637 return result; 638 } 639 createAppErrorReportLocked(ProcessRecord r, long timeMillis, ApplicationErrorReport.CrashInfo crashInfo)640 private ApplicationErrorReport createAppErrorReportLocked(ProcessRecord r, 641 long timeMillis, ApplicationErrorReport.CrashInfo crashInfo) { 642 if (r.errorReportReceiver == null) { 643 return null; 644 } 645 646 if (!r.crashing && !r.notResponding && !r.forceCrashReport) { 647 return null; 648 } 649 650 ApplicationErrorReport report = new ApplicationErrorReport(); 651 report.packageName = r.info.packageName; 652 report.installerPackageName = r.errorReportReceiver.getPackageName(); 653 report.processName = r.processName; 654 report.time = timeMillis; 655 report.systemApp = (r.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0; 656 657 if (r.crashing || r.forceCrashReport) { 658 report.type = ApplicationErrorReport.TYPE_CRASH; 659 report.crashInfo = crashInfo; 660 } else if (r.notResponding) { 661 report.type = ApplicationErrorReport.TYPE_ANR; 662 report.anrInfo = new ApplicationErrorReport.AnrInfo(); 663 664 report.anrInfo.activity = r.notRespondingReport.tag; 665 report.anrInfo.cause = r.notRespondingReport.shortMsg; 666 report.anrInfo.info = r.notRespondingReport.longMsg; 667 } 668 669 return report; 670 } 671 handleAppCrashLocked(ProcessRecord app, String reason, String shortMsg, String longMsg, String stackTrace, AppErrorDialog.Data data)672 boolean handleAppCrashLocked(ProcessRecord app, String reason, 673 String shortMsg, String longMsg, String stackTrace, AppErrorDialog.Data data) { 674 final long now = SystemClock.uptimeMillis(); 675 final boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(), 676 Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0; 677 678 final boolean procIsBoundForeground = 679 (app.curProcState == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE); 680 681 Long crashTime; 682 Long crashTimePersistent; 683 boolean tryAgain = false; 684 685 if (!app.isolated) { 686 crashTime = mProcessCrashTimes.get(app.info.processName, app.uid); 687 crashTimePersistent = mProcessCrashTimesPersistent.get(app.info.processName, app.uid); 688 } else { 689 crashTime = crashTimePersistent = null; 690 } 691 692 // Bump up the crash count of any services currently running in the proc. 693 for (int i = app.services.size() - 1; i >= 0; i--) { 694 // Any services running in the application need to be placed 695 // back in the pending list. 696 ServiceRecord sr = app.services.valueAt(i); 697 // If the service was restarted a while ago, then reset crash count, else increment it. 698 if (now > sr.restartTime + ProcessList.MIN_CRASH_INTERVAL) { 699 sr.crashCount = 1; 700 } else { 701 sr.crashCount++; 702 } 703 // Allow restarting for started or bound foreground services that are crashing. 704 // This includes wallpapers. 705 if (sr.crashCount < mService.mConstants.BOUND_SERVICE_MAX_CRASH_RETRY 706 && (sr.isForeground || procIsBoundForeground)) { 707 tryAgain = true; 708 } 709 } 710 711 if (crashTime != null && now < crashTime + ProcessList.MIN_CRASH_INTERVAL) { 712 // The process crashed again very quickly. If it was a bound foreground service, let's 713 // try to restart again in a while, otherwise the process loses! 714 Slog.w(TAG, "Process " + app.info.processName 715 + " has crashed too many times: killing!"); 716 EventLog.writeEvent(EventLogTags.AM_PROCESS_CRASHED_TOO_MUCH, 717 app.userId, app.info.processName, app.uid); 718 mService.mStackSupervisor.handleAppCrashLocked(app); 719 if (!app.persistent) { 720 // We don't want to start this process again until the user 721 // explicitly does so... but for persistent process, we really 722 // need to keep it running. If a persistent process is actually 723 // repeatedly crashing, then badness for everyone. 724 EventLog.writeEvent(EventLogTags.AM_PROC_BAD, app.userId, app.uid, 725 app.info.processName); 726 if (!app.isolated) { 727 // XXX We don't have a way to mark isolated processes 728 // as bad, since they don't have a peristent identity. 729 mBadProcesses.put(app.info.processName, app.uid, 730 new BadProcessInfo(now, shortMsg, longMsg, stackTrace)); 731 mProcessCrashTimes.remove(app.info.processName, app.uid); 732 } 733 app.bad = true; 734 app.removed = true; 735 // Don't let services in this process be restarted and potentially 736 // annoy the user repeatedly. Unless it is persistent, since those 737 // processes run critical code. 738 mService.removeProcessLocked(app, false, tryAgain, "crash"); 739 mService.mStackSupervisor.resumeFocusedStackTopActivityLocked(); 740 if (!showBackground) { 741 return false; 742 } 743 } 744 mService.mStackSupervisor.resumeFocusedStackTopActivityLocked(); 745 } else { 746 final TaskRecord affectedTask = 747 mService.mStackSupervisor.finishTopCrashedActivitiesLocked(app, reason); 748 if (data != null) { 749 data.task = affectedTask; 750 } 751 if (data != null && crashTimePersistent != null 752 && now < crashTimePersistent + ProcessList.MIN_CRASH_INTERVAL) { 753 data.repeating = true; 754 } 755 } 756 757 if (data != null && tryAgain) { 758 data.isRestartableForService = true; 759 } 760 761 // If the crashing process is what we consider to be the "home process" and it has been 762 // replaced by a third-party app, clear the package preferred activities from packages 763 // with a home activity running in the process to prevent a repeatedly crashing app 764 // from blocking the user to manually clear the list. 765 final ArrayList<ActivityRecord> activities = app.activities; 766 if (app == mService.mHomeProcess && activities.size() > 0 767 && (mService.mHomeProcess.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { 768 for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { 769 final ActivityRecord r = activities.get(activityNdx); 770 if (r.isActivityTypeHome()) { 771 Log.i(TAG, "Clearing package preferred activities from " + r.packageName); 772 try { 773 ActivityThread.getPackageManager() 774 .clearPackagePreferredActivities(r.packageName); 775 } catch (RemoteException c) { 776 // pm is in same process, this will never happen. 777 } 778 } 779 } 780 } 781 782 if (!app.isolated) { 783 // XXX Can't keep track of crash times for isolated processes, 784 // because they don't have a persistent identity. 785 mProcessCrashTimes.put(app.info.processName, app.uid, now); 786 mProcessCrashTimesPersistent.put(app.info.processName, app.uid, now); 787 } 788 789 if (app.crashHandler != null) mService.mHandler.post(app.crashHandler); 790 return true; 791 } 792 handleShowAppErrorUi(Message msg)793 void handleShowAppErrorUi(Message msg) { 794 AppErrorDialog.Data data = (AppErrorDialog.Data) msg.obj; 795 boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(), 796 Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0; 797 798 AppErrorDialog dialogToShow = null; 799 final String packageName; 800 final int userId; 801 synchronized (mService) { 802 final ProcessRecord proc = data.proc; 803 final AppErrorResult res = data.result; 804 if (proc == null) { 805 Slog.e(TAG, "handleShowAppErrorUi: proc is null"); 806 return; 807 } 808 packageName = proc.info.packageName; 809 userId = proc.userId; 810 if (proc.crashDialog != null) { 811 Slog.e(TAG, "App already has crash dialog: " + proc); 812 if (res != null) { 813 res.set(AppErrorDialog.ALREADY_SHOWING); 814 } 815 return; 816 } 817 boolean isBackground = (UserHandle.getAppId(proc.uid) 818 >= Process.FIRST_APPLICATION_UID 819 && proc.pid != MY_PID); 820 for (int profileId : mService.mUserController.getCurrentProfileIds()) { 821 isBackground &= (userId != profileId); 822 } 823 if (isBackground && !showBackground) { 824 Slog.w(TAG, "Skipping crash dialog of " + proc + ": background"); 825 if (res != null) { 826 res.set(AppErrorDialog.BACKGROUND_USER); 827 } 828 return; 829 } 830 final boolean showFirstCrash = Settings.Global.getInt( 831 mContext.getContentResolver(), 832 Settings.Global.SHOW_FIRST_CRASH_DIALOG, 0) != 0; 833 final boolean showFirstCrashDevOption = Settings.Secure.getIntForUser( 834 mContext.getContentResolver(), 835 Settings.Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION, 836 0, 837 mService.mUserController.getCurrentUserId()) != 0; 838 final boolean crashSilenced = mAppsNotReportingCrashes != null && 839 mAppsNotReportingCrashes.contains(proc.info.packageName); 840 if ((mService.canShowErrorDialogs() || showBackground) && !crashSilenced 841 && (showFirstCrash || showFirstCrashDevOption || data.repeating)) { 842 proc.crashDialog = dialogToShow = new AppErrorDialog(mContext, mService, data); 843 } else { 844 // The device is asleep, so just pretend that the user 845 // saw a crash dialog and hit "force quit". 846 if (res != null) { 847 res.set(AppErrorDialog.CANT_SHOW); 848 } 849 } 850 } 851 // If we've created a crash dialog, show it without the lock held 852 if (dialogToShow != null) { 853 Slog.i(TAG, "Showing crash dialog for package " + packageName + " u" + userId); 854 dialogToShow.show(); 855 } 856 } 857 stopReportingCrashesLocked(ProcessRecord proc)858 void stopReportingCrashesLocked(ProcessRecord proc) { 859 if (mAppsNotReportingCrashes == null) { 860 mAppsNotReportingCrashes = new ArraySet<>(); 861 } 862 mAppsNotReportingCrashes.add(proc.info.packageName); 863 } 864 isInterestingForBackgroundTraces(ProcessRecord app)865 static boolean isInterestingForBackgroundTraces(ProcessRecord app) { 866 // The system_server is always considered interesting. 867 if (app.pid == MY_PID) { 868 return true; 869 } 870 871 // A package is considered interesting if any of the following is true : 872 // 873 // - It's displaying an activity. 874 // - It's the SystemUI. 875 // - It has an overlay or a top UI visible. 876 // 877 // NOTE: The check whether a given ProcessRecord belongs to the systemui 878 // process is a bit of a kludge, but the same pattern seems repeated at 879 // several places in the system server. 880 return app.isInterestingToUserLocked() || 881 (app.info != null && "com.android.systemui".equals(app.info.packageName)) || 882 (app.hasTopUi || app.hasOverlayUi); 883 } 884 appNotResponding(ProcessRecord app, ActivityRecord activity, ActivityRecord parent, boolean aboveSystem, final String annotation)885 final void appNotResponding(ProcessRecord app, ActivityRecord activity, 886 ActivityRecord parent, boolean aboveSystem, final String annotation) { 887 ArrayList<Integer> firstPids = new ArrayList<Integer>(5); 888 SparseArray<Boolean> lastPids = new SparseArray<Boolean>(20); 889 890 if (mService.mController != null) { 891 try { 892 // 0 == continue, -1 = kill process immediately 893 int res = mService.mController.appEarlyNotResponding( 894 app.processName, app.pid, annotation); 895 if (res < 0 && app.pid != MY_PID) { 896 app.kill("anr", true); 897 } 898 } catch (RemoteException e) { 899 mService.mController = null; 900 Watchdog.getInstance().setActivityController(null); 901 } 902 } 903 904 long anrTime = SystemClock.uptimeMillis(); 905 if (ActivityManagerService.MONITOR_CPU_USAGE) { 906 mService.updateCpuStatsNow(); 907 } 908 909 // Unless configured otherwise, swallow ANRs in background processes & kill the process. 910 boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(), 911 Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0; 912 913 boolean isSilentANR; 914 915 synchronized (mService) { 916 // PowerManager.reboot() can block for a long time, so ignore ANRs while shutting down. 917 if (mService.mShuttingDown) { 918 Slog.i(TAG, "During shutdown skipping ANR: " + app + " " + annotation); 919 return; 920 } else if (app.notResponding) { 921 Slog.i(TAG, "Skipping duplicate ANR: " + app + " " + annotation); 922 return; 923 } else if (app.crashing) { 924 Slog.i(TAG, "Crashing app skipping ANR: " + app + " " + annotation); 925 return; 926 } else if (app.killedByAm) { 927 Slog.i(TAG, "App already killed by AM skipping ANR: " + app + " " + annotation); 928 return; 929 } else if (app.killed) { 930 Slog.i(TAG, "Skipping died app ANR: " + app + " " + annotation); 931 return; 932 } 933 934 // In case we come through here for the same app before completing 935 // this one, mark as anring now so we will bail out. 936 app.notResponding = true; 937 938 // Log the ANR to the event log. 939 EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid, 940 app.processName, app.info.flags, annotation); 941 942 // Dump thread traces as quickly as we can, starting with "interesting" processes. 943 firstPids.add(app.pid); 944 945 // Don't dump other PIDs if it's a background ANR 946 isSilentANR = !showBackground && !isInterestingForBackgroundTraces(app); 947 if (!isSilentANR) { 948 int parentPid = app.pid; 949 if (parent != null && parent.app != null && parent.app.pid > 0) { 950 parentPid = parent.app.pid; 951 } 952 if (parentPid != app.pid) firstPids.add(parentPid); 953 954 if (MY_PID != app.pid && MY_PID != parentPid) firstPids.add(MY_PID); 955 956 for (int i = mService.mLruProcesses.size() - 1; i >= 0; i--) { 957 ProcessRecord r = mService.mLruProcesses.get(i); 958 if (r != null && r.thread != null) { 959 int pid = r.pid; 960 if (pid > 0 && pid != app.pid && pid != parentPid && pid != MY_PID) { 961 if (r.persistent) { 962 firstPids.add(pid); 963 if (DEBUG_ANR) Slog.i(TAG, "Adding persistent proc: " + r); 964 } else if (r.treatLikeActivity) { 965 firstPids.add(pid); 966 if (DEBUG_ANR) Slog.i(TAG, "Adding likely IME: " + r); 967 } else { 968 lastPids.put(pid, Boolean.TRUE); 969 if (DEBUG_ANR) Slog.i(TAG, "Adding ANR proc: " + r); 970 } 971 } 972 } 973 } 974 } 975 } 976 977 // Log the ANR to the main log. 978 StringBuilder info = new StringBuilder(); 979 info.setLength(0); 980 info.append("ANR in ").append(app.processName); 981 if (activity != null && activity.shortComponentName != null) { 982 info.append(" (").append(activity.shortComponentName).append(")"); 983 } 984 info.append("\n"); 985 info.append("PID: ").append(app.pid).append("\n"); 986 if (annotation != null) { 987 info.append("Reason: ").append(annotation).append("\n"); 988 } 989 if (parent != null && parent != activity) { 990 info.append("Parent: ").append(parent.shortComponentName).append("\n"); 991 } 992 993 ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true); 994 995 // don't dump native PIDs for background ANRs unless it is the process of interest 996 String[] nativeProcs = null; 997 if (isSilentANR) { 998 for (int i = 0; i < NATIVE_STACKS_OF_INTEREST.length; i++) { 999 if (NATIVE_STACKS_OF_INTEREST[i].equals(app.processName)) { 1000 nativeProcs = new String[] { app.processName }; 1001 break; 1002 } 1003 } 1004 } else { 1005 nativeProcs = NATIVE_STACKS_OF_INTEREST; 1006 } 1007 1008 int[] pids = nativeProcs == null ? null : Process.getPidsForCommands(nativeProcs); 1009 ArrayList<Integer> nativePids = null; 1010 1011 if (pids != null) { 1012 nativePids = new ArrayList<Integer>(pids.length); 1013 for (int i : pids) { 1014 nativePids.add(i); 1015 } 1016 } 1017 1018 // For background ANRs, don't pass the ProcessCpuTracker to 1019 // avoid spending 1/2 second collecting stats to rank lastPids. 1020 File tracesFile = ActivityManagerService.dumpStackTraces( 1021 true, firstPids, 1022 (isSilentANR) ? null : processCpuTracker, 1023 (isSilentANR) ? null : lastPids, 1024 nativePids); 1025 1026 String cpuInfo = null; 1027 if (ActivityManagerService.MONITOR_CPU_USAGE) { 1028 mService.updateCpuStatsNow(); 1029 synchronized (mService.mProcessCpuTracker) { 1030 cpuInfo = mService.mProcessCpuTracker.printCurrentState(anrTime); 1031 } 1032 info.append(processCpuTracker.printCurrentLoad()); 1033 info.append(cpuInfo); 1034 } 1035 1036 info.append(processCpuTracker.printCurrentState(anrTime)); 1037 1038 Slog.e(TAG, info.toString()); 1039 if (tracesFile == null) { 1040 // There is no trace file, so dump (only) the alleged culprit's threads to the log 1041 Process.sendSignal(app.pid, Process.SIGNAL_QUIT); 1042 } 1043 1044 StatsLog.write(StatsLog.ANR_OCCURRED, app.uid, app.processName, 1045 activity == null ? "unknown": activity.shortComponentName, annotation, 1046 (app.info != null) ? (app.info.isInstantApp() 1047 ? StatsLog.ANROCCURRED__IS_INSTANT_APP__TRUE 1048 : StatsLog.ANROCCURRED__IS_INSTANT_APP__FALSE) 1049 : StatsLog.ANROCCURRED__IS_INSTANT_APP__UNAVAILABLE, 1050 app != null ? (app.isInterestingToUserLocked() 1051 ? StatsLog.ANROCCURRED__FOREGROUND_STATE__FOREGROUND 1052 : StatsLog.ANROCCURRED__FOREGROUND_STATE__BACKGROUND) 1053 : StatsLog.ANROCCURRED__FOREGROUND_STATE__UNKNOWN); 1054 mService.addErrorToDropBox("anr", app, app.processName, activity, parent, annotation, 1055 cpuInfo, tracesFile, null); 1056 1057 if (mService.mController != null) { 1058 try { 1059 // 0 == show dialog, 1 = keep waiting, -1 = kill process immediately 1060 int res = mService.mController.appNotResponding( 1061 app.processName, app.pid, info.toString()); 1062 if (res != 0) { 1063 if (res < 0 && app.pid != MY_PID) { 1064 app.kill("anr", true); 1065 } else { 1066 synchronized (mService) { 1067 mService.mServices.scheduleServiceTimeoutLocked(app); 1068 } 1069 } 1070 return; 1071 } 1072 } catch (RemoteException e) { 1073 mService.mController = null; 1074 Watchdog.getInstance().setActivityController(null); 1075 } 1076 } 1077 1078 synchronized (mService) { 1079 mService.mBatteryStatsService.noteProcessAnr(app.processName, app.uid); 1080 1081 if (isSilentANR) { 1082 app.kill("bg anr", true); 1083 return; 1084 } 1085 1086 // Set the app's notResponding state, and look up the errorReportReceiver 1087 makeAppNotRespondingLocked(app, 1088 activity != null ? activity.shortComponentName : null, 1089 annotation != null ? "ANR " + annotation : "ANR", 1090 info.toString()); 1091 1092 // Bring up the infamous App Not Responding dialog 1093 Message msg = Message.obtain(); 1094 msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG; 1095 msg.obj = new AppNotRespondingDialog.Data(app, activity, aboveSystem); 1096 1097 mService.mUiHandler.sendMessage(msg); 1098 } 1099 } 1100 makeAppNotRespondingLocked(ProcessRecord app, String activity, String shortMsg, String longMsg)1101 private void makeAppNotRespondingLocked(ProcessRecord app, 1102 String activity, String shortMsg, String longMsg) { 1103 app.notResponding = true; 1104 app.notRespondingReport = generateProcessError(app, 1105 ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING, 1106 activity, shortMsg, longMsg, null); 1107 startAppProblemLocked(app); 1108 app.stopFreezingAllLocked(); 1109 } 1110 handleShowAnrUi(Message msg)1111 void handleShowAnrUi(Message msg) { 1112 Dialog dialogToShow = null; 1113 synchronized (mService) { 1114 AppNotRespondingDialog.Data data = (AppNotRespondingDialog.Data) msg.obj; 1115 final ProcessRecord proc = data.proc; 1116 if (proc == null) { 1117 Slog.e(TAG, "handleShowAnrUi: proc is null"); 1118 return; 1119 } 1120 if (proc.anrDialog != null) { 1121 Slog.e(TAG, "App already has anr dialog: " + proc); 1122 MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR, 1123 AppNotRespondingDialog.ALREADY_SHOWING); 1124 return; 1125 } 1126 1127 Intent intent = new Intent("android.intent.action.ANR"); 1128 if (!mService.mProcessesReady) { 1129 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY 1130 | Intent.FLAG_RECEIVER_FOREGROUND); 1131 } 1132 mService.broadcastIntentLocked(null, null, intent, 1133 null, null, 0, null, null, null, AppOpsManager.OP_NONE, 1134 null, false, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */); 1135 1136 boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(), 1137 Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0; 1138 if (mService.canShowErrorDialogs() || showBackground) { 1139 dialogToShow = new AppNotRespondingDialog(mService, mContext, data); 1140 proc.anrDialog = dialogToShow; 1141 } else { 1142 MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR, 1143 AppNotRespondingDialog.CANT_SHOW); 1144 // Just kill the app if there is no dialog to be shown. 1145 mService.killAppAtUsersRequest(proc, null); 1146 } 1147 } 1148 // If we've created a crash dialog, show it without the lock held 1149 if (dialogToShow != null) { 1150 dialogToShow.show(); 1151 } 1152 } 1153 1154 /** 1155 * Information about a process that is currently marked as bad. 1156 */ 1157 static final class BadProcessInfo { BadProcessInfo(long time, String shortMsg, String longMsg, String stack)1158 BadProcessInfo(long time, String shortMsg, String longMsg, String stack) { 1159 this.time = time; 1160 this.shortMsg = shortMsg; 1161 this.longMsg = longMsg; 1162 this.stack = stack; 1163 } 1164 1165 final long time; 1166 final String shortMsg; 1167 final String longMsg; 1168 final String stack; 1169 } 1170 1171 } 1172