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