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.os.Process.THREAD_PRIORITY_BACKGROUND; 20 21 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; 22 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; 23 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.app.ApplicationStartInfo; 27 import android.app.Flags; 28 import android.app.IApplicationStartInfoCompleteListener; 29 import android.content.BroadcastReceiver; 30 import android.content.ComponentName; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.IntentFilter; 34 import android.content.pm.PackageManager; 35 import android.icu.text.SimpleDateFormat; 36 import android.os.Binder; 37 import android.os.Debug; 38 import android.os.FileUtils; 39 import android.os.Handler; 40 import android.os.IBinder.DeathRecipient; 41 import android.os.RemoteException; 42 import android.os.UserHandle; 43 import android.text.TextUtils; 44 import android.util.ArrayMap; 45 import android.util.AtomicFile; 46 import android.util.Slog; 47 import android.util.SparseArray; 48 import android.util.proto.ProtoInputStream; 49 import android.util.proto.ProtoOutputStream; 50 import android.util.proto.WireTypeMismatchException; 51 52 import com.android.internal.annotations.GuardedBy; 53 import com.android.internal.annotations.VisibleForTesting; 54 import com.android.internal.app.ProcessMap; 55 import com.android.internal.os.Clock; 56 import com.android.internal.os.MonotonicClock; 57 import com.android.modules.utils.TypedXmlPullParser; 58 import com.android.modules.utils.TypedXmlSerializer; 59 import com.android.server.IoThread; 60 import com.android.server.ServiceThread; 61 import com.android.server.SystemServiceManager; 62 import com.android.server.wm.WindowProcessController; 63 64 import java.io.ByteArrayInputStream; 65 import java.io.ByteArrayOutputStream; 66 import java.io.File; 67 import java.io.FileInputStream; 68 import java.io.FileOutputStream; 69 import java.io.IOException; 70 import java.io.ObjectInputStream; 71 import java.io.ObjectOutputStream; 72 import java.io.PrintWriter; 73 import java.util.ArrayList; 74 import java.util.Collections; 75 import java.util.Date; 76 import java.util.List; 77 import java.util.Map; 78 import java.util.Optional; 79 import java.util.concurrent.TimeUnit; 80 import java.util.concurrent.atomic.AtomicBoolean; 81 import java.util.function.BiFunction; 82 83 /** A class to manage all the {@link android.app.ApplicationStartInfo} records. */ 84 public final class AppStartInfoTracker { 85 private static final String TAG = TAG_WITH_CLASS_NAME ? "AppStartInfoTracker" : TAG_AM; 86 private static final boolean DEBUG = false; 87 88 /** Interval of persisting the app start info to persistent storage. */ 89 private static final long APP_START_INFO_PERSIST_INTERVAL = TimeUnit.MINUTES.toMillis(30); 90 91 /** These are actions that the forEach* should take after each iteration */ 92 private static final int FOREACH_ACTION_NONE = 0; 93 private static final int FOREACH_ACTION_REMOVE_ITEM = 1; 94 private static final int FOREACH_ACTION_STOP_ITERATION = 2; 95 private static final int FOREACH_ACTION_REMOVE_AND_STOP_ITERATION = 3; 96 97 private static final String MONITORING_MODE_EMPTY_TEXT = "No records"; 98 99 @VisibleForTesting static final int APP_START_INFO_HISTORY_LIST_SIZE = 16; 100 101 @VisibleForTesting 102 static final long APP_START_INFO_HISTORY_LENGTH_MS = TimeUnit.DAYS.toMillis(14); 103 104 /** 105 * The max number of records that can be present in {@link mInProgressRecords}. 106 * 107 * The magic number of 5 records is expected to be enough because this covers in progress 108 * activity starts only, of which more than a 1-2 at a time is very uncommon/unlikely. 109 */ 110 @VisibleForTesting static final int MAX_IN_PROGRESS_RECORDS = 5; 111 112 private static final int APP_START_INFO_MONITORING_MODE_LIST_SIZE = 100; 113 114 @VisibleForTesting static final String APP_START_STORE_DIR = "procstartstore"; 115 116 @VisibleForTesting static final String APP_START_INFO_FILE = "procstartinfo"; 117 118 @VisibleForTesting final Object mLock = new Object(); 119 120 @VisibleForTesting boolean mEnabled = false; 121 122 /** 123 * Monotonic clock which does not reset on reboot. 124 * 125 * Time for offset is persisted along with records, see {@link #persistProcessStartInfo}. 126 * This does not currently follow the recommendation of {@link MonotonicClock} to persist on 127 * shutdown as it's ok in this case to lose any time change past the last persist as records 128 * added since then will be lost as well. Since this time is used for cleanup as well, the 129 * potential old offset may result in the cleanup window being extended slightly beyond the 130 * targeted 14 days. 131 * 132 * TODO: b/402794215 - Persist on shutdown once persist performance is sufficiently improved. 133 */ 134 @VisibleForTesting MonotonicClock mMonotonicClock = null; 135 136 /** Initialized in {@link #init} and read-only after that. */ 137 @VisibleForTesting ActivityManagerService mService; 138 139 /** Initialized in {@link #init} and read-only after that. */ 140 private Handler mHandler; 141 142 /** The task to persist app process start info */ 143 @GuardedBy("mLock") 144 private Runnable mAppStartInfoPersistTask = null; 145 146 /** 147 * Last time(in ms) since epoch that the app start info was persisted into persistent storage. 148 */ 149 @GuardedBy("mLock") 150 private long mLastAppStartInfoPersistTimestamp = 0L; 151 152 /** 153 * Retention policy: keep up to X historical start info per package. 154 * 155 * <p>Initialized in {@link #init} and read-only after that. No lock is needed. 156 */ 157 @VisibleForTesting int mAppStartInfoHistoryListSize; 158 159 @GuardedBy("mLock") 160 private final ProcessMap<AppStartInfoContainer> mData; 161 162 /** UID as key. */ 163 @GuardedBy("mLock") 164 private final SparseArray<ArrayList<ApplicationStartInfoCompleteCallback>> mCallbacks; 165 166 /** 167 * Whether or not we've loaded the historical app process start info from persistent storage. 168 */ 169 @VisibleForTesting AtomicBoolean mAppStartInfoLoaded = new AtomicBoolean(); 170 171 /** Temporary list being used to filter/sort intermediate results in {@link #getStartInfo}. */ 172 @GuardedBy("mLock") 173 final ArrayList<ApplicationStartInfo> mTmpStartInfoList = new ArrayList<>(); 174 175 /** 176 * The path to the directory which includes the historical proc start info file as specified in 177 * {@link #mProcStartInfoFile}. 178 */ 179 @VisibleForTesting File mProcStartStoreDir; 180 181 /** The path to the historical proc start info file, persisted in the storage. */ 182 @VisibleForTesting File mProcStartInfoFile; 183 184 /** 185 * Temporary list of records that have not been completed. 186 * 187 * Key is timestamp of launch from {@link #ActivityMetricsLaunchObserver}. 188 */ 189 @GuardedBy("mLock") 190 @VisibleForTesting 191 final ArrayMap<Long, ApplicationStartInfo> mInProgressRecords = new ArrayMap<>(); 192 193 /** Temporary list of keys present in {@link mInProgressRecords} for sorting. */ 194 @GuardedBy("mLock") 195 @VisibleForTesting 196 final ArrayList<Integer> mTemporaryInProgressIndexes = new ArrayList<>(); 197 AppStartInfoTracker()198 AppStartInfoTracker() { 199 mCallbacks = new SparseArray<>(); 200 mData = new ProcessMap<AppStartInfoContainer>(); 201 } 202 init(ActivityManagerService service)203 void init(ActivityManagerService service) { 204 mService = service; 205 206 ServiceThread thread = 207 new ServiceThread(TAG + ":handler", THREAD_PRIORITY_BACKGROUND, true /* allowIo */); 208 thread.start(); 209 mHandler = new Handler(thread.getLooper()); 210 211 mProcStartStoreDir = new File(SystemServiceManager.ensureSystemDir(), APP_START_STORE_DIR); 212 if (!FileUtils.createDir(mProcStartStoreDir)) { 213 Slog.e(TAG, "Unable to create " + mProcStartStoreDir); 214 return; 215 } 216 mProcStartInfoFile = new File(mProcStartStoreDir, APP_START_INFO_FILE); 217 218 mAppStartInfoHistoryListSize = APP_START_INFO_HISTORY_LIST_SIZE; 219 } 220 onSystemReady()221 void onSystemReady() { 222 mEnabled = Flags.appStartInfo(); 223 if (!mEnabled) { 224 return; 225 } 226 227 registerForUserRemoval(); 228 registerForPackageRemoval(); 229 IoThread.getHandler().post(() -> { 230 loadExistingProcessStartInfo(); 231 }); 232 233 if (mMonotonicClock == null) { 234 // This should only happen if there are no persisted records, or if the records were 235 // persisted by a version without the monotonic clock. Either way, create a new clock 236 // with no offset. In the case of records with no monotonic time the value will default 237 // to 0 and all new records will correctly end up in front of them. 238 mMonotonicClock = new MonotonicClock(Clock.SYSTEM_CLOCK.elapsedRealtime(), 239 Clock.SYSTEM_CLOCK); 240 } 241 } 242 243 /** 244 * Trim in progress records structure to acceptable size. To be called after each time a new 245 * record is added. 246 * 247 * This is necessary both for robustness, as well as because the call to 248 * {@link onReportFullyDrawn} which triggers the removal in the success case is not guaranteed. 249 * 250 * <p class="note"> Note: this is the expected path for removal of in progress records for 251 * successful activity triggered starts that don't report fully drawn. It is *not* only an edge 252 * case.</p> 253 */ 254 @GuardedBy("mLock") maybeTrimInProgressRecordsLocked()255 private void maybeTrimInProgressRecordsLocked() { 256 if (mInProgressRecords.size() <= MAX_IN_PROGRESS_RECORDS) { 257 // Size is acceptable, do nothing. 258 return; 259 } 260 261 // Make sure the temporary list is empty. 262 mTemporaryInProgressIndexes.clear(); 263 264 // Populate the list with indexes for size of {@link mInProgressRecords}. 265 for (int i = 0; i < mInProgressRecords.size(); i++) { 266 mTemporaryInProgressIndexes.add(i, i); 267 } 268 269 // Sort the index collection by value of the corresponding key in {@link mInProgressRecords} 270 // from smallest to largest. 271 Collections.sort(mTemporaryInProgressIndexes, (a, b) -> Long.compare( 272 mInProgressRecords.keyAt(a), mInProgressRecords.keyAt(b))); 273 274 if (mTemporaryInProgressIndexes.size() == MAX_IN_PROGRESS_RECORDS + 1) { 275 // Only removing a single record so don't bother sorting again as we don't have to worry 276 // about indexes changing. 277 mInProgressRecords.removeAt(mTemporaryInProgressIndexes.get(0)); 278 } else { 279 // Removing more than 1 record, remove the records we want to keep from the list and 280 // then sort again so we can remove in reverse order of indexes. 281 mTemporaryInProgressIndexes.subList( 282 mTemporaryInProgressIndexes.size() - MAX_IN_PROGRESS_RECORDS, 283 mTemporaryInProgressIndexes.size()).clear(); 284 Collections.sort(mTemporaryInProgressIndexes); 285 286 // Remove all remaining record indexes in reverse order to avoid changing the already 287 // calculated indexes. 288 for (int i = mTemporaryInProgressIndexes.size() - 1; i >= 0; i--) { 289 mInProgressRecords.removeAt(mTemporaryInProgressIndexes.get(i)); 290 } 291 } 292 293 // Clear the temorary list. 294 mTemporaryInProgressIndexes.clear(); 295 } 296 297 /** 298 * Should only be called for Activity launch sequences from an instance of 299 * {@link ActivityMetricsLaunchObserver}. 300 */ onActivityIntentStarted(@onNull Intent intent, long timestampNanos)301 void onActivityIntentStarted(@NonNull Intent intent, long timestampNanos) { 302 synchronized (mLock) { 303 if (!mEnabled) { 304 return; 305 } 306 ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTimeMs()); 307 start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); 308 start.setIntent(intent); 309 start.setStartType(ApplicationStartInfo.START_TYPE_UNSET); 310 start.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_LAUNCH, timestampNanos); 311 312 if (android.app.Flags.appStartInfoComponent()) { 313 start.setStartComponent(ApplicationStartInfo.START_COMPONENT_ACTIVITY); 314 } 315 316 // TODO: handle possible alarm activity start. 317 if (intent != null && intent.getCategories() != null 318 && intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) { 319 start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER); 320 } else { 321 start.setReason(ApplicationStartInfo.START_REASON_START_ACTIVITY); 322 } 323 mInProgressRecords.put(timestampNanos, start); 324 maybeTrimInProgressRecordsLocked(); 325 } 326 } 327 328 /** 329 * Should only be called for Activity launch sequences from an instance of 330 * {@link ActivityMetricsLaunchObserver}. 331 */ onActivityIntentFailed(long id)332 void onActivityIntentFailed(long id) { 333 synchronized (mLock) { 334 if (!mEnabled) { 335 return; 336 } 337 int index = mInProgressRecords.indexOfKey(id); 338 if (index < 0) { 339 return; 340 } 341 ApplicationStartInfo info = mInProgressRecords.valueAt(index); 342 if (info == null) { 343 mInProgressRecords.removeAt(index); 344 return; 345 } 346 info.setStartupState(ApplicationStartInfo.STARTUP_STATE_ERROR); 347 mInProgressRecords.removeAt(index); 348 } 349 } 350 351 /** 352 * Should only be called for Activity launch sequences from an instance of 353 * {@link ActivityMetricsLaunchObserver}. 354 */ onActivityLaunched(long id, ComponentName name, long temperature, ProcessRecord app)355 void onActivityLaunched(long id, ComponentName name, long temperature, ProcessRecord app) { 356 synchronized (mLock) { 357 if (!mEnabled) { 358 return; 359 } 360 int index = mInProgressRecords.indexOfKey(id); 361 if (index < 0) { 362 return; 363 } 364 ApplicationStartInfo info = mInProgressRecords.valueAt(index); 365 if (info == null || app == null) { 366 mInProgressRecords.removeAt(index); 367 return; 368 } 369 info.setStartType((int) temperature); 370 addBaseFieldsFromProcessRecord(info, app); 371 ApplicationStartInfo newInfo = addStartInfoLocked(info); 372 if (newInfo == null) { 373 // newInfo can be null if records are added before load from storage is 374 // complete. In this case the newly added record will be lost. 375 mInProgressRecords.removeAt(index); 376 } else { 377 mInProgressRecords.setValueAt(index, newInfo); 378 } 379 } 380 } 381 382 /** 383 * Should only be called for Activity launch sequences from an instance of 384 * {@link ActivityMetricsLaunchObserver}. 385 */ onActivityLaunchCancelled(long id)386 void onActivityLaunchCancelled(long id) { 387 synchronized (mLock) { 388 if (!mEnabled) { 389 return; 390 } 391 int index = mInProgressRecords.indexOfKey(id); 392 if (index < 0) { 393 return; 394 } 395 ApplicationStartInfo info = mInProgressRecords.valueAt(index); 396 if (info == null) { 397 mInProgressRecords.removeAt(index); 398 return; 399 } 400 info.setStartupState(ApplicationStartInfo.STARTUP_STATE_ERROR); 401 mInProgressRecords.removeAt(index); 402 } 403 } 404 405 /** 406 * Should only be called for Activity launch sequences from an instance of 407 * {@link ActivityMetricsLaunchObserver}. 408 */ onActivityLaunchFinished(long id, ComponentName name, long timestampNanos, int launchMode)409 void onActivityLaunchFinished(long id, ComponentName name, long timestampNanos, 410 int launchMode) { 411 synchronized (mLock) { 412 if (!mEnabled) { 413 return; 414 } 415 int index = mInProgressRecords.indexOfKey(id); 416 if (index < 0) { 417 return; 418 } 419 ApplicationStartInfo info = mInProgressRecords.valueAt(index); 420 if (info == null) { 421 mInProgressRecords.removeAt(index); 422 return; 423 } 424 info.setLaunchMode(launchMode); 425 if (!android.app.Flags.appStartInfoTimestamps()) { 426 info.setStartupState(ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN); 427 checkCompletenessAndCallback(info); 428 } 429 } 430 } 431 432 /** 433 * Should only be called for Activity launch sequences from an instance of 434 * {@link ActivityMetricsLaunchObserver}. 435 */ 436 @Nullable onActivityReportFullyDrawn(long id, long timestampNanos)437 ApplicationStartInfo onActivityReportFullyDrawn(long id, long timestampNanos) { 438 synchronized (mLock) { 439 if (!mEnabled) { 440 return null; 441 } 442 int index = mInProgressRecords.indexOfKey(id); 443 if (index < 0) { 444 return null; 445 } 446 ApplicationStartInfo info = mInProgressRecords.valueAt(index); 447 if (info == null) { 448 mInProgressRecords.removeAt(index); 449 return null; 450 } 451 info.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN, 452 timestampNanos); 453 mInProgressRecords.removeAt(index); 454 return info; 455 } 456 } 457 handleProcessServiceStart(long startTimeNs, ProcessRecord app, ServiceRecord serviceRecord)458 public void handleProcessServiceStart(long startTimeNs, ProcessRecord app, 459 ServiceRecord serviceRecord) { 460 synchronized (mLock) { 461 if (!mEnabled) { 462 return; 463 } 464 ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTimeMs()); 465 addBaseFieldsFromProcessRecord(start, app); 466 start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); 467 start.addStartupTimestamp( 468 ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs); 469 start.setStartType(ApplicationStartInfo.START_TYPE_COLD); 470 471 if (android.app.Flags.appStartInfoComponent()) { 472 start.setStartComponent(ApplicationStartInfo.START_COMPONENT_SERVICE); 473 } 474 475 // TODO: handle possible alarm service start. 476 start.setReason(serviceRecord.permission != null 477 && serviceRecord.permission.contains("android.permission.BIND_JOB_SERVICE") 478 ? ApplicationStartInfo.START_REASON_JOB 479 : ApplicationStartInfo.START_REASON_SERVICE); 480 if (serviceRecord.intent != null) { 481 start.setIntent(serviceRecord.intent.getIntent()); 482 } 483 addStartInfoLocked(start); 484 } 485 } 486 487 /** Process a broadcast triggered app start. */ handleProcessBroadcastStart(long startTimeNs, ProcessRecord app, Intent intent, boolean isAlarm)488 public void handleProcessBroadcastStart(long startTimeNs, ProcessRecord app, Intent intent, 489 boolean isAlarm) { 490 synchronized (mLock) { 491 if (!mEnabled) { 492 return; 493 } 494 ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTimeMs()); 495 addBaseFieldsFromProcessRecord(start, app); 496 start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); 497 start.addStartupTimestamp( 498 ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs); 499 start.setStartType(ApplicationStartInfo.START_TYPE_COLD); 500 if (isAlarm) { 501 start.setReason(ApplicationStartInfo.START_REASON_ALARM); 502 } else { 503 start.setReason(ApplicationStartInfo.START_REASON_BROADCAST); 504 } 505 start.setIntent(intent); 506 507 if (android.app.Flags.appStartInfoComponent()) { 508 start.setStartComponent(ApplicationStartInfo.START_COMPONENT_BROADCAST); 509 } 510 511 addStartInfoLocked(start); 512 } 513 } 514 515 /** Process a content provider triggered app start. */ handleProcessContentProviderStart(long startTimeNs, ProcessRecord app)516 public void handleProcessContentProviderStart(long startTimeNs, ProcessRecord app) { 517 synchronized (mLock) { 518 if (!mEnabled) { 519 return; 520 } 521 ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTimeMs()); 522 addBaseFieldsFromProcessRecord(start, app); 523 start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); 524 start.addStartupTimestamp( 525 ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs); 526 start.setStartType(ApplicationStartInfo.START_TYPE_COLD); 527 start.setReason(ApplicationStartInfo.START_REASON_CONTENT_PROVIDER); 528 529 if (android.app.Flags.appStartInfoComponent()) { 530 start.setStartComponent(ApplicationStartInfo.START_COMPONENT_CONTENT_PROVIDER); 531 } 532 533 addStartInfoLocked(start); 534 } 535 } 536 handleProcessBackupStart(long startTimeNs, ProcessRecord app, BackupRecord backupRecord, boolean cold)537 public void handleProcessBackupStart(long startTimeNs, ProcessRecord app, 538 BackupRecord backupRecord, boolean cold) { 539 synchronized (mLock) { 540 if (!mEnabled) { 541 return; 542 } 543 ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTimeMs()); 544 addBaseFieldsFromProcessRecord(start, app); 545 start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); 546 start.addStartupTimestamp( 547 ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs); 548 start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD 549 : ApplicationStartInfo.START_TYPE_WARM); 550 start.setReason(ApplicationStartInfo.START_REASON_BACKUP); 551 552 if (android.app.Flags.appStartInfoComponent()) { 553 start.setStartComponent(ApplicationStartInfo.START_COMPONENT_OTHER); 554 } 555 556 addStartInfoLocked(start); 557 } 558 } 559 addBaseFieldsFromProcessRecord(ApplicationStartInfo start, ProcessRecord app)560 private void addBaseFieldsFromProcessRecord(ApplicationStartInfo start, ProcessRecord app) { 561 if (app == null) { 562 if (DEBUG) { 563 Slog.w(TAG, 564 "app is null in addBaseFieldsFromProcessRecord: " + Debug.getCallers(4)); 565 } 566 return; 567 } 568 final int definingUid = app.getHostingRecord() != null 569 ? app.getHostingRecord().getDefiningUid() : 0; 570 start.setPid(app.getPid()); 571 start.setRealUid(app.uid); 572 start.setPackageUid(app.info.uid); 573 start.setDefiningUid(definingUid > 0 ? definingUid : app.info.uid); 574 start.setProcessName(app.processName); 575 start.setPackageName(app.info.packageName); 576 if (android.content.pm.Flags.stayStopped()) { 577 // TODO: Verify this is created at the right time to have the correct force-stopped 578 // state in the ProcessRecord. 579 final WindowProcessController wpc = app.getWindowProcessController(); 580 start.setForceStopped(app.wasForceStopped() 581 || (wpc != null ? wpc.wasForceStopped() : false)); 582 } 583 } 584 585 /** 586 * Helper functions for monitoring shell command. 587 * > adb shell am start-info-detailed-monitoring [package-name] 588 */ configureDetailedMonitoring(PrintWriter pw, String packageName, int userId)589 void configureDetailedMonitoring(PrintWriter pw, String packageName, int userId) { 590 synchronized (mLock) { 591 if (!mEnabled) { 592 return; 593 } 594 595 forEachPackageLocked((name, records) -> { 596 for (int i = 0; i < records.size(); i++) { 597 records.valueAt(i).disableAppMonitoringMode(); 598 } 599 return AppStartInfoTracker.FOREACH_ACTION_NONE; 600 }); 601 602 if (TextUtils.isEmpty(packageName)) { 603 pw.println("ActivityManager AppStartInfo detailed monitoring disabled"); 604 } else { 605 SparseArray<AppStartInfoContainer> array = mData.getMap().get(packageName); 606 if (array != null) { 607 for (int i = 0; i < array.size(); i++) { 608 array.valueAt(i).enableAppMonitoringModeForUser(userId); 609 } 610 pw.println("ActivityManager AppStartInfo detailed monitoring enabled for " 611 + packageName); 612 } else { 613 pw.println("Package " + packageName + " not found"); 614 } 615 } 616 } 617 } 618 addTimestampToStart(ProcessRecord app, long timeNs, int key)619 void addTimestampToStart(ProcessRecord app, long timeNs, int key) { 620 addTimestampToStart(app.info.packageName, app.uid, timeNs, key); 621 } 622 addTimestampToStart(String packageName, int uid, long timeNs, int key)623 void addTimestampToStart(String packageName, int uid, long timeNs, int key) { 624 if (!mEnabled) { 625 return; 626 } 627 synchronized (mLock) { 628 AppStartInfoContainer container = mData.get(packageName, uid); 629 if (container == null) { 630 // Record was not created, discard new data. 631 if (DEBUG) { 632 Slog.d(TAG, "No container found for package=" + packageName + " and uid=" + uid 633 + ". Discarding timestamp key=" + key + " val=" + timeNs); 634 } 635 return; 636 } 637 container.addTimestampToStartLocked(key, timeNs); 638 } 639 } 640 641 @GuardedBy("mLock") addStartInfoLocked(ApplicationStartInfo raw)642 private ApplicationStartInfo addStartInfoLocked(ApplicationStartInfo raw) { 643 if (!mAppStartInfoLoaded.get()) { 644 //records added before initial load from storage will be lost. 645 Slog.w(TAG, "Skipping saving the start info due to ongoing loading from storage"); 646 return null; 647 } 648 649 final ApplicationStartInfo info = new ApplicationStartInfo(raw); 650 int uid = raw.getRealUid(); 651 652 // Isolated process starts won't be reasonably accessible if stored by their uid, don't 653 // store them. 654 if (com.android.server.am.Flags.appStartInfoIsolatedProcess() 655 && UserHandle.isIsolated(uid)) { 656 return null; 657 } 658 659 AppStartInfoContainer container = mData.get(raw.getPackageName(), uid); 660 if (container == null) { 661 container = new AppStartInfoContainer(mAppStartInfoHistoryListSize); 662 container.mUid = uid; 663 mData.put(raw.getPackageName(), uid, container); 664 } 665 container.addStartInfoLocked(info); 666 667 schedulePersistProcessStartInfo(false); 668 669 return info; 670 } 671 672 /** 673 * Called whenever a potentially final piece of data is added to a {@link ApplicationStartInfo} 674 * object. Checks for completeness and triggers callback if a callback has been registered and 675 * the object is complete. 676 */ checkCompletenessAndCallback(ApplicationStartInfo startInfo)677 private void checkCompletenessAndCallback(ApplicationStartInfo startInfo) { 678 synchronized (mLock) { 679 if (startInfo.getStartupState() 680 == ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN) { 681 final List<ApplicationStartInfoCompleteCallback> callbacks = 682 mCallbacks.get(startInfo.getRealUid()); 683 if (callbacks == null) { 684 return; 685 } 686 final int size = callbacks.size(); 687 for (int i = 0; i < size; i++) { 688 if (callbacks.get(i) != null) { 689 callbacks.get(i).onApplicationStartInfoComplete(startInfo); 690 } 691 } 692 mCallbacks.remove(startInfo.getRealUid()); 693 } 694 } 695 } 696 getStartInfo(String packageName, int filterUid, int filterPid, int maxNum, ArrayList<ApplicationStartInfo> results)697 void getStartInfo(String packageName, int filterUid, int filterPid, 698 int maxNum, ArrayList<ApplicationStartInfo> results) { 699 if (!mEnabled) { 700 return; 701 } 702 if (maxNum == 0) { 703 maxNum = APP_START_INFO_HISTORY_LIST_SIZE; 704 } 705 final long identity = Binder.clearCallingIdentity(); 706 try { 707 synchronized (mLock) { 708 boolean emptyPackageName = TextUtils.isEmpty(packageName); 709 if (!emptyPackageName) { 710 // fast path 711 AppStartInfoContainer container = mData.get(packageName, filterUid); 712 if (container != null) { 713 container.getStartInfoLocked(filterPid, maxNum, results); 714 } 715 } else { 716 // slow path 717 final ArrayList<ApplicationStartInfo> list = mTmpStartInfoList; 718 list.clear(); 719 // get all packages 720 forEachPackageLocked( 721 (name, records) -> { 722 AppStartInfoContainer container = records.get(filterUid); 723 if (container != null) { 724 list.addAll(container.mInfos); 725 } 726 return AppStartInfoTracker.FOREACH_ACTION_NONE; 727 }); 728 729 Collections.sort( 730 list, (a, b) -> 731 Long.compare(b.getMonotonicCreationTimeMs(), 732 a.getMonotonicCreationTimeMs())); 733 int size = list.size(); 734 if (maxNum > 0) { 735 size = Math.min(size, maxNum); 736 } 737 for (int i = 0; i < size; i++) { 738 results.add(list.get(i)); 739 } 740 list.clear(); 741 } 742 } 743 } finally { 744 Binder.restoreCallingIdentity(identity); 745 } 746 } 747 748 final class ApplicationStartInfoCompleteCallback implements DeathRecipient { 749 private final int mUid; 750 private final IApplicationStartInfoCompleteListener mCallback; 751 ApplicationStartInfoCompleteCallback(IApplicationStartInfoCompleteListener callback, int uid)752 ApplicationStartInfoCompleteCallback(IApplicationStartInfoCompleteListener callback, 753 int uid) { 754 mCallback = callback; 755 mUid = uid; 756 try { 757 mCallback.asBinder().linkToDeath(this, 0); 758 } catch (RemoteException e) { 759 /*ignored*/ 760 } 761 } 762 onApplicationStartInfoComplete(ApplicationStartInfo startInfo)763 void onApplicationStartInfoComplete(ApplicationStartInfo startInfo) { 764 try { 765 mCallback.onApplicationStartInfoComplete(startInfo); 766 } catch (RemoteException e) { 767 /*ignored*/ 768 } 769 } 770 unlinkToDeath()771 void unlinkToDeath() { 772 mCallback.asBinder().unlinkToDeath(this, 0); 773 } 774 775 @Override binderDied()776 public void binderDied() { 777 removeStartInfoCompleteListener(mCallback, mUid, false); 778 } 779 } 780 addStartInfoCompleteListener( final IApplicationStartInfoCompleteListener listener, final int uid)781 void addStartInfoCompleteListener( 782 final IApplicationStartInfoCompleteListener listener, final int uid) { 783 synchronized (mLock) { 784 if (!mEnabled) { 785 return; 786 } 787 ArrayList<ApplicationStartInfoCompleteCallback> callbacks = mCallbacks.get(uid); 788 if (callbacks == null) { 789 mCallbacks.set(uid, 790 callbacks = new ArrayList<ApplicationStartInfoCompleteCallback>()); 791 } 792 callbacks.add(new ApplicationStartInfoCompleteCallback(listener, uid)); 793 } 794 } 795 removeStartInfoCompleteListener( final IApplicationStartInfoCompleteListener listener, final int uid, boolean unlinkDeathRecipient)796 void removeStartInfoCompleteListener( 797 final IApplicationStartInfoCompleteListener listener, final int uid, 798 boolean unlinkDeathRecipient) { 799 synchronized (mLock) { 800 if (!mEnabled) { 801 return; 802 } 803 final ArrayList<ApplicationStartInfoCompleteCallback> callbacks = mCallbacks.get(uid); 804 if (callbacks == null) { 805 return; 806 } 807 final int size = callbacks.size(); 808 int index; 809 for (index = 0; index < size; index++) { 810 final ApplicationStartInfoCompleteCallback callback = callbacks.get(index); 811 if (callback.mCallback == listener) { 812 if (unlinkDeathRecipient) { 813 callback.unlinkToDeath(); 814 } 815 break; 816 } 817 } 818 if (index < size) { 819 callbacks.remove(index); 820 } 821 if (callbacks.isEmpty()) { 822 mCallbacks.remove(uid); 823 } 824 } 825 } 826 827 /** 828 * Run provided callback for each packake in start info dataset. 829 * 830 * @return whether the for each completed naturally, false if it was stopped manually. 831 */ 832 @GuardedBy("mLock") forEachPackageLocked( BiFunction<String, SparseArray<AppStartInfoContainer>, Integer> callback)833 private boolean forEachPackageLocked( 834 BiFunction<String, SparseArray<AppStartInfoContainer>, Integer> callback) { 835 if (callback != null) { 836 ArrayMap<String, SparseArray<AppStartInfoContainer>> map = mData.getMap(); 837 for (int i = map.size() - 1; i >= 0; i--) { 838 switch (callback.apply(map.keyAt(i), map.valueAt(i))) { 839 case FOREACH_ACTION_REMOVE_ITEM: 840 map.removeAt(i); 841 break; 842 case FOREACH_ACTION_STOP_ITERATION: 843 return false; 844 case FOREACH_ACTION_REMOVE_AND_STOP_ITERATION: 845 map.removeAt(i); 846 return false; 847 case FOREACH_ACTION_NONE: 848 default: 849 break; 850 } 851 } 852 } 853 return true; 854 } 855 856 @GuardedBy("mLock") removePackageLocked(String packageName, int uid, boolean removeUid, int userId)857 private void removePackageLocked(String packageName, int uid, boolean removeUid, int userId) { 858 ArrayMap<String, SparseArray<AppStartInfoContainer>> map = mData.getMap(); 859 SparseArray<AppStartInfoContainer> array = map.get(packageName); 860 if (array == null) { 861 return; 862 } 863 if (userId == UserHandle.USER_ALL) { 864 mData.getMap().remove(packageName); 865 } else { 866 for (int i = array.size() - 1; i >= 0; i--) { 867 if (UserHandle.getUserId(array.keyAt(i)) == userId) { 868 array.removeAt(i); 869 break; 870 } 871 } 872 if (array.size() == 0) { 873 map.remove(packageName); 874 } 875 } 876 } 877 878 @GuardedBy("mLock") removeByUserIdLocked(final int userId)879 private void removeByUserIdLocked(final int userId) { 880 if (userId == UserHandle.USER_ALL) { 881 mData.getMap().clear(); 882 return; 883 } 884 forEachPackageLocked( 885 (packageName, records) -> { 886 for (int i = records.size() - 1; i >= 0; i--) { 887 if (UserHandle.getUserId(records.keyAt(i)) == userId) { 888 records.removeAt(i); 889 break; 890 } 891 } 892 return records.size() == 0 ? FOREACH_ACTION_REMOVE_ITEM : FOREACH_ACTION_NONE; 893 }); 894 } 895 896 @VisibleForTesting onUserRemoved(int userId)897 void onUserRemoved(int userId) { 898 synchronized (mLock) { 899 if (!mEnabled) { 900 return; 901 } 902 removeByUserIdLocked(userId); 903 schedulePersistProcessStartInfo(true); 904 } 905 } 906 907 @VisibleForTesting onPackageRemoved(String packageName, int uid, boolean allUsers)908 void onPackageRemoved(String packageName, int uid, boolean allUsers) { 909 if (!mEnabled) { 910 return; 911 } 912 if (packageName != null) { 913 final boolean removeUid = 914 TextUtils.isEmpty(mService.mPackageManagerInt.getNameForUid(uid)); 915 synchronized (mLock) { 916 removePackageLocked( 917 packageName, 918 uid, 919 removeUid, 920 allUsers ? UserHandle.USER_ALL : UserHandle.getUserId(uid)); 921 schedulePersistProcessStartInfo(true); 922 } 923 } 924 } 925 registerForUserRemoval()926 private void registerForUserRemoval() { 927 IntentFilter filter = new IntentFilter(); 928 filter.addAction(Intent.ACTION_USER_REMOVED); 929 mService.mContext.registerReceiverForAllUsers( 930 new BroadcastReceiver() { 931 @Override 932 public void onReceive(Context context, Intent intent) { 933 int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); 934 if (userId < 1) return; 935 onUserRemoved(userId); 936 } 937 }, 938 filter, 939 null, 940 mHandler); 941 } 942 registerForPackageRemoval()943 private void registerForPackageRemoval() { 944 IntentFilter filter = new IntentFilter(); 945 filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 946 filter.addDataScheme("package"); 947 mService.mContext.registerReceiverForAllUsers( 948 new BroadcastReceiver() { 949 @Override 950 public void onReceive(Context context, Intent intent) { 951 boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); 952 if (replacing) { 953 return; 954 } 955 int uid = intent.getIntExtra(Intent.EXTRA_UID, UserHandle.USER_NULL); 956 boolean allUsers = 957 intent.getBooleanExtra(Intent.EXTRA_REMOVED_FOR_ALL_USERS, false); 958 onPackageRemoved(intent.getData().getSchemeSpecificPart(), uid, allUsers); 959 } 960 }, 961 filter, 962 null, 963 mHandler); 964 } 965 966 /** 967 * Load the existing {@link android.app.ApplicationStartInfo} records from persistent storage. 968 */ 969 @VisibleForTesting loadExistingProcessStartInfo()970 void loadExistingProcessStartInfo() { 971 if (!mEnabled) { 972 return; 973 } 974 if (!mProcStartInfoFile.canRead()) { 975 // If file can't be read, mark complete so we can begin accepting new records. 976 mAppStartInfoLoaded.set(true); 977 return; 978 } 979 980 FileInputStream fin = null; 981 try { 982 AtomicFile af = new AtomicFile(mProcStartInfoFile); 983 fin = af.openRead(); 984 ProtoInputStream proto = new ProtoInputStream(fin); 985 for (int next = proto.nextField(); 986 next != ProtoInputStream.NO_MORE_FIELDS; 987 next = proto.nextField()) { 988 switch (next) { 989 case (int) AppsStartInfoProto.LAST_UPDATE_TIMESTAMP: 990 synchronized (mLock) { 991 mLastAppStartInfoPersistTimestamp = 992 proto.readLong(AppsStartInfoProto.LAST_UPDATE_TIMESTAMP); 993 } 994 break; 995 case (int) AppsStartInfoProto.PACKAGES: 996 loadPackagesFromProto(proto, next); 997 break; 998 case (int) AppsStartInfoProto.MONOTONIC_TIME: 999 long monotonicTime = proto.readLong(AppsStartInfoProto.MONOTONIC_TIME); 1000 mMonotonicClock = new MonotonicClock(monotonicTime, Clock.SYSTEM_CLOCK); 1001 break; 1002 } 1003 } 1004 } catch (IOException | IllegalArgumentException | WireTypeMismatchException 1005 | ClassNotFoundException e) { 1006 Slog.w(TAG, "Error in loading historical app start info from persistent storage: " + e); 1007 } finally { 1008 if (fin != null) { 1009 try { 1010 fin.close(); 1011 } catch (IOException e) { 1012 } 1013 } 1014 } 1015 mAppStartInfoLoaded.set(true); 1016 } 1017 loadPackagesFromProto(ProtoInputStream proto, long fieldId)1018 private void loadPackagesFromProto(ProtoInputStream proto, long fieldId) 1019 throws IOException, WireTypeMismatchException, ClassNotFoundException { 1020 long token = proto.start(fieldId); 1021 String pkgName = ""; 1022 1023 // Create objects for reuse. 1024 ByteArrayInputStream byteArrayInputStream = null; 1025 ObjectInputStream objectInputStream = null; 1026 TypedXmlPullParser typedXmlPullParser = null; 1027 1028 for (int next = proto.nextField(); 1029 next != ProtoInputStream.NO_MORE_FIELDS; 1030 next = proto.nextField()) { 1031 switch (next) { 1032 case (int) AppsStartInfoProto.Package.PACKAGE_NAME: 1033 pkgName = proto.readString(AppsStartInfoProto.Package.PACKAGE_NAME); 1034 break; 1035 case (int) AppsStartInfoProto.Package.USERS: 1036 AppStartInfoContainer container = 1037 new AppStartInfoContainer(mAppStartInfoHistoryListSize); 1038 int uid = container.readFromProto(proto, AppsStartInfoProto.Package.USERS, 1039 pkgName, byteArrayInputStream, objectInputStream, typedXmlPullParser); 1040 1041 // If the isolated process flag is enabled and the uid is that of an isolated 1042 // process, then break early so that the container will not be added to mData. 1043 // This is expected only as a one time mitigation, records added after this flag 1044 // is enabled should always return false for isIsolated and thereby always 1045 // continue on. 1046 if (com.android.server.am.Flags.appStartInfoIsolatedProcess() 1047 && UserHandle.isIsolated(uid)) { 1048 break; 1049 } 1050 1051 synchronized (mLock) { 1052 mData.put(pkgName, uid, container); 1053 } 1054 break; 1055 } 1056 } 1057 proto.end(token); 1058 } 1059 1060 /** Persist the existing {@link android.app.ApplicationStartInfo} records to storage. */ 1061 @VisibleForTesting persistProcessStartInfo()1062 void persistProcessStartInfo() { 1063 if (!mEnabled) { 1064 return; 1065 } 1066 AtomicFile af = new AtomicFile(mProcStartInfoFile); 1067 FileOutputStream out = null; 1068 boolean succeeded; 1069 long now = System.currentTimeMillis(); 1070 try { 1071 out = af.startWrite(); 1072 ProtoOutputStream proto = new ProtoOutputStream(out); 1073 proto.write(AppsStartInfoProto.LAST_UPDATE_TIMESTAMP, now); 1074 1075 // Create objects for reuse. 1076 ByteArrayOutputStream byteArrayOutputStream = null; 1077 ObjectOutputStream objectOutputStream = null; 1078 TypedXmlSerializer typedXmlSerializer = null; 1079 1080 synchronized (mLock) { 1081 succeeded = forEachPackageLocked( 1082 (packageName, records) -> { 1083 long token = proto.start(AppsStartInfoProto.PACKAGES); 1084 proto.write(AppsStartInfoProto.Package.PACKAGE_NAME, packageName); 1085 int uidArraySize = records.size(); 1086 for (int j = 0; j < uidArraySize; j++) { 1087 try { 1088 records.valueAt(j).writeToProto(proto, 1089 AppsStartInfoProto.Package.USERS, byteArrayOutputStream, 1090 objectOutputStream, typedXmlSerializer); 1091 } catch (IOException e) { 1092 Slog.w(TAG, "Unable to write app start info into persistent" 1093 + "storage: " + e); 1094 // There was likely an issue with this record that won't resolve 1095 // next time we try to persist so remove it. Also stop iteration 1096 // as we failed the write and need to start again from scratch. 1097 return AppStartInfoTracker 1098 .FOREACH_ACTION_REMOVE_AND_STOP_ITERATION; 1099 } 1100 } 1101 proto.end(token); 1102 return AppStartInfoTracker.FOREACH_ACTION_NONE; 1103 }); 1104 if (succeeded) { 1105 mLastAppStartInfoPersistTimestamp = now; 1106 } 1107 } 1108 proto.write(AppsStartInfoProto.MONOTONIC_TIME, getMonotonicTimeMs()); 1109 if (succeeded) { 1110 proto.flush(); 1111 af.finishWrite(out); 1112 } else { 1113 af.failWrite(out); 1114 } 1115 } catch (IOException e) { 1116 Slog.w(TAG, "Unable to write historical app start info into persistent storage: " + e); 1117 af.failWrite(out); 1118 } 1119 synchronized (mLock) { 1120 mAppStartInfoPersistTask = null; 1121 } 1122 } 1123 1124 /** 1125 * Schedule a task to persist the {@link android.app.ApplicationStartInfo} records to storage. 1126 */ 1127 @VisibleForTesting schedulePersistProcessStartInfo(boolean immediately)1128 void schedulePersistProcessStartInfo(boolean immediately) { 1129 synchronized (mLock) { 1130 if (!mEnabled) { 1131 return; 1132 } 1133 if (mAppStartInfoPersistTask == null || immediately) { 1134 if (mAppStartInfoPersistTask != null) { 1135 IoThread.getHandler().removeCallbacks(mAppStartInfoPersistTask); 1136 } 1137 mAppStartInfoPersistTask = this::persistProcessStartInfo; 1138 IoThread.getHandler() 1139 .postDelayed( 1140 mAppStartInfoPersistTask, 1141 immediately ? 0 : APP_START_INFO_PERSIST_INTERVAL); 1142 } 1143 } 1144 } 1145 1146 /** Helper function for testing only. */ 1147 @VisibleForTesting clearProcessStartInfo(boolean removeFile)1148 void clearProcessStartInfo(boolean removeFile) { 1149 synchronized (mLock) { 1150 if (!mEnabled) { 1151 return; 1152 } 1153 if (mAppStartInfoPersistTask != null) { 1154 IoThread.getHandler().removeCallbacks(mAppStartInfoPersistTask); 1155 mAppStartInfoPersistTask = null; 1156 } 1157 if (removeFile && mProcStartInfoFile != null) { 1158 mProcStartInfoFile.delete(); 1159 } 1160 mData.getMap().clear(); 1161 mInProgressRecords.clear(); 1162 } 1163 } 1164 1165 /** 1166 * Helper functions for shell command. 1167 * > adb shell dumpsys activity clear-start-info [package-name] 1168 */ clearHistoryProcessStartInfo(String packageName, int userId)1169 void clearHistoryProcessStartInfo(String packageName, int userId) { 1170 if (!mEnabled) { 1171 return; 1172 } 1173 Optional<Integer> appId = Optional.empty(); 1174 if (TextUtils.isEmpty(packageName)) { 1175 synchronized (mLock) { 1176 removeByUserIdLocked(userId); 1177 } 1178 } else { 1179 final int uid = 1180 mService.mPackageManagerInt.getPackageUid( 1181 packageName, PackageManager.MATCH_ALL, userId); 1182 appId = Optional.of(UserHandle.getAppId(uid)); 1183 synchronized (mLock) { 1184 removePackageLocked(packageName, uid, true, userId); 1185 } 1186 } 1187 schedulePersistProcessStartInfo(true); 1188 } 1189 1190 /** 1191 * Helper functions for shell command. 1192 * > adb shell dumpsys activity start-info [package-name] 1193 */ dumpHistoryProcessStartInfo(PrintWriter pw, String packageName)1194 void dumpHistoryProcessStartInfo(PrintWriter pw, String packageName) { 1195 if (!mEnabled) { 1196 return; 1197 } 1198 pw.println("ACTIVITY MANAGER LRU PROCESSES (dumpsys activity start-info)"); 1199 SimpleDateFormat sdf = new SimpleDateFormat(); 1200 synchronized (mLock) { 1201 pw.println("Last Timestamp of Persistence Into Persistent Storage: " 1202 + sdf.format(new Date(mLastAppStartInfoPersistTimestamp))); 1203 if (TextUtils.isEmpty(packageName)) { 1204 forEachPackageLocked((name, records) -> { 1205 dumpHistoryProcessStartInfoLocked(pw, " ", name, records, sdf); 1206 return AppStartInfoTracker.FOREACH_ACTION_NONE; 1207 }); 1208 } else { 1209 SparseArray<AppStartInfoContainer> array = mData.getMap().get(packageName); 1210 if (array != null) { 1211 dumpHistoryProcessStartInfoLocked(pw, " ", packageName, array, sdf); 1212 } 1213 } 1214 } 1215 } 1216 1217 @GuardedBy("mLock") dumpHistoryProcessStartInfoLocked(PrintWriter pw, String prefix, String packageName, SparseArray<AppStartInfoContainer> array, SimpleDateFormat sdf)1218 private void dumpHistoryProcessStartInfoLocked(PrintWriter pw, String prefix, 1219 String packageName, SparseArray<AppStartInfoContainer> array, 1220 SimpleDateFormat sdf) { 1221 pw.println(prefix + "package: " + packageName); 1222 int size = array.size(); 1223 for (int i = 0; i < size; i++) { 1224 pw.println(prefix + " Historical Process Start for userId=" + array.keyAt(i)); 1225 array.valueAt(i).dumpLocked(pw, prefix + " ", sdf); 1226 } 1227 } 1228 1229 /** 1230 * Monotonic time that doesn't change with reboot or device time change for ordering records. 1231 */ 1232 @VisibleForTesting getMonotonicTimeMs()1233 public long getMonotonicTimeMs() { 1234 if (mMonotonicClock == null) { 1235 // This should never happen. Return 0 to not interfere with past or future records. 1236 return 0; 1237 } 1238 return mMonotonicClock.monotonicTime(); 1239 } 1240 1241 /** A container class of (@link android.app.ApplicationStartInfo) */ 1242 final class AppStartInfoContainer { 1243 private ArrayList<ApplicationStartInfo> mInfos; // Always kept sorted by monotonic time. 1244 private int mMaxCapacity; 1245 private int mUid; 1246 private boolean mMonitoringModeEnabled = false; 1247 AppStartInfoContainer(final int maxCapacity)1248 AppStartInfoContainer(final int maxCapacity) { 1249 mInfos = new ArrayList<ApplicationStartInfo>(); 1250 mMaxCapacity = maxCapacity; 1251 } 1252 getMaxCapacity()1253 int getMaxCapacity() { 1254 return mMonitoringModeEnabled ? APP_START_INFO_MONITORING_MODE_LIST_SIZE : mMaxCapacity; 1255 } 1256 1257 @GuardedBy("mLock") enableAppMonitoringModeForUser(int userId)1258 void enableAppMonitoringModeForUser(int userId) { 1259 if (UserHandle.getUserId(mUid) == userId) { 1260 mMonitoringModeEnabled = true; 1261 } 1262 } 1263 1264 @GuardedBy("mLock") disableAppMonitoringMode()1265 void disableAppMonitoringMode() { 1266 mMonitoringModeEnabled = false; 1267 1268 // Capacity is reduced by turning off monitoring mode. Check if array size is within 1269 // new lower limits and trim extraneous records if it is not. 1270 if (mInfos.size() <= getMaxCapacity()) { 1271 return; 1272 } 1273 1274 if (!android.app.Flags.appStartInfoKeepRecordsSorted()) { 1275 // Sort records so we can remove the least recent ones. 1276 Collections.sort(mInfos, (a, b) -> 1277 Long.compare(b.getMonotonicCreationTimeMs(), 1278 a.getMonotonicCreationTimeMs())); 1279 } 1280 1281 // Remove records and trim list object back to size. 1282 mInfos.subList(0, mInfos.size() - getMaxCapacity()).clear(); 1283 mInfos.trimToSize(); 1284 } 1285 1286 @GuardedBy("mLock") getStartInfoLocked( final int filterPid, final int maxNum, ArrayList<ApplicationStartInfo> results)1287 void getStartInfoLocked( 1288 final int filterPid, final int maxNum, ArrayList<ApplicationStartInfo> results) { 1289 results.addAll(mInfos.size() <= maxNum ? 0 : mInfos.size() - maxNum, mInfos); 1290 } 1291 1292 @GuardedBy("mLock") addStartInfoLocked(ApplicationStartInfo info)1293 void addStartInfoLocked(ApplicationStartInfo info) { 1294 if (android.app.Flags.appStartInfoKeepRecordsSorted()) { 1295 while (mInfos.size() >= getMaxCapacity()) { 1296 // Expected to execute at most once. 1297 mInfos.removeLast(); 1298 } 1299 mInfos.addFirst(info); 1300 } else { 1301 int size = mInfos.size(); 1302 if (size >= getMaxCapacity()) { 1303 // Remove oldest record if size is over max capacity. 1304 int oldestIndex = -1; 1305 long oldestTimeStamp = Long.MAX_VALUE; 1306 for (int i = 0; i < size; i++) { 1307 ApplicationStartInfo startInfo = mInfos.get(i); 1308 if (startInfo.getMonotonicCreationTimeMs() < oldestTimeStamp) { 1309 oldestTimeStamp = startInfo.getMonotonicCreationTimeMs(); 1310 oldestIndex = i; 1311 } 1312 } 1313 if (oldestIndex >= 0) { 1314 mInfos.remove(oldestIndex); 1315 } 1316 } 1317 mInfos.add(info); 1318 Collections.sort(mInfos, (a, b) -> 1319 Long.compare(b.getMonotonicCreationTimeMs(), 1320 a.getMonotonicCreationTimeMs())); 1321 } 1322 } 1323 1324 /** 1325 * Add the provided key/timestamp to the most recent start record, if it is currently 1326 * accepting new timestamps. 1327 * 1328 * Will also update the start records startup state and trigger the completion listener when 1329 * appropriate. 1330 */ 1331 @GuardedBy("mLock") addTimestampToStartLocked(int key, long timestampNs)1332 void addTimestampToStartLocked(int key, long timestampNs) { 1333 if (mInfos.isEmpty()) { 1334 if (DEBUG) Slog.d(TAG, "No records to add to."); 1335 return; 1336 } 1337 1338 // Records are sorted newest to oldest, grab record at index 0. 1339 ApplicationStartInfo startInfo = mInfos.get(0); 1340 1341 if (!isAddTimestampAllowed(startInfo, key, timestampNs)) { 1342 return; 1343 } 1344 1345 startInfo.addStartupTimestamp(key, timestampNs); 1346 1347 if (key == ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME 1348 && android.app.Flags.appStartInfoTimestamps()) { 1349 startInfo.setStartupState(ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN); 1350 checkCompletenessAndCallback(startInfo); 1351 } 1352 } 1353 isAddTimestampAllowed(ApplicationStartInfo startInfo, int key, long timestampNs)1354 private boolean isAddTimestampAllowed(ApplicationStartInfo startInfo, int key, 1355 long timestampNs) { 1356 int startupState = startInfo.getStartupState(); 1357 1358 // If startup state is error then don't accept any further timestamps. 1359 if (startupState == ApplicationStartInfo.STARTUP_STATE_ERROR) { 1360 if (DEBUG) Slog.d(TAG, "Startup state is error, not accepting new timestamps."); 1361 return false; 1362 } 1363 1364 Map<Integer, Long> timestamps = startInfo.getStartupTimestamps(); 1365 1366 if (startupState == ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN) { 1367 switch (key) { 1368 case ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN: 1369 // Allowed, continue to confirm it's not already added. 1370 break; 1371 case ApplicationStartInfo.START_TIMESTAMP_INITIAL_RENDERTHREAD_FRAME: 1372 Long firstFrameTimeNs = timestamps 1373 .get(ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME); 1374 if (firstFrameTimeNs == null) { 1375 // This should never happen. State can't be first frame drawn if first 1376 // frame timestamp was not provided. 1377 return false; 1378 } 1379 1380 if (timestampNs > firstFrameTimeNs) { 1381 // Initial renderthread frame has to occur before first frame. 1382 return false; 1383 } 1384 1385 // Allowed, continue to confirm it's not already added. 1386 break; 1387 case ApplicationStartInfo.START_TIMESTAMP_SURFACEFLINGER_COMPOSITION_COMPLETE: 1388 // Allowed, continue to confirm it's not already added. 1389 break; 1390 default: 1391 return false; 1392 } 1393 } 1394 1395 if (timestamps.get(key) != null) { 1396 // Timestamp should not occur more than once for a given start. 1397 return false; 1398 } 1399 1400 return true; 1401 } 1402 1403 @GuardedBy("mLock") dumpLocked(PrintWriter pw, String prefix, SimpleDateFormat sdf)1404 void dumpLocked(PrintWriter pw, String prefix, SimpleDateFormat sdf) { 1405 if (mMonitoringModeEnabled) { 1406 // For monitoring mode, calculate the average start time for each start state to 1407 // add to output. 1408 List<Long> coldStartTimes = new ArrayList<>(); 1409 List<Long> warmStartTimes = new ArrayList<>(); 1410 List<Long> hotStartTimes = new ArrayList<>(); 1411 1412 for (int i = 0; i < mInfos.size(); i++) { 1413 ApplicationStartInfo startInfo = mInfos.get(i); 1414 Map<Integer, Long> timestamps = startInfo.getStartupTimestamps(); 1415 1416 // Confirm required timestamps exist. 1417 if (timestamps.containsKey(ApplicationStartInfo.START_TIMESTAMP_LAUNCH) 1418 && timestamps.containsKey( 1419 ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME)) { 1420 // Add timestamp to correct collection. 1421 long time = timestamps.get(ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME) 1422 - timestamps.get(ApplicationStartInfo.START_TIMESTAMP_LAUNCH); 1423 switch (startInfo.getStartType()) { 1424 case ApplicationStartInfo.START_TYPE_COLD: 1425 coldStartTimes.add(time); 1426 break; 1427 case ApplicationStartInfo.START_TYPE_WARM: 1428 warmStartTimes.add(time); 1429 break; 1430 case ApplicationStartInfo.START_TYPE_HOT: 1431 hotStartTimes.add(time); 1432 break; 1433 } 1434 } 1435 } 1436 1437 pw.println(prefix + " Average Start Time in ns for Cold Starts: " 1438 + (coldStartTimes.isEmpty() ? MONITORING_MODE_EMPTY_TEXT 1439 : calculateAverage(coldStartTimes))); 1440 pw.println(prefix + " Average Start Time in ns for Warm Starts: " 1441 + (warmStartTimes.isEmpty() ? MONITORING_MODE_EMPTY_TEXT 1442 : calculateAverage(warmStartTimes))); 1443 pw.println(prefix + " Average Start Time in ns for Hot Starts: " 1444 + (hotStartTimes.isEmpty() ? MONITORING_MODE_EMPTY_TEXT 1445 : calculateAverage(hotStartTimes))); 1446 } 1447 1448 int size = mInfos.size(); 1449 for (int i = 0; i < size; i++) { 1450 mInfos.get(i).dump(pw, prefix + " ", "#" + i, sdf); 1451 } 1452 } 1453 calculateAverage(List<Long> vals)1454 private long calculateAverage(List<Long> vals) { 1455 return (long) vals.stream().mapToDouble(a -> a).average().orElse(0.0); 1456 } 1457 1458 @GuardedBy("mLock") writeToProto(ProtoOutputStream proto, long fieldId, ByteArrayOutputStream byteArrayOutputStream, ObjectOutputStream objectOutputStream, TypedXmlSerializer typedXmlSerializer)1459 void writeToProto(ProtoOutputStream proto, long fieldId, 1460 ByteArrayOutputStream byteArrayOutputStream, ObjectOutputStream objectOutputStream, 1461 TypedXmlSerializer typedXmlSerializer) throws IOException { 1462 long token = proto.start(fieldId); 1463 proto.write(AppsStartInfoProto.Package.User.UID, mUid); 1464 int size = mInfos.size(); 1465 if (android.app.Flags.appStartInfoCleanupOldRecords()) { 1466 long removeOlderThan = getMonotonicTimeMs() - APP_START_INFO_HISTORY_LENGTH_MS; 1467 // Iterate backwards so we can remove old records as we go. 1468 for (int i = size - 1; i >= 0; i--) { 1469 if (mInfos.get(i).getMonotonicCreationTimeMs() < removeOlderThan) { 1470 // Remove the record. 1471 mInfos.remove(i); 1472 } else { 1473 mInfos.get(i).writeToProto( 1474 proto, AppsStartInfoProto.Package.User.APP_START_INFO, 1475 byteArrayOutputStream, objectOutputStream, typedXmlSerializer); 1476 } 1477 } 1478 } else { 1479 for (int i = 0; i < size; i++) { 1480 mInfos.get(i).writeToProto( 1481 proto, AppsStartInfoProto.Package.User.APP_START_INFO, 1482 byteArrayOutputStream, objectOutputStream, typedXmlSerializer); 1483 } 1484 } 1485 proto.write(AppsStartInfoProto.Package.User.MONITORING_ENABLED, mMonitoringModeEnabled); 1486 proto.end(token); 1487 } 1488 readFromProto(ProtoInputStream proto, long fieldId, String packageName, ByteArrayInputStream byteArrayInputStream, ObjectInputStream objectInputStream, TypedXmlPullParser typedXmlPullParser)1489 int readFromProto(ProtoInputStream proto, long fieldId, String packageName, 1490 ByteArrayInputStream byteArrayInputStream, ObjectInputStream objectInputStream, 1491 TypedXmlPullParser typedXmlPullParser) 1492 throws IOException, WireTypeMismatchException, ClassNotFoundException { 1493 long token = proto.start(fieldId); 1494 for (int next = proto.nextField(); 1495 next != ProtoInputStream.NO_MORE_FIELDS; 1496 next = proto.nextField()) { 1497 switch (next) { 1498 case (int) AppsStartInfoProto.Package.User.UID: 1499 mUid = proto.readInt(AppsStartInfoProto.Package.User.UID); 1500 break; 1501 case (int) AppsStartInfoProto.Package.User.APP_START_INFO: 1502 // Create record with monotonic time 0 in case the persisted record does not 1503 // have a create time. 1504 ApplicationStartInfo info = new ApplicationStartInfo(0); 1505 info.readFromProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO, 1506 byteArrayInputStream, objectInputStream, typedXmlPullParser); 1507 info.setPackageName(packageName); 1508 if (android.app.Flags.appStartInfoKeepRecordsSorted()) { 1509 // Since the writes are done from oldest to newest, each additional 1510 // record will be newer than the previous so use addFirst. 1511 mInfos.addFirst(info); 1512 } else { 1513 mInfos.add(info); 1514 } 1515 break; 1516 case (int) AppsStartInfoProto.Package.User.MONITORING_ENABLED: 1517 mMonitoringModeEnabled = proto.readBoolean( 1518 AppsStartInfoProto.Package.User.MONITORING_ENABLED); 1519 break; 1520 } 1521 } 1522 proto.end(token); 1523 return mUid; 1524 } 1525 } 1526 } 1527