1 /* 2 * Copyright (C) 2019 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 static android.app.ActivityManager.RunningAppProcessInfo.procStateToImportance; 20 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; 21 import static android.os.Process.THREAD_PRIORITY_BACKGROUND; 22 23 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES; 24 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; 25 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; 26 27 import android.annotation.Nullable; 28 import android.app.ApplicationExitInfo; 29 import android.app.ApplicationExitInfo.Reason; 30 import android.app.ApplicationExitInfo.SubReason; 31 import android.app.IAppTraceRetriever; 32 import android.content.BroadcastReceiver; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.content.IntentFilter; 36 import android.content.pm.PackageManager; 37 import android.icu.text.SimpleDateFormat; 38 import android.os.Binder; 39 import android.os.FileUtils; 40 import android.os.Handler; 41 import android.os.Looper; 42 import android.os.Message; 43 import android.os.ParcelFileDescriptor; 44 import android.os.Process; 45 import android.os.SystemProperties; 46 import android.os.UserHandle; 47 import android.system.OsConstants; 48 import android.text.TextUtils; 49 import android.util.ArrayMap; 50 import android.util.ArraySet; 51 import android.util.AtomicFile; 52 import android.util.Pair; 53 import android.util.Pools.SynchronizedPool; 54 import android.util.Slog; 55 import android.util.SparseArray; 56 import android.util.proto.ProtoInputStream; 57 import android.util.proto.ProtoOutputStream; 58 import android.util.proto.WireTypeMismatchException; 59 60 import com.android.internal.annotations.GuardedBy; 61 import com.android.internal.annotations.VisibleForTesting; 62 import com.android.internal.app.ProcessMap; 63 import com.android.internal.util.ArrayUtils; 64 import com.android.internal.util.function.pooled.PooledLambda; 65 import com.android.server.IoThread; 66 import com.android.server.ServiceThread; 67 import com.android.server.SystemServiceManager; 68 69 import java.io.BufferedInputStream; 70 import java.io.BufferedOutputStream; 71 import java.io.File; 72 import java.io.FileInputStream; 73 import java.io.FileNotFoundException; 74 import java.io.FileOutputStream; 75 import java.io.IOException; 76 import java.io.PrintWriter; 77 import java.util.ArrayList; 78 import java.util.Collections; 79 import java.util.Date; 80 import java.util.List; 81 import java.util.concurrent.TimeUnit; 82 import java.util.function.BiConsumer; 83 import java.util.function.BiFunction; 84 import java.util.function.Consumer; 85 import java.util.function.Predicate; 86 import java.util.function.Supplier; 87 import java.util.zip.GZIPOutputStream; 88 89 /** 90 * A class to manage all the {@link android.app.ApplicationExitInfo} records. 91 */ 92 public final class AppExitInfoTracker { 93 private static final String TAG = TAG_WITH_CLASS_NAME ? "AppExitInfoTracker" : TAG_AM; 94 95 /** 96 * Interval of persisting the app exit info to persistent storage. 97 */ 98 private static final long APP_EXIT_INFO_PERSIST_INTERVAL = TimeUnit.MINUTES.toMillis(30); 99 100 /** These are actions that the forEach* should take after each iteration */ 101 private static final int FOREACH_ACTION_NONE = 0; 102 private static final int FOREACH_ACTION_REMOVE_ITEM = 1; 103 private static final int FOREACH_ACTION_STOP_ITERATION = 2; 104 105 private static final int APP_EXIT_RAW_INFO_POOL_SIZE = 8; 106 107 @VisibleForTesting 108 static final String APP_EXIT_STORE_DIR = "procexitstore"; 109 110 @VisibleForTesting 111 static final String APP_EXIT_INFO_FILE = "procexitinfo"; 112 113 private static final String APP_TRACE_FILE_SUFFIX = ".gz"; 114 115 private final Object mLock = new Object(); 116 117 /** 118 * Initialized in {@link #init} and read-only after that. 119 */ 120 private ActivityManagerService mService; 121 122 /** 123 * Initialized in {@link #init} and read-only after that. 124 */ 125 private KillHandler mKillHandler; 126 127 /** 128 * The task to persist app process exit info 129 */ 130 @GuardedBy("mLock") 131 private Runnable mAppExitInfoPersistTask = null; 132 133 /** 134 * Last time(in ms) since epoch that the app exit info was persisted into persistent storage. 135 */ 136 @GuardedBy("mLock") 137 private long mLastAppExitInfoPersistTimestamp = 0L; 138 139 /** 140 * Retention policy: keep up to X historical exit info per package. 141 * 142 * Initialized in {@link #init} and read-only after that. 143 * Not lock is needed. 144 */ 145 private int mAppExitInfoHistoryListSize; 146 147 /* 148 * PackageName/uid -> [pid/info, ...] holder, the uid here is the package uid. 149 */ 150 @GuardedBy("mLock") 151 private final ProcessMap<AppExitInfoContainer> mData; 152 153 /** A pool of raw {@link android.app.ApplicationExitInfo} records. */ 154 @GuardedBy("mLock") 155 private final SynchronizedPool<ApplicationExitInfo> mRawRecordsPool; 156 157 /** 158 * Wheather or not we've loaded the historical app process exit info from 159 * persistent storage. 160 */ 161 @VisibleForTesting 162 @GuardedBy("mLock") 163 boolean mAppExitInfoLoaded = false; 164 165 /** 166 * Temporary list being used to filter/sort intermediate results in {@link #getExitInfo}. 167 */ 168 @GuardedBy("mLock") 169 final ArrayList<ApplicationExitInfo> mTmpInfoList = new ArrayList<ApplicationExitInfo>(); 170 171 /** 172 * Temporary list being used to filter/sort intermediate results in {@link #getExitInfo}. 173 */ 174 @GuardedBy("mLock") 175 final ArrayList<ApplicationExitInfo> mTmpInfoList2 = new ArrayList<ApplicationExitInfo>(); 176 177 /** 178 * The path to the directory which includes the historical proc exit info file 179 * as specified in {@link #mProcExitInfoFile}, as well as the associated trace files. 180 */ 181 @VisibleForTesting 182 File mProcExitStoreDir; 183 184 /** 185 * The path to the historical proc exit info file, persisted in the storage. 186 */ 187 @VisibleForTesting 188 File mProcExitInfoFile; 189 190 /** 191 * Mapping between the isolated UID to its application uid. 192 */ 193 final IsolatedUidRecords mIsolatedUidRecords = 194 new IsolatedUidRecords(); 195 196 /** 197 * Bookkeeping app process exit info from Zygote. 198 */ 199 final AppExitInfoExternalSource mAppExitInfoSourceZygote = 200 new AppExitInfoExternalSource("zygote", null); 201 202 /** 203 * Bookkeeping low memory kills info from lmkd. 204 */ 205 final AppExitInfoExternalSource mAppExitInfoSourceLmkd = 206 new AppExitInfoExternalSource("lmkd", ApplicationExitInfo.REASON_LOW_MEMORY); 207 208 /** 209 * The active per-UID/PID state data set by 210 * {@link android.app.ActivityManager#setProcessStateSummary}; 211 * these state data are to be "claimed" when its process dies, by then the data will be moved 212 * from this list to the new instance of ApplicationExitInfo. 213 * 214 * <p> The mapping here is UID -> PID -> state </p> 215 * 216 * @see android.app.ActivityManager#setProcessStateSummary(byte[]) 217 */ 218 @GuardedBy("mLock") 219 final SparseArray<SparseArray<byte[]>> mActiveAppStateSummary = new SparseArray<>(); 220 221 /** 222 * The active per-UID/PID trace file when an ANR occurs but the process hasn't been killed yet, 223 * each record is a path to the actual trace file; these files are to be "claimed" 224 * when its process dies, by then the "ownership" of the files will be transferred 225 * from this list to the new instance of ApplicationExitInfo. 226 * 227 * <p> The mapping here is UID -> PID -> file </p> 228 */ 229 @GuardedBy("mLock") 230 final SparseArray<SparseArray<File>> mActiveAppTraces = new SparseArray<>(); 231 232 /** 233 * The implementation of the interface IAppTraceRetriever. 234 */ 235 final AppTraceRetriever mAppTraceRetriever = new AppTraceRetriever(); 236 AppExitInfoTracker()237 AppExitInfoTracker() { 238 mData = new ProcessMap<AppExitInfoContainer>(); 239 mRawRecordsPool = new SynchronizedPool<ApplicationExitInfo>(APP_EXIT_RAW_INFO_POOL_SIZE); 240 } 241 init(ActivityManagerService service)242 void init(ActivityManagerService service) { 243 mService = service; 244 ServiceThread thread = new ServiceThread(TAG + ":killHandler", 245 THREAD_PRIORITY_BACKGROUND, true /* allowIo */); 246 thread.start(); 247 mKillHandler = new KillHandler(thread.getLooper()); 248 249 mProcExitStoreDir = new File(SystemServiceManager.ensureSystemDir(), APP_EXIT_STORE_DIR); 250 if (!FileUtils.createDir(mProcExitStoreDir)) { 251 Slog.e(TAG, "Unable to create " + mProcExitStoreDir); 252 return; 253 } 254 mProcExitInfoFile = new File(mProcExitStoreDir, APP_EXIT_INFO_FILE); 255 256 mAppExitInfoHistoryListSize = service.mContext.getResources().getInteger( 257 com.android.internal.R.integer.config_app_exit_info_history_list_size); 258 } 259 onSystemReady()260 void onSystemReady() { 261 registerForUserRemoval(); 262 registerForPackageRemoval(); 263 IoThread.getHandler().post(() -> { 264 // Read the sysprop set by lmkd and set this to persist so app could read it. 265 SystemProperties.set("persist.sys.lmk.reportkills", 266 Boolean.toString(SystemProperties.getBoolean("sys.lmk.reportkills", false))); 267 loadExistingProcessExitInfo(); 268 }); 269 } 270 scheduleNoteProcessDied(final ProcessRecord app)271 void scheduleNoteProcessDied(final ProcessRecord app) { 272 if (app == null || app.info == null) { 273 return; 274 } 275 276 synchronized (mLock) { 277 if (!mAppExitInfoLoaded) { 278 return; 279 } 280 mKillHandler.obtainMessage(KillHandler.MSG_PROC_DIED, obtainRawRecordLocked(app)) 281 .sendToTarget(); 282 } 283 } 284 scheduleNoteAppKill(final ProcessRecord app, final @Reason int reason, final @SubReason int subReason, final String msg)285 void scheduleNoteAppKill(final ProcessRecord app, final @Reason int reason, 286 final @SubReason int subReason, final String msg) { 287 synchronized (mLock) { 288 if (!mAppExitInfoLoaded) { 289 return; 290 } 291 if (app == null || app.info == null) { 292 return; 293 } 294 295 ApplicationExitInfo raw = obtainRawRecordLocked(app); 296 raw.setReason(reason); 297 raw.setSubReason(subReason); 298 raw.setDescription(msg); 299 mKillHandler.obtainMessage(KillHandler.MSG_APP_KILL, raw).sendToTarget(); 300 } 301 } 302 scheduleNoteAppKill(final int pid, final int uid, final @Reason int reason, final @SubReason int subReason, final String msg)303 void scheduleNoteAppKill(final int pid, final int uid, final @Reason int reason, 304 final @SubReason int subReason, final String msg) { 305 synchronized (mLock) { 306 if (!mAppExitInfoLoaded) { 307 return; 308 } 309 ProcessRecord app; 310 synchronized (mService.mPidsSelfLocked) { 311 app = mService.mPidsSelfLocked.get(pid); 312 } 313 if (app == null) { 314 if (DEBUG_PROCESSES) { 315 Slog.w(TAG, "Skipping saving the kill reason for pid " + pid 316 + "(uid=" + uid + ") since its process record is not found"); 317 } 318 } else { 319 scheduleNoteAppKill(app, reason, subReason, msg); 320 } 321 } 322 } 323 324 interface LmkdKillListener { 325 /** 326 * Called when there is a process kill by lmkd. 327 */ onLmkdKillOccurred(int pid, int uid)328 void onLmkdKillOccurred(int pid, int uid); 329 } 330 setLmkdKillListener(final LmkdKillListener listener)331 void setLmkdKillListener(final LmkdKillListener listener) { 332 synchronized (mLock) { 333 mAppExitInfoSourceLmkd.setOnProcDiedListener((pid, uid) -> 334 listener.onLmkdKillOccurred(pid, uid)); 335 } 336 } 337 338 /** Called when there is a low memory kill */ scheduleNoteLmkdProcKilled(final int pid, final int uid)339 void scheduleNoteLmkdProcKilled(final int pid, final int uid) { 340 mKillHandler.obtainMessage(KillHandler.MSG_LMKD_PROC_KILLED, pid, uid) 341 .sendToTarget(); 342 } 343 scheduleChildProcDied(int pid, int uid, int status)344 private void scheduleChildProcDied(int pid, int uid, int status) { 345 mKillHandler.obtainMessage(KillHandler.MSG_CHILD_PROC_DIED, pid, uid, (Integer) status) 346 .sendToTarget(); 347 } 348 349 /** Calls when zygote sends us SIGCHLD */ handleZygoteSigChld(int pid, int uid, int status)350 void handleZygoteSigChld(int pid, int uid, int status) { 351 if (DEBUG_PROCESSES) { 352 Slog.i(TAG, "Got SIGCHLD from zygote: pid=" + pid + ", uid=" + uid 353 + ", status=" + Integer.toHexString(status)); 354 } 355 scheduleChildProcDied(pid, uid, status); 356 } 357 358 /** 359 * Main routine to create or update the {@link android.app.ApplicationExitInfo} for the given 360 * ProcessRecord, also query the zygote and lmkd records to make the information more accurate. 361 */ 362 @VisibleForTesting 363 @GuardedBy("mLock") handleNoteProcessDiedLocked(final ApplicationExitInfo raw)364 void handleNoteProcessDiedLocked(final ApplicationExitInfo raw) { 365 if (raw != null) { 366 if (DEBUG_PROCESSES) { 367 Slog.i(TAG, "Update process exit info for " + raw.getPackageName() 368 + "(" + raw.getPid() + "/u" + raw.getRealUid() + ")"); 369 } 370 371 ApplicationExitInfo info = getExitInfoLocked(raw.getPackageName(), 372 raw.getPackageUid(), raw.getPid()); 373 374 // query zygote and lmkd to get the exit info, and clear the saved info 375 Pair<Long, Object> zygote = mAppExitInfoSourceZygote.remove( 376 raw.getPid(), raw.getRealUid()); 377 Pair<Long, Object> lmkd = mAppExitInfoSourceLmkd.remove( 378 raw.getPid(), raw.getRealUid()); 379 mIsolatedUidRecords.removeIsolatedUidLocked(raw.getRealUid()); 380 381 if (info == null) { 382 info = addExitInfoLocked(raw); 383 } 384 385 if (lmkd != null) { 386 updateExistingExitInfoRecordLocked(info, null, 387 ApplicationExitInfo.REASON_LOW_MEMORY); 388 } else if (zygote != null) { 389 updateExistingExitInfoRecordLocked(info, (Integer) zygote.second, null); 390 } 391 } 392 } 393 394 /** 395 * Make note when ActivityManagerService decides to kill an application process. 396 */ 397 @VisibleForTesting 398 @GuardedBy("mLock") handleNoteAppKillLocked(final ApplicationExitInfo raw)399 void handleNoteAppKillLocked(final ApplicationExitInfo raw) { 400 ApplicationExitInfo info = getExitInfoLocked( 401 raw.getPackageName(), raw.getPackageUid(), raw.getPid()); 402 403 if (info == null) { 404 addExitInfoLocked(raw); 405 } else { 406 // always override the existing info since we are now more informational. 407 info.setReason(raw.getReason()); 408 info.setSubReason(raw.getSubReason()); 409 info.setStatus(0); 410 info.setTimestamp(System.currentTimeMillis()); 411 info.setDescription(raw.getDescription()); 412 } 413 } 414 415 @GuardedBy("mLock") addExitInfoLocked(ApplicationExitInfo raw)416 private ApplicationExitInfo addExitInfoLocked(ApplicationExitInfo raw) { 417 if (!mAppExitInfoLoaded) { 418 Slog.w(TAG, "Skipping saving the exit info due to ongoing loading from storage"); 419 return null; 420 } 421 422 final ApplicationExitInfo info = new ApplicationExitInfo(raw); 423 final String[] packages = raw.getPackageList(); 424 final int uid = raw.getPackageUid(); 425 for (int i = 0; i < packages.length; i++) { 426 addExitInfoInnerLocked(packages[i], uid, info); 427 } 428 429 schedulePersistProcessExitInfo(false); 430 431 return info; 432 } 433 434 /** 435 * Update an existing {@link android.app.ApplicationExitInfo} record with given information. 436 */ 437 @GuardedBy("mLock") updateExistingExitInfoRecordLocked(ApplicationExitInfo info, Integer status, Integer reason)438 private void updateExistingExitInfoRecordLocked(ApplicationExitInfo info, 439 Integer status, Integer reason) { 440 if (info == null || !isFresh(info.getTimestamp())) { 441 // if the record is way outdated, don't update it then (because of potential pid reuse) 442 return; 443 } 444 if (status != null) { 445 if (OsConstants.WIFEXITED(status)) { 446 info.setReason(ApplicationExitInfo.REASON_EXIT_SELF); 447 info.setStatus(OsConstants.WEXITSTATUS(status)); 448 } else if (OsConstants.WIFSIGNALED(status)) { 449 if (info.getReason() == ApplicationExitInfo.REASON_UNKNOWN) { 450 info.setReason(ApplicationExitInfo.REASON_SIGNALED); 451 info.setStatus(OsConstants.WTERMSIG(status)); 452 } else if (info.getReason() == ApplicationExitInfo.REASON_CRASH_NATIVE) { 453 info.setStatus(OsConstants.WTERMSIG(status)); 454 } 455 } 456 } 457 if (reason != null) { 458 info.setReason(reason); 459 } 460 } 461 462 /** 463 * Update an existing {@link android.app.ApplicationExitInfo} record with given information. 464 * 465 * @return true if a recond is updated 466 */ 467 @GuardedBy("mLock") updateExitInfoIfNecessaryLocked( int pid, int uid, Integer status, Integer reason)468 private boolean updateExitInfoIfNecessaryLocked( 469 int pid, int uid, Integer status, Integer reason) { 470 Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid); 471 if (k != null) { 472 uid = k; 473 } 474 ArrayList<ApplicationExitInfo> tlist = mTmpInfoList; 475 tlist.clear(); 476 final int targetUid = uid; 477 forEachPackageLocked((packageName, records) -> { 478 AppExitInfoContainer container = records.get(targetUid); 479 if (container == null) { 480 return FOREACH_ACTION_NONE; 481 } 482 tlist.clear(); 483 container.getExitInfoLocked(pid, 1, tlist); 484 if (tlist.size() == 0) { 485 return FOREACH_ACTION_NONE; 486 } 487 ApplicationExitInfo info = tlist.get(0); 488 if (info.getRealUid() != targetUid) { 489 tlist.clear(); 490 return FOREACH_ACTION_NONE; 491 } 492 // Okay found it, update its reason. 493 updateExistingExitInfoRecordLocked(info, status, reason); 494 495 return FOREACH_ACTION_STOP_ITERATION; 496 }); 497 return tlist.size() > 0; 498 } 499 500 /** 501 * Get the exit info with matching package name, filterUid and filterPid (if > 0) 502 */ 503 @VisibleForTesting getExitInfo(final String packageName, final int filterUid, final int filterPid, final int maxNum, final ArrayList<ApplicationExitInfo> results)504 void getExitInfo(final String packageName, final int filterUid, 505 final int filterPid, final int maxNum, final ArrayList<ApplicationExitInfo> results) { 506 long identity = Binder.clearCallingIdentity(); 507 try { 508 synchronized (mLock) { 509 boolean emptyPackageName = TextUtils.isEmpty(packageName); 510 if (!emptyPackageName) { 511 // fast path 512 AppExitInfoContainer container = mData.get(packageName, filterUid); 513 if (container != null) { 514 container.getExitInfoLocked(filterPid, maxNum, results); 515 } 516 } else { 517 // slow path 518 final ArrayList<ApplicationExitInfo> list = mTmpInfoList2; 519 list.clear(); 520 // get all packages 521 forEachPackageLocked((name, records) -> { 522 AppExitInfoContainer container = records.get(filterUid); 523 if (container != null) { 524 mTmpInfoList.clear(); 525 list.addAll(container.toListLocked(mTmpInfoList, filterPid)); 526 } 527 return AppExitInfoTracker.FOREACH_ACTION_NONE; 528 }); 529 530 Collections.sort(list, (a, b) -> (int) (b.getTimestamp() - a.getTimestamp())); 531 int size = list.size(); 532 if (maxNum > 0) { 533 size = Math.min(size, maxNum); 534 } 535 for (int i = 0; i < size; i++) { 536 results.add(list.get(i)); 537 } 538 list.clear(); 539 } 540 } 541 } finally { 542 Binder.restoreCallingIdentity(identity); 543 } 544 } 545 546 /** 547 * Return the first matching exit info record, for internal use, the parameters are not supposed 548 * to be empty. 549 */ 550 @GuardedBy("mLock") getExitInfoLocked(final String packageName, final int filterUid, final int filterPid)551 private ApplicationExitInfo getExitInfoLocked(final String packageName, 552 final int filterUid, final int filterPid) { 553 ArrayList<ApplicationExitInfo> list = mTmpInfoList; 554 list.clear(); 555 getExitInfo(packageName, filterUid, filterPid, 1, list); 556 557 ApplicationExitInfo info = list.size() > 0 ? list.get(0) : null; 558 list.clear(); 559 return info; 560 } 561 562 @VisibleForTesting onUserRemoved(int userId)563 void onUserRemoved(int userId) { 564 mAppExitInfoSourceZygote.removeByUserId(userId); 565 mAppExitInfoSourceLmkd.removeByUserId(userId); 566 mIsolatedUidRecords.removeByUserId(userId); 567 synchronized (mLock) { 568 removeByUserIdLocked(userId); 569 schedulePersistProcessExitInfo(true); 570 } 571 } 572 573 @VisibleForTesting onPackageRemoved(String packageName, int uid, boolean allUsers)574 void onPackageRemoved(String packageName, int uid, boolean allUsers) { 575 if (packageName != null) { 576 final boolean removeUid = TextUtils.isEmpty( 577 mService.mPackageManagerInt.getNameForUid(uid)); 578 synchronized (mLock) { 579 if (removeUid) { 580 mAppExitInfoSourceZygote.removeByUidLocked(uid, allUsers); 581 mAppExitInfoSourceLmkd.removeByUidLocked(uid, allUsers); 582 mIsolatedUidRecords.removeAppUid(uid, allUsers); 583 } 584 removePackageLocked(packageName, uid, removeUid, 585 allUsers ? UserHandle.USER_ALL : UserHandle.getUserId(uid)); 586 schedulePersistProcessExitInfo(true); 587 } 588 } 589 } 590 registerForUserRemoval()591 private void registerForUserRemoval() { 592 IntentFilter filter = new IntentFilter(); 593 filter.addAction(Intent.ACTION_USER_REMOVED); 594 mService.mContext.registerReceiverForAllUsers(new BroadcastReceiver() { 595 @Override 596 public void onReceive(Context context, Intent intent) { 597 int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); 598 if (userId < 1) return; 599 onUserRemoved(userId); 600 } 601 }, filter, null, mKillHandler); 602 } 603 registerForPackageRemoval()604 private void registerForPackageRemoval() { 605 IntentFilter filter = new IntentFilter(); 606 filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 607 filter.addDataScheme("package"); 608 mService.mContext.registerReceiverForAllUsers(new BroadcastReceiver() { 609 @Override 610 public void onReceive(Context context, Intent intent) { 611 boolean replacing = intent.getBooleanExtra( 612 Intent.EXTRA_REPLACING, false); 613 if (replacing) { 614 return; 615 } 616 int uid = intent.getIntExtra(Intent.EXTRA_UID, UserHandle.USER_NULL); 617 boolean allUsers = intent.getBooleanExtra( 618 Intent.EXTRA_REMOVED_FOR_ALL_USERS, false); 619 onPackageRemoved(intent.getData().getSchemeSpecificPart(), uid, allUsers); 620 } 621 }, filter, null, mKillHandler); 622 } 623 624 /** 625 * Load the existing {@link android.app.ApplicationExitInfo} records from persistent storage. 626 */ 627 @VisibleForTesting loadExistingProcessExitInfo()628 void loadExistingProcessExitInfo() { 629 if (!mProcExitInfoFile.canRead()) { 630 synchronized (mLock) { 631 mAppExitInfoLoaded = true; 632 } 633 return; 634 } 635 636 FileInputStream fin = null; 637 try { 638 AtomicFile af = new AtomicFile(mProcExitInfoFile); 639 fin = af.openRead(); 640 ProtoInputStream proto = new ProtoInputStream(fin); 641 for (int next = proto.nextField(); 642 next != ProtoInputStream.NO_MORE_FIELDS; 643 next = proto.nextField()) { 644 switch (next) { 645 case (int) AppsExitInfoProto.LAST_UPDATE_TIMESTAMP: 646 synchronized (mLock) { 647 mLastAppExitInfoPersistTimestamp = 648 proto.readLong(AppsExitInfoProto.LAST_UPDATE_TIMESTAMP); 649 } 650 break; 651 case (int) AppsExitInfoProto.PACKAGES: 652 loadPackagesFromProto(proto, next); 653 break; 654 } 655 } 656 } catch (IOException | IllegalArgumentException | WireTypeMismatchException e) { 657 Slog.w(TAG, "Error in loading historical app exit info from persistent storage: " + e); 658 } finally { 659 if (fin != null) { 660 try { 661 fin.close(); 662 } catch (IOException e) { 663 } 664 } 665 } 666 synchronized (mLock) { 667 pruneAnrTracesIfNecessaryLocked(); 668 mAppExitInfoLoaded = true; 669 } 670 } 671 loadPackagesFromProto(ProtoInputStream proto, long fieldId)672 private void loadPackagesFromProto(ProtoInputStream proto, long fieldId) 673 throws IOException, WireTypeMismatchException { 674 long token = proto.start(fieldId); 675 String pkgName = ""; 676 for (int next = proto.nextField(); 677 next != ProtoInputStream.NO_MORE_FIELDS; 678 next = proto.nextField()) { 679 switch (next) { 680 case (int) AppsExitInfoProto.Package.PACKAGE_NAME: 681 pkgName = proto.readString(AppsExitInfoProto.Package.PACKAGE_NAME); 682 break; 683 case (int) AppsExitInfoProto.Package.USERS: 684 AppExitInfoContainer container = new AppExitInfoContainer( 685 mAppExitInfoHistoryListSize); 686 int uid = container.readFromProto(proto, AppsExitInfoProto.Package.USERS); 687 synchronized (mLock) { 688 mData.put(pkgName, uid, container); 689 } 690 break; 691 } 692 } 693 proto.end(token); 694 } 695 696 /** 697 * Persist the existing {@link android.app.ApplicationExitInfo} records to storage. 698 */ 699 @VisibleForTesting persistProcessExitInfo()700 void persistProcessExitInfo() { 701 AtomicFile af = new AtomicFile(mProcExitInfoFile); 702 FileOutputStream out = null; 703 long now = System.currentTimeMillis(); 704 try { 705 out = af.startWrite(); 706 ProtoOutputStream proto = new ProtoOutputStream(out); 707 proto.write(AppsExitInfoProto.LAST_UPDATE_TIMESTAMP, now); 708 synchronized (mLock) { 709 forEachPackageLocked((packageName, records) -> { 710 long token = proto.start(AppsExitInfoProto.PACKAGES); 711 proto.write(AppsExitInfoProto.Package.PACKAGE_NAME, packageName); 712 int uidArraySize = records.size(); 713 for (int j = 0; j < uidArraySize; j++) { 714 records.valueAt(j).writeToProto(proto, AppsExitInfoProto.Package.USERS); 715 } 716 proto.end(token); 717 return AppExitInfoTracker.FOREACH_ACTION_NONE; 718 }); 719 mLastAppExitInfoPersistTimestamp = now; 720 } 721 proto.flush(); 722 af.finishWrite(out); 723 } catch (IOException e) { 724 Slog.w(TAG, "Unable to write historical app exit info into persistent storage: " + e); 725 af.failWrite(out); 726 } 727 synchronized (mLock) { 728 mAppExitInfoPersistTask = null; 729 } 730 } 731 732 /** 733 * Schedule a task to persist the {@link android.app.ApplicationExitInfo} records to storage. 734 */ 735 @VisibleForTesting schedulePersistProcessExitInfo(boolean immediately)736 void schedulePersistProcessExitInfo(boolean immediately) { 737 synchronized (mLock) { 738 if (mAppExitInfoPersistTask == null || immediately) { 739 if (mAppExitInfoPersistTask != null) { 740 IoThread.getHandler().removeCallbacks(mAppExitInfoPersistTask); 741 } 742 mAppExitInfoPersistTask = this::persistProcessExitInfo; 743 IoThread.getHandler().postDelayed(mAppExitInfoPersistTask, 744 immediately ? 0 : APP_EXIT_INFO_PERSIST_INTERVAL); 745 } 746 } 747 } 748 749 /** 750 * Helper function for testing only. 751 */ 752 @VisibleForTesting clearProcessExitInfo(boolean removeFile)753 void clearProcessExitInfo(boolean removeFile) { 754 synchronized (mLock) { 755 if (mAppExitInfoPersistTask != null) { 756 IoThread.getHandler().removeCallbacks(mAppExitInfoPersistTask); 757 mAppExitInfoPersistTask = null; 758 } 759 if (removeFile && mProcExitInfoFile != null) { 760 mProcExitInfoFile.delete(); 761 } 762 mData.getMap().clear(); 763 mActiveAppStateSummary.clear(); 764 mActiveAppTraces.clear(); 765 pruneAnrTracesIfNecessaryLocked(); 766 } 767 } 768 769 /** 770 * Helper function for shell command 771 */ clearHistoryProcessExitInfo(String packageName, int userId)772 void clearHistoryProcessExitInfo(String packageName, int userId) { 773 if (TextUtils.isEmpty(packageName)) { 774 synchronized (mLock) { 775 removeByUserIdLocked(userId); 776 } 777 } else { 778 final int uid = mService.mPackageManagerInt.getPackageUid(packageName, 779 PackageManager.MATCH_ALL, userId); 780 synchronized (mLock) { 781 removePackageLocked(packageName, uid, true, userId); 782 } 783 } 784 schedulePersistProcessExitInfo(true); 785 } 786 dumpHistoryProcessExitInfo(PrintWriter pw, String packageName)787 void dumpHistoryProcessExitInfo(PrintWriter pw, String packageName) { 788 pw.println("ACTIVITY MANAGER PROCESS EXIT INFO (dumpsys activity exit-info)"); 789 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); 790 synchronized (mLock) { 791 pw.println("Last Timestamp of Persistence Into Persistent Storage: " 792 + sdf.format(new Date(mLastAppExitInfoPersistTimestamp))); 793 if (TextUtils.isEmpty(packageName)) { 794 forEachPackageLocked((name, records) -> { 795 dumpHistoryProcessExitInfoLocked(pw, " ", name, records, sdf); 796 return AppExitInfoTracker.FOREACH_ACTION_NONE; 797 }); 798 } else { 799 SparseArray<AppExitInfoContainer> array = mData.getMap().get(packageName); 800 if (array != null) { 801 dumpHistoryProcessExitInfoLocked(pw, " ", packageName, array, sdf); 802 } 803 } 804 } 805 } 806 807 @GuardedBy("mLock") dumpHistoryProcessExitInfoLocked(PrintWriter pw, String prefix, String packageName, SparseArray<AppExitInfoContainer> array, SimpleDateFormat sdf)808 private void dumpHistoryProcessExitInfoLocked(PrintWriter pw, String prefix, 809 String packageName, SparseArray<AppExitInfoContainer> array, 810 SimpleDateFormat sdf) { 811 pw.println(prefix + "package: " + packageName); 812 int size = array.size(); 813 for (int i = 0; i < size; i++) { 814 pw.println(prefix + " Historical Process Exit for userId=" + array.keyAt(i)); 815 array.valueAt(i).dumpLocked(pw, prefix + " ", sdf); 816 } 817 } 818 819 @GuardedBy("mLock") addExitInfoInnerLocked(String packageName, int userId, ApplicationExitInfo info)820 private void addExitInfoInnerLocked(String packageName, int userId, ApplicationExitInfo info) { 821 AppExitInfoContainer container = mData.get(packageName, userId); 822 if (container == null) { 823 container = new AppExitInfoContainer(mAppExitInfoHistoryListSize); 824 if (UserHandle.isIsolated(info.getRealUid())) { 825 Integer k = mIsolatedUidRecords.getUidByIsolatedUid(info.getRealUid()); 826 if (k != null) { 827 container.mUid = k; 828 } 829 } else { 830 container.mUid = info.getRealUid(); 831 } 832 mData.put(packageName, userId, container); 833 } 834 container.addExitInfoLocked(info); 835 } 836 837 @GuardedBy("mLocked") forEachPackageLocked( BiFunction<String, SparseArray<AppExitInfoContainer>, Integer> callback)838 private void forEachPackageLocked( 839 BiFunction<String, SparseArray<AppExitInfoContainer>, Integer> callback) { 840 if (callback != null) { 841 ArrayMap<String, SparseArray<AppExitInfoContainer>> map = mData.getMap(); 842 for (int i = map.size() - 1; i >= 0; i--) { 843 switch (callback.apply(map.keyAt(i), map.valueAt(i))) { 844 case FOREACH_ACTION_REMOVE_ITEM: 845 final SparseArray<AppExitInfoContainer> records = map.valueAt(i); 846 for (int j = records.size() - 1; j >= 0; j--) { 847 records.valueAt(j).destroyLocked(); 848 } 849 map.removeAt(i); 850 break; 851 case FOREACH_ACTION_STOP_ITERATION: 852 i = 0; 853 break; 854 case FOREACH_ACTION_NONE: 855 default: 856 break; 857 } 858 } 859 } 860 } 861 862 @GuardedBy("mLocked") removePackageLocked(String packageName, int uid, boolean removeUid, int userId)863 private void removePackageLocked(String packageName, int uid, boolean removeUid, int userId) { 864 if (removeUid) { 865 mActiveAppStateSummary.remove(uid); 866 final int idx = mActiveAppTraces.indexOfKey(uid); 867 if (idx >= 0) { 868 final SparseArray<File> array = mActiveAppTraces.valueAt(idx); 869 for (int i = array.size() - 1; i >= 0; i--) { 870 array.valueAt(i).delete(); 871 } 872 mActiveAppTraces.removeAt(idx); 873 } 874 } 875 ArrayMap<String, SparseArray<AppExitInfoContainer>> map = mData.getMap(); 876 SparseArray<AppExitInfoContainer> array = map.get(packageName); 877 if (array == null) { 878 return; 879 } 880 if (userId == UserHandle.USER_ALL) { 881 for (int i = array.size() - 1; i >= 0; i--) { 882 array.valueAt(i).destroyLocked(); 883 } 884 mData.getMap().remove(packageName); 885 } else { 886 for (int i = array.size() - 1; i >= 0; i--) { 887 if (UserHandle.getUserId(array.keyAt(i)) == userId) { 888 array.valueAt(i).destroyLocked(); 889 array.removeAt(i); 890 break; 891 } 892 } 893 if (array.size() == 0) { 894 map.remove(packageName); 895 } 896 } 897 } 898 899 @GuardedBy("mLocked") removeByUserIdLocked(final int userId)900 private void removeByUserIdLocked(final int userId) { 901 if (userId == UserHandle.USER_ALL) { 902 mData.getMap().clear(); 903 mActiveAppStateSummary.clear(); 904 mActiveAppTraces.clear(); 905 pruneAnrTracesIfNecessaryLocked(); 906 return; 907 } 908 removeFromSparse2dArray(mActiveAppStateSummary, 909 (v) -> UserHandle.getUserId(v) == userId, null, null); 910 removeFromSparse2dArray(mActiveAppTraces, 911 (v) -> UserHandle.getUserId(v) == userId, null, (v) -> v.delete()); 912 forEachPackageLocked((packageName, records) -> { 913 for (int i = records.size() - 1; i >= 0; i--) { 914 if (UserHandle.getUserId(records.keyAt(i)) == userId) { 915 records.valueAt(i).destroyLocked(); 916 records.removeAt(i); 917 break; 918 } 919 } 920 return records.size() == 0 ? FOREACH_ACTION_REMOVE_ITEM : FOREACH_ACTION_NONE; 921 }); 922 } 923 924 @VisibleForTesting 925 @GuardedBy("mLock") obtainRawRecordLocked(ProcessRecord app)926 ApplicationExitInfo obtainRawRecordLocked(ProcessRecord app) { 927 ApplicationExitInfo info = mRawRecordsPool.acquire(); 928 if (info == null) { 929 info = new ApplicationExitInfo(); 930 } 931 932 final int definingUid = app.hostingRecord != null ? app.hostingRecord.getDefiningUid() : 0; 933 info.setPid(app.pid); 934 info.setRealUid(app.uid); 935 info.setPackageUid(app.info.uid); 936 info.setDefiningUid(definingUid > 0 ? definingUid : app.info.uid); 937 info.setProcessName(app.processName); 938 info.setConnectionGroup(app.connectionGroup); 939 info.setPackageName(app.info.packageName); 940 info.setPackageList(app.getPackageList()); 941 info.setReason(ApplicationExitInfo.REASON_UNKNOWN); 942 info.setStatus(0); 943 info.setImportance(procStateToImportance(app.setProcState)); 944 info.setPss(app.lastPss); 945 info.setRss(app.mLastRss); 946 info.setTimestamp(System.currentTimeMillis()); 947 948 return info; 949 } 950 951 @VisibleForTesting 952 @GuardedBy("mLock") recycleRawRecordLocked(ApplicationExitInfo info)953 void recycleRawRecordLocked(ApplicationExitInfo info) { 954 info.setProcessName(null); 955 info.setDescription(null); 956 info.setPackageList(null); 957 958 mRawRecordsPool.release(info); 959 } 960 961 /** 962 * Called from {@link ActivityManagerService#setProcessStateSummary}. 963 */ 964 @VisibleForTesting setProcessStateSummary(int uid, final int pid, final byte[] data)965 void setProcessStateSummary(int uid, final int pid, final byte[] data) { 966 synchronized (mLock) { 967 Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid); 968 if (k != null) { 969 uid = k; 970 } 971 putToSparse2dArray(mActiveAppStateSummary, uid, pid, data, SparseArray::new, null); 972 } 973 } 974 975 @VisibleForTesting getProcessStateSummary(int uid, final int pid)976 @Nullable byte[] getProcessStateSummary(int uid, final int pid) { 977 synchronized (mLock) { 978 Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid); 979 if (k != null) { 980 uid = k; 981 } 982 int index = mActiveAppStateSummary.indexOfKey(uid); 983 if (index < 0) { 984 return null; 985 } 986 return mActiveAppStateSummary.valueAt(index).get(pid); 987 } 988 } 989 990 /** 991 * Called from ProcessRecord when an ANR occurred and the ANR trace is taken. 992 */ scheduleLogAnrTrace(final int pid, final int uid, final String[] packageList, final File traceFile, final long startOff, final long endOff)993 void scheduleLogAnrTrace(final int pid, final int uid, final String[] packageList, 994 final File traceFile, final long startOff, final long endOff) { 995 mKillHandler.sendMessage(PooledLambda.obtainMessage( 996 this::handleLogAnrTrace, pid, uid, packageList, 997 traceFile, startOff, endOff)); 998 } 999 1000 /** 1001 * Copy and compress the given ANR trace file 1002 */ 1003 @VisibleForTesting handleLogAnrTrace(final int pid, int uid, final String[] packageList, final File traceFile, final long startOff, final long endOff)1004 void handleLogAnrTrace(final int pid, int uid, final String[] packageList, 1005 final File traceFile, final long startOff, final long endOff) { 1006 if (!traceFile.exists() || ArrayUtils.isEmpty(packageList)) { 1007 return; 1008 } 1009 final long size = traceFile.length(); 1010 final long length = endOff - startOff; 1011 if (startOff >= size || endOff > size || length <= 0) { 1012 return; 1013 } 1014 1015 final File outFile = new File(mProcExitStoreDir, traceFile.getName() 1016 + APP_TRACE_FILE_SUFFIX); 1017 // Copy & compress 1018 if (copyToGzFile(traceFile, outFile, startOff, length)) { 1019 // Wrote successfully. 1020 synchronized (mLock) { 1021 Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid); 1022 if (k != null) { 1023 uid = k; 1024 } 1025 if (DEBUG_PROCESSES) { 1026 Slog.i(TAG, "Stored ANR traces of " + pid + "/u" + uid + " in " + outFile); 1027 } 1028 boolean pending = true; 1029 // Unlikely but possible: the app has died 1030 for (int i = 0; i < packageList.length; i++) { 1031 final AppExitInfoContainer container = mData.get(packageList[i], uid); 1032 // Try to see if we could append this trace to an existing record 1033 if (container != null && container.appendTraceIfNecessaryLocked(pid, outFile)) { 1034 // Okay someone took it 1035 pending = false; 1036 } 1037 } 1038 if (pending) { 1039 // Save it into a temporary list for later use (when the app dies). 1040 putToSparse2dArray(mActiveAppTraces, uid, pid, outFile, 1041 SparseArray::new, (v) -> v.delete()); 1042 } 1043 } 1044 } 1045 } 1046 1047 /** 1048 * Copy the given portion of the file into a gz file. 1049 * 1050 * @param inFile The source file. 1051 * @param outFile The destination file, which will be compressed in gzip format. 1052 * @param start The start offset where the copy should start from. 1053 * @param length The number of bytes that should be copied. 1054 * @return If the copy was successful or not. 1055 */ copyToGzFile(final File inFile, final File outFile, final long start, final long length)1056 private static boolean copyToGzFile(final File inFile, final File outFile, 1057 final long start, final long length) { 1058 long remaining = length; 1059 try ( 1060 BufferedInputStream in = new BufferedInputStream(new FileInputStream(inFile)); 1061 GZIPOutputStream out = new GZIPOutputStream(new BufferedOutputStream( 1062 new FileOutputStream(outFile)))) { 1063 final byte[] buffer = new byte[8192]; 1064 in.skip(start); 1065 while (remaining > 0) { 1066 int t = in.read(buffer, 0, (int) Math.min(buffer.length, remaining)); 1067 if (t < 0) { 1068 break; 1069 } 1070 out.write(buffer, 0, t); 1071 remaining -= t; 1072 } 1073 } catch (IOException e) { 1074 if (DEBUG_PROCESSES) { 1075 Slog.e(TAG, "Error in copying ANR trace from " + inFile + " to " + outFile, e); 1076 } 1077 return false; 1078 } 1079 return remaining == 0 && outFile.exists(); 1080 } 1081 1082 /** 1083 * In case there is any orphan ANR trace file, remove it. 1084 */ 1085 @GuardedBy("mLock") pruneAnrTracesIfNecessaryLocked()1086 private void pruneAnrTracesIfNecessaryLocked() { 1087 final ArraySet<String> allFiles = new ArraySet(); 1088 final File[] files = mProcExitStoreDir.listFiles((f) -> { 1089 final String name = f.getName(); 1090 boolean trace = name.startsWith(ActivityManagerService.ANR_FILE_PREFIX) 1091 && name.endsWith(APP_TRACE_FILE_SUFFIX); 1092 if (trace) { 1093 allFiles.add(name); 1094 } 1095 return trace; 1096 }); 1097 if (ArrayUtils.isEmpty(files)) { 1098 return; 1099 } 1100 // Find out the owners from the existing records 1101 forEachPackageLocked((name, records) -> { 1102 for (int i = records.size() - 1; i >= 0; i--) { 1103 final AppExitInfoContainer container = records.valueAt(i); 1104 container.forEachRecordLocked((pid, info) -> { 1105 final File traceFile = info.getTraceFile(); 1106 if (traceFile != null) { 1107 allFiles.remove(traceFile.getName()); 1108 } 1109 return FOREACH_ACTION_NONE; 1110 }); 1111 } 1112 return AppExitInfoTracker.FOREACH_ACTION_NONE; 1113 }); 1114 // See if there is any active process owns it. 1115 forEachSparse2dArray(mActiveAppTraces, (v) -> allFiles.remove(v.getName())); 1116 1117 // Remove orphan traces if nobody claims it. 1118 for (int i = allFiles.size() - 1; i >= 0; i--) { 1119 (new File(mProcExitStoreDir, allFiles.valueAt(i))).delete(); 1120 } 1121 } 1122 1123 /** 1124 * A utility function to add the given value to the given 2d SparseArray 1125 */ putToSparse2dArray(final SparseArray<T> array, final int outerKey, final int innerKey, final U value, final Supplier<T> newInstance, final Consumer<U> actionToOldValue)1126 private static <T extends SparseArray<U>, U> void putToSparse2dArray(final SparseArray<T> array, 1127 final int outerKey, final int innerKey, final U value, final Supplier<T> newInstance, 1128 final Consumer<U> actionToOldValue) { 1129 int idx = array.indexOfKey(outerKey); 1130 T innerArray = null; 1131 if (idx < 0) { 1132 innerArray = newInstance.get(); 1133 array.put(outerKey, innerArray); 1134 } else { 1135 innerArray = array.valueAt(idx); 1136 } 1137 idx = innerArray.indexOfKey(innerKey); 1138 if (idx >= 0) { 1139 if (actionToOldValue != null) { 1140 actionToOldValue.accept(innerArray.valueAt(idx)); 1141 } 1142 innerArray.setValueAt(idx, value); 1143 } else { 1144 innerArray.put(innerKey, value); 1145 } 1146 } 1147 1148 /** 1149 * A utility function to iterate through the given 2d SparseArray 1150 */ forEachSparse2dArray( final SparseArray<T> array, final Consumer<U> action)1151 private static <T extends SparseArray<U>, U> void forEachSparse2dArray( 1152 final SparseArray<T> array, final Consumer<U> action) { 1153 if (action != null) { 1154 for (int i = array.size() - 1; i >= 0; i--) { 1155 T innerArray = array.valueAt(i); 1156 if (innerArray == null) { 1157 continue; 1158 } 1159 for (int j = innerArray.size() - 1; j >= 0; j--) { 1160 action.accept(innerArray.valueAt(j)); 1161 } 1162 } 1163 } 1164 } 1165 1166 /** 1167 * A utility function to remove elements from the given 2d SparseArray 1168 */ removeFromSparse2dArray( final SparseArray<T> array, final Predicate<Integer> outerPredicate, final Predicate<Integer> innerPredicate, final Consumer<U> action)1169 private static <T extends SparseArray<U>, U> void removeFromSparse2dArray( 1170 final SparseArray<T> array, final Predicate<Integer> outerPredicate, 1171 final Predicate<Integer> innerPredicate, final Consumer<U> action) { 1172 for (int i = array.size() - 1; i >= 0; i--) { 1173 if (outerPredicate == null || outerPredicate.test(array.keyAt(i))) { 1174 final T innerArray = array.valueAt(i); 1175 if (innerArray == null) { 1176 continue; 1177 } 1178 for (int j = innerArray.size() - 1; j >= 0; j--) { 1179 if (innerPredicate == null || innerPredicate.test(innerArray.keyAt(j))) { 1180 if (action != null) { 1181 action.accept(innerArray.valueAt(j)); 1182 } 1183 innerArray.removeAt(j); 1184 } 1185 } 1186 if (innerArray.size() == 0) { 1187 array.removeAt(i); 1188 } 1189 } 1190 } 1191 } 1192 1193 /** 1194 * A utility function to find and remove elements from the given 2d SparseArray. 1195 */ findAndRemoveFromSparse2dArray( final SparseArray<T> array, final int outerKey, final int innerKey)1196 private static <T extends SparseArray<U>, U> U findAndRemoveFromSparse2dArray( 1197 final SparseArray<T> array, final int outerKey, final int innerKey) { 1198 final int idx = array.indexOfKey(outerKey); 1199 if (idx >= 0) { 1200 T p = array.valueAt(idx); 1201 if (p == null) { 1202 return null; 1203 } 1204 final int innerIdx = p.indexOfKey(innerKey); 1205 if (innerIdx >= 0) { 1206 final U ret = p.valueAt(innerIdx); 1207 p.removeAt(innerIdx); 1208 if (p.size() == 0) { 1209 array.removeAt(idx); 1210 } 1211 return ret; 1212 } 1213 } 1214 return null; 1215 } 1216 1217 /** 1218 * A container class of {@link android.app.ApplicationExitInfo} 1219 */ 1220 final class AppExitInfoContainer { 1221 private SparseArray<ApplicationExitInfo> mInfos; // index is pid 1222 private int mMaxCapacity; 1223 private int mUid; // Application uid, not isolated uid. 1224 AppExitInfoContainer(final int maxCapacity)1225 AppExitInfoContainer(final int maxCapacity) { 1226 mInfos = new SparseArray<ApplicationExitInfo>(); 1227 mMaxCapacity = maxCapacity; 1228 } 1229 1230 @GuardedBy("mLock") getExitInfoLocked(final int filterPid, final int maxNum, ArrayList<ApplicationExitInfo> results)1231 void getExitInfoLocked(final int filterPid, final int maxNum, 1232 ArrayList<ApplicationExitInfo> results) { 1233 if (filterPid > 0) { 1234 ApplicationExitInfo r = mInfos.get(filterPid); 1235 if (r != null) { 1236 results.add(r); 1237 } 1238 } else { 1239 final int numRep = mInfos.size(); 1240 if (maxNum <= 0 || numRep <= maxNum) { 1241 // Return all records. 1242 for (int i = 0; i < numRep; i++) { 1243 results.add(mInfos.valueAt(i)); 1244 } 1245 Collections.sort(results, 1246 (a, b) -> (int) (b.getTimestamp() - a.getTimestamp())); 1247 } else { 1248 if (maxNum == 1) { 1249 // Most of the caller might be only interested with the most recent one 1250 ApplicationExitInfo r = mInfos.valueAt(0); 1251 for (int i = 1; i < numRep; i++) { 1252 ApplicationExitInfo t = mInfos.valueAt(i); 1253 if (r.getTimestamp() < t.getTimestamp()) { 1254 r = t; 1255 } 1256 } 1257 results.add(r); 1258 } else { 1259 // Huh, need to sort it out then. 1260 ArrayList<ApplicationExitInfo> list = mTmpInfoList2; 1261 list.clear(); 1262 for (int i = 0; i < numRep; i++) { 1263 list.add(mInfos.valueAt(i)); 1264 } 1265 Collections.sort(list, 1266 (a, b) -> (int) (b.getTimestamp() - a.getTimestamp())); 1267 for (int i = 0; i < maxNum; i++) { 1268 results.add(list.get(i)); 1269 } 1270 list.clear(); 1271 } 1272 } 1273 } 1274 } 1275 1276 @GuardedBy("mLock") addExitInfoLocked(ApplicationExitInfo info)1277 void addExitInfoLocked(ApplicationExitInfo info) { 1278 int size; 1279 if ((size = mInfos.size()) >= mMaxCapacity) { 1280 int oldestIndex = -1; 1281 long oldestTimeStamp = Long.MAX_VALUE; 1282 for (int i = 0; i < size; i++) { 1283 ApplicationExitInfo r = mInfos.valueAt(i); 1284 if (r.getTimestamp() < oldestTimeStamp) { 1285 oldestTimeStamp = r.getTimestamp(); 1286 oldestIndex = i; 1287 } 1288 } 1289 if (oldestIndex >= 0) { 1290 final File traceFile = mInfos.valueAt(oldestIndex).getTraceFile(); 1291 if (traceFile != null) { 1292 traceFile.delete(); 1293 } 1294 mInfos.removeAt(oldestIndex); 1295 } 1296 } 1297 // Claim the state information if there is any 1298 final int uid = info.getPackageUid(); 1299 final int pid = info.getPid(); 1300 info.setProcessStateSummary(findAndRemoveFromSparse2dArray( 1301 mActiveAppStateSummary, uid, pid)); 1302 info.setTraceFile(findAndRemoveFromSparse2dArray(mActiveAppTraces, uid, pid)); 1303 info.setAppTraceRetriever(mAppTraceRetriever); 1304 mInfos.append(pid, info); 1305 } 1306 1307 @GuardedBy("mLock") appendTraceIfNecessaryLocked(final int pid, final File traceFile)1308 boolean appendTraceIfNecessaryLocked(final int pid, final File traceFile) { 1309 final ApplicationExitInfo r = mInfos.get(pid); 1310 if (r != null) { 1311 r.setTraceFile(traceFile); 1312 r.setAppTraceRetriever(mAppTraceRetriever); 1313 return true; 1314 } 1315 return false; 1316 } 1317 1318 @GuardedBy("mLock") destroyLocked()1319 void destroyLocked() { 1320 for (int i = mInfos.size() - 1; i >= 0; i--) { 1321 ApplicationExitInfo ai = mInfos.valueAt(i); 1322 final File traceFile = ai.getTraceFile(); 1323 if (traceFile != null) { 1324 traceFile.delete(); 1325 } 1326 ai.setTraceFile(null); 1327 ai.setAppTraceRetriever(null); 1328 } 1329 } 1330 1331 @GuardedBy("mLock") forEachRecordLocked(final BiFunction<Integer, ApplicationExitInfo, Integer> callback)1332 void forEachRecordLocked(final BiFunction<Integer, ApplicationExitInfo, Integer> callback) { 1333 if (callback != null) { 1334 for (int i = mInfos.size() - 1; i >= 0; i--) { 1335 switch (callback.apply(mInfos.keyAt(i), mInfos.valueAt(i))) { 1336 case FOREACH_ACTION_REMOVE_ITEM: 1337 final File traceFile = mInfos.valueAt(i).getTraceFile(); 1338 if (traceFile != null) { 1339 traceFile.delete(); 1340 } 1341 mInfos.removeAt(i); 1342 break; 1343 case FOREACH_ACTION_STOP_ITERATION: 1344 i = 0; 1345 break; 1346 case FOREACH_ACTION_NONE: 1347 default: 1348 break; 1349 } 1350 } 1351 } 1352 } 1353 1354 @GuardedBy("mLock") dumpLocked(PrintWriter pw, String prefix, SimpleDateFormat sdf)1355 void dumpLocked(PrintWriter pw, String prefix, SimpleDateFormat sdf) { 1356 ArrayList<ApplicationExitInfo> list = new ArrayList<ApplicationExitInfo>(); 1357 for (int i = mInfos.size() - 1; i >= 0; i--) { 1358 list.add(mInfos.valueAt(i)); 1359 } 1360 Collections.sort(list, (a, b) -> (int) (b.getTimestamp() - a.getTimestamp())); 1361 int size = list.size(); 1362 for (int i = 0; i < size; i++) { 1363 list.get(i).dump(pw, prefix + " ", "#" + i, sdf); 1364 } 1365 } 1366 1367 @GuardedBy("mLock") writeToProto(ProtoOutputStream proto, long fieldId)1368 void writeToProto(ProtoOutputStream proto, long fieldId) { 1369 long token = proto.start(fieldId); 1370 proto.write(AppsExitInfoProto.Package.User.UID, mUid); 1371 int size = mInfos.size(); 1372 for (int i = 0; i < size; i++) { 1373 mInfos.valueAt(i).writeToProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO); 1374 } 1375 proto.end(token); 1376 } 1377 readFromProto(ProtoInputStream proto, long fieldId)1378 int readFromProto(ProtoInputStream proto, long fieldId) 1379 throws IOException, WireTypeMismatchException { 1380 long token = proto.start(fieldId); 1381 for (int next = proto.nextField(); 1382 next != ProtoInputStream.NO_MORE_FIELDS; 1383 next = proto.nextField()) { 1384 switch (next) { 1385 case (int) AppsExitInfoProto.Package.User.UID: 1386 mUid = proto.readInt(AppsExitInfoProto.Package.User.UID); 1387 break; 1388 case (int) AppsExitInfoProto.Package.User.APP_EXIT_INFO: 1389 ApplicationExitInfo info = new ApplicationExitInfo(); 1390 info.readFromProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO); 1391 mInfos.put(info.getPid(), info); 1392 break; 1393 } 1394 } 1395 proto.end(token); 1396 return mUid; 1397 } 1398 1399 @GuardedBy("mLock") toListLocked(List<ApplicationExitInfo> list, int filterPid)1400 List<ApplicationExitInfo> toListLocked(List<ApplicationExitInfo> list, int filterPid) { 1401 if (list == null) { 1402 list = new ArrayList<ApplicationExitInfo>(); 1403 } 1404 for (int i = mInfos.size() - 1; i >= 0; i--) { 1405 if (filterPid == 0 || filterPid == mInfos.keyAt(i)) { 1406 list.add(mInfos.valueAt(i)); 1407 } 1408 } 1409 return list; 1410 } 1411 } 1412 1413 /** 1414 * Maintains the mapping between real UID and the application uid. 1415 */ 1416 final class IsolatedUidRecords { 1417 /** 1418 * A mapping from application uid (with the userId) to isolated uids. 1419 */ 1420 @GuardedBy("mLock") 1421 private final SparseArray<ArraySet<Integer>> mUidToIsolatedUidMap; 1422 1423 /** 1424 * A mapping from isolated uids to application uid (with the userId) 1425 */ 1426 @GuardedBy("mLock") 1427 private final SparseArray<Integer> mIsolatedUidToUidMap; 1428 IsolatedUidRecords()1429 IsolatedUidRecords() { 1430 mUidToIsolatedUidMap = new SparseArray<ArraySet<Integer>>(); 1431 mIsolatedUidToUidMap = new SparseArray<Integer>(); 1432 } 1433 addIsolatedUid(int isolatedUid, int uid)1434 void addIsolatedUid(int isolatedUid, int uid) { 1435 synchronized (mLock) { 1436 ArraySet<Integer> set = mUidToIsolatedUidMap.get(uid); 1437 if (set == null) { 1438 set = new ArraySet<Integer>(); 1439 mUidToIsolatedUidMap.put(uid, set); 1440 } 1441 set.add(isolatedUid); 1442 1443 mIsolatedUidToUidMap.put(isolatedUid, uid); 1444 } 1445 } 1446 1447 @GuardedBy("mLock") getUidByIsolatedUid(int isolatedUid)1448 Integer getUidByIsolatedUid(int isolatedUid) { 1449 if (UserHandle.isIsolated(isolatedUid)) { 1450 synchronized (mLock) { 1451 return mIsolatedUidToUidMap.get(isolatedUid); 1452 } 1453 } 1454 return isolatedUid; 1455 } 1456 1457 @GuardedBy("mLock") removeAppUidLocked(int uid)1458 private void removeAppUidLocked(int uid) { 1459 ArraySet<Integer> set = mUidToIsolatedUidMap.get(uid); 1460 if (set != null) { 1461 for (int i = set.size() - 1; i >= 0; i--) { 1462 int isolatedUid = set.removeAt(i); 1463 mIsolatedUidToUidMap.remove(isolatedUid); 1464 } 1465 } 1466 } 1467 1468 @VisibleForTesting removeAppUid(int uid, boolean allUsers)1469 void removeAppUid(int uid, boolean allUsers) { 1470 synchronized (mLock) { 1471 if (allUsers) { 1472 uid = UserHandle.getAppId(uid); 1473 for (int i = mUidToIsolatedUidMap.size() - 1; i >= 0; i--) { 1474 int u = mUidToIsolatedUidMap.keyAt(i); 1475 if (uid == UserHandle.getAppId(u)) { 1476 removeAppUidLocked(u); 1477 } 1478 mUidToIsolatedUidMap.removeAt(i); 1479 } 1480 } else { 1481 removeAppUidLocked(uid); 1482 mUidToIsolatedUidMap.remove(uid); 1483 } 1484 } 1485 } 1486 1487 @GuardedBy("mLock") removeIsolatedUidLocked(int isolatedUid)1488 int removeIsolatedUidLocked(int isolatedUid) { 1489 if (!UserHandle.isIsolated(isolatedUid)) { 1490 return isolatedUid; 1491 } 1492 int uid = mIsolatedUidToUidMap.get(isolatedUid, -1); 1493 if (uid == -1) { 1494 return isolatedUid; 1495 } 1496 mIsolatedUidToUidMap.remove(isolatedUid); 1497 ArraySet<Integer> set = mUidToIsolatedUidMap.get(uid); 1498 if (set != null) { 1499 set.remove(isolatedUid); 1500 } 1501 // let the ArraySet stay in the mUidToIsolatedUidMap even if it's empty 1502 return uid; 1503 } 1504 removeByUserId(int userId)1505 void removeByUserId(int userId) { 1506 if (userId == UserHandle.USER_CURRENT) { 1507 userId = mService.mUserController.getCurrentUserId(); 1508 } 1509 synchronized (mLock) { 1510 if (userId == UserHandle.USER_ALL) { 1511 mIsolatedUidToUidMap.clear(); 1512 mUidToIsolatedUidMap.clear(); 1513 return; 1514 } 1515 for (int i = mIsolatedUidToUidMap.size() - 1; i >= 0; i--) { 1516 int isolatedUid = mIsolatedUidToUidMap.keyAt(i); 1517 int uid = mIsolatedUidToUidMap.valueAt(i); 1518 if (UserHandle.getUserId(uid) == userId) { 1519 mIsolatedUidToUidMap.removeAt(i); 1520 mUidToIsolatedUidMap.remove(uid); 1521 } 1522 } 1523 } 1524 } 1525 } 1526 1527 final class KillHandler extends Handler { 1528 static final int MSG_LMKD_PROC_KILLED = 4101; 1529 static final int MSG_CHILD_PROC_DIED = 4102; 1530 static final int MSG_PROC_DIED = 4103; 1531 static final int MSG_APP_KILL = 4104; 1532 KillHandler(Looper looper)1533 KillHandler(Looper looper) { 1534 super(looper, null, true); 1535 } 1536 1537 @Override handleMessage(Message msg)1538 public void handleMessage(Message msg) { 1539 switch (msg.what) { 1540 case MSG_LMKD_PROC_KILLED: 1541 mAppExitInfoSourceLmkd.onProcDied(msg.arg1 /* pid */, msg.arg2 /* uid */, 1542 null /* status */); 1543 break; 1544 case MSG_CHILD_PROC_DIED: 1545 mAppExitInfoSourceZygote.onProcDied(msg.arg1 /* pid */, msg.arg2 /* uid */, 1546 (Integer) msg.obj /* status */); 1547 break; 1548 case MSG_PROC_DIED: { 1549 ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj; 1550 synchronized (mLock) { 1551 handleNoteProcessDiedLocked(raw); 1552 recycleRawRecordLocked(raw); 1553 } 1554 } 1555 break; 1556 case MSG_APP_KILL: { 1557 ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj; 1558 synchronized (mLock) { 1559 handleNoteAppKillLocked(raw); 1560 recycleRawRecordLocked(raw); 1561 } 1562 } 1563 break; 1564 default: 1565 super.handleMessage(msg); 1566 } 1567 } 1568 } 1569 isFresh(long timestamp)1570 private static boolean isFresh(long timestamp) { 1571 // A process could be dying but being stuck in some state, i.e., 1572 // being TRACED by tombstoned, thus the zygote receives SIGCHILD 1573 // way after we already knew the kill (maybe because we did the kill :P), 1574 // so here check if the last known kill information is "fresh" enough. 1575 long now = System.currentTimeMillis(); 1576 1577 return (timestamp + AppExitInfoExternalSource.APP_EXIT_INFO_FRESHNESS_MS) >= now; 1578 } 1579 1580 /** 1581 * Keep the raw information about app kills from external sources, i.e., lmkd 1582 */ 1583 final class AppExitInfoExternalSource { 1584 private static final long APP_EXIT_INFO_FRESHNESS_MS = 300 * 1000; 1585 1586 /** 1587 * A mapping between uid -> pid -> {timestamp, extra info(Nullable)}. 1588 * The uid here is the application uid, not the isolated uid. 1589 */ 1590 @GuardedBy("mLock") 1591 private final SparseArray<SparseArray<Pair<Long, Object>>> mData; 1592 1593 /** A tag for logging only */ 1594 private final String mTag; 1595 1596 /** A preset reason in case a proc dies */ 1597 private final Integer mPresetReason; 1598 1599 /** A callback that will be notified when a proc dies */ 1600 private BiConsumer<Integer, Integer> mProcDiedListener; 1601 AppExitInfoExternalSource(String tag, Integer reason)1602 AppExitInfoExternalSource(String tag, Integer reason) { 1603 mData = new SparseArray<SparseArray<Pair<Long, Object>>>(); 1604 mTag = tag; 1605 mPresetReason = reason; 1606 } 1607 1608 @GuardedBy("mLock") addLocked(int pid, int uid, Object extra)1609 private void addLocked(int pid, int uid, Object extra) { 1610 Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid); 1611 if (k != null) { 1612 uid = k; 1613 } 1614 1615 SparseArray<Pair<Long, Object>> array = mData.get(uid); 1616 if (array == null) { 1617 array = new SparseArray<Pair<Long, Object>>(); 1618 mData.put(uid, array); 1619 } 1620 array.put(pid, new Pair<Long, Object>(System.currentTimeMillis(), extra)); 1621 } 1622 1623 @VisibleForTesting remove(int pid, int uid)1624 Pair<Long, Object> remove(int pid, int uid) { 1625 synchronized (mLock) { 1626 Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid); 1627 if (k != null) { 1628 uid = k; 1629 } 1630 1631 SparseArray<Pair<Long, Object>> array = mData.get(uid); 1632 if (array != null) { 1633 Pair<Long, Object> p = array.get(pid); 1634 if (p != null) { 1635 array.remove(pid); 1636 return isFresh(p.first) ? p : null; 1637 } 1638 } 1639 return null; 1640 } 1641 } 1642 removeByUserId(int userId)1643 void removeByUserId(int userId) { 1644 if (userId == UserHandle.USER_CURRENT) { 1645 userId = mService.mUserController.getCurrentUserId(); 1646 } 1647 synchronized (mLock) { 1648 if (userId == UserHandle.USER_ALL) { 1649 mData.clear(); 1650 return; 1651 } 1652 for (int i = mData.size() - 1; i >= 0; i--) { 1653 int uid = mData.keyAt(i); 1654 if (UserHandle.getUserId(uid) == userId) { 1655 mData.removeAt(i); 1656 } 1657 } 1658 } 1659 } 1660 1661 @GuardedBy("mLock") removeByUidLocked(int uid, boolean allUsers)1662 void removeByUidLocked(int uid, boolean allUsers) { 1663 if (UserHandle.isIsolated(uid)) { 1664 Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid); 1665 if (k != null) { 1666 uid = k; 1667 } 1668 } 1669 1670 if (allUsers) { 1671 uid = UserHandle.getAppId(uid); 1672 for (int i = mData.size() - 1; i >= 0; i--) { 1673 if (UserHandle.getAppId(mData.keyAt(i)) == uid) { 1674 mData.removeAt(i); 1675 } 1676 } 1677 } else { 1678 mData.remove(uid); 1679 } 1680 } 1681 setOnProcDiedListener(BiConsumer<Integer, Integer> listener)1682 void setOnProcDiedListener(BiConsumer<Integer, Integer> listener) { 1683 synchronized (mLock) { 1684 mProcDiedListener = listener; 1685 } 1686 } 1687 onProcDied(final int pid, final int uid, final Integer status)1688 void onProcDied(final int pid, final int uid, final Integer status) { 1689 if (DEBUG_PROCESSES) { 1690 Slog.i(TAG, mTag + ": proc died: pid=" + pid + " uid=" + uid 1691 + ", status=" + status); 1692 } 1693 1694 if (mService == null) { 1695 return; 1696 } 1697 1698 // Unlikely but possible: the record has been created 1699 // Let's update it if we could find a ApplicationExitInfo record 1700 synchronized (mLock) { 1701 if (!updateExitInfoIfNecessaryLocked(pid, uid, status, mPresetReason)) { 1702 addLocked(pid, uid, status); 1703 } 1704 1705 // Notify any interesed party regarding the lmkd kills 1706 final BiConsumer<Integer, Integer> listener = mProcDiedListener; 1707 if (listener != null) { 1708 mService.mHandler.post(()-> listener.accept(pid, uid)); 1709 } 1710 } 1711 } 1712 } 1713 1714 /** 1715 * The implementation to the IAppTraceRetriever interface. 1716 */ 1717 @VisibleForTesting 1718 class AppTraceRetriever extends IAppTraceRetriever.Stub { 1719 @Override getTraceFileDescriptor(final String packageName, final int uid, final int pid)1720 public ParcelFileDescriptor getTraceFileDescriptor(final String packageName, 1721 final int uid, final int pid) { 1722 mService.enforceNotIsolatedCaller("getTraceFileDescriptor"); 1723 1724 if (TextUtils.isEmpty(packageName)) { 1725 throw new IllegalArgumentException("Invalid package name"); 1726 } 1727 final int callingPid = Binder.getCallingPid(); 1728 final int callingUid = Binder.getCallingUid(); 1729 final int callingUserId = UserHandle.getCallingUserId(); 1730 final int userId = UserHandle.getUserId(uid); 1731 1732 mService.mUserController.handleIncomingUser(callingPid, callingUid, userId, true, 1733 ALLOW_NON_FULL, "getTraceFileDescriptor", null); 1734 if (mService.enforceDumpPermissionForPackage(packageName, userId, 1735 callingUid, "getTraceFileDescriptor") != Process.INVALID_UID) { 1736 synchronized (mLock) { 1737 final ApplicationExitInfo info = getExitInfoLocked(packageName, uid, pid); 1738 if (info == null) { 1739 return null; 1740 } 1741 final File traceFile = info.getTraceFile(); 1742 if (traceFile == null) { 1743 return null; 1744 } 1745 final long identity = Binder.clearCallingIdentity(); 1746 try { 1747 // The fd will be closed after being written into Parcel 1748 return ParcelFileDescriptor.open(traceFile, 1749 ParcelFileDescriptor.MODE_READ_ONLY); 1750 } catch (FileNotFoundException e) { 1751 return null; 1752 } finally { 1753 Binder.restoreCallingIdentity(identity); 1754 } 1755 } 1756 } 1757 return null; 1758 } 1759 } 1760 } 1761