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