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