diff options
| author | 2023-02-01 16:54:00 +0000 | |
|---|---|---|
| committer | 2023-10-11 20:31:55 +0000 | |
| commit | 0afc60f7bbde247f8e365fb438f2723c6821cd0d (patch) | |
| tree | 97752c7209d6e267777e3ca2b05cd3c77171b4e3 | |
| parent | a452e0bde1fc96da5aa2bc4e45443139c0da0058 (diff) | |
Add AppStartInfo object handling
Adds tracker that handles objects (recording, persistence, access) and hooks it up.
Test: make, flash on device
adb shell dumpsys activity start-info [package-name]
Bug: 247814855
Change-Id: Ie9face4b6e5be1a9c08f7a4ceaf52b9df6516a0f
7 files changed, 1232 insertions, 2 deletions
diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java index 37111e931d71..c8317c8faad5 100644 --- a/core/java/android/app/ApplicationStartInfo.java +++ b/core/java/android/app/ApplicationStartInfo.java @@ -27,7 +27,15 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; import android.util.ArrayMap; - +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; +import android.util.proto.WireTypeMismatchException; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -608,6 +616,103 @@ public final class ApplicationStartInfo implements Parcelable { } }; + /** + * Write to a protocol buffer output stream. Protocol buffer message definition at {@link + * android.app.ApplicationStartInfoProto} + * + * @param proto Stream to write the ApplicationStartInfo object to. + * @param fieldId Field Id of the ApplicationStartInfo as defined in the parent message + * @hide + */ + public void writeToProto(ProtoOutputStream proto, long fieldId) throws IOException { + final long token = proto.start(fieldId); + proto.write(ApplicationStartInfoProto.PID, mPid); + proto.write(ApplicationStartInfoProto.REAL_UID, mRealUid); + proto.write(ApplicationStartInfoProto.PACKAGE_UID, mPackageUid); + proto.write(ApplicationStartInfoProto.DEFINING_UID, mDefiningUid); + proto.write(ApplicationStartInfoProto.PROCESS_NAME, mProcessName); + proto.write(ApplicationStartInfoProto.STARTUP_STATE, mStartupState); + proto.write(ApplicationStartInfoProto.REASON, mReason); + if (mStartupTimestampsNs != null && mStartupTimestampsNs.size() > 0) { + ByteArrayOutputStream timestampsBytes = new ByteArrayOutputStream(); + ObjectOutputStream timestampsOut = new ObjectOutputStream(timestampsBytes); + timestampsOut.writeObject(mStartupTimestampsNs); + proto.write(ApplicationStartInfoProto.STARTUP_TIMESTAMPS, + timestampsBytes.toByteArray()); + } + proto.write(ApplicationStartInfoProto.START_TYPE, mStartType); + if (mStartIntent != null) { + Parcel parcel = Parcel.obtain(); + mStartIntent.writeToParcel(parcel, 0); + proto.write(ApplicationStartInfoProto.START_INTENT, parcel.marshall()); + parcel.recycle(); + } + proto.write(ApplicationStartInfoProto.LAUNCH_MODE, mLaunchMode); + proto.end(token); + } + + /** + * Read from a protocol buffer input stream. Protocol buffer message definition at {@link + * android.app.ApplicationStartInfoProto} + * + * @param proto Stream to read the ApplicationStartInfo object from. + * @param fieldId Field Id of the ApplicationStartInfo as defined in the parent message + * @hide + */ + public void readFromProto(ProtoInputStream proto, long fieldId) + throws IOException, WireTypeMismatchException, ClassNotFoundException { + final long token = proto.start(fieldId); + while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (proto.getFieldNumber()) { + case (int) ApplicationStartInfoProto.PID: + mPid = proto.readInt(ApplicationStartInfoProto.PID); + break; + case (int) ApplicationStartInfoProto.REAL_UID: + mRealUid = proto.readInt(ApplicationStartInfoProto.REAL_UID); + break; + case (int) ApplicationStartInfoProto.PACKAGE_UID: + mPackageUid = proto.readInt(ApplicationStartInfoProto.PACKAGE_UID); + break; + case (int) ApplicationStartInfoProto.DEFINING_UID: + mDefiningUid = proto.readInt(ApplicationStartInfoProto.DEFINING_UID); + break; + case (int) ApplicationStartInfoProto.PROCESS_NAME: + mProcessName = intern(proto.readString(ApplicationStartInfoProto.PROCESS_NAME)); + break; + case (int) ApplicationStartInfoProto.STARTUP_STATE: + mStartupState = proto.readInt(ApplicationStartInfoProto.STARTUP_STATE); + break; + case (int) ApplicationStartInfoProto.REASON: + mReason = proto.readInt(ApplicationStartInfoProto.REASON); + break; + case (int) ApplicationStartInfoProto.STARTUP_TIMESTAMPS: + ByteArrayInputStream timestampsBytes = new ByteArrayInputStream(proto.readBytes( + ApplicationStartInfoProto.STARTUP_TIMESTAMPS)); + ObjectInputStream timestampsIn = new ObjectInputStream(timestampsBytes); + mStartupTimestampsNs = (ArrayMap<Integer, Long>) timestampsIn.readObject(); + break; + case (int) ApplicationStartInfoProto.START_TYPE: + mStartType = proto.readInt(ApplicationStartInfoProto.START_TYPE); + break; + case (int) ApplicationStartInfoProto.START_INTENT: + byte[] startIntentBytes = proto.readBytes( + ApplicationStartInfoProto.START_INTENT); + if (startIntentBytes.length > 0) { + Parcel parcel = Parcel.obtain(); + parcel.unmarshall(startIntentBytes, 0, startIntentBytes.length); + parcel.setDataPosition(0); + mStartIntent = Intent.CREATOR.createFromParcel(parcel); + parcel.recycle(); + } + break; + case (int) ApplicationStartInfoProto.LAUNCH_MODE: + mLaunchMode = proto.readInt(ApplicationStartInfoProto.LAUNCH_MODE); + break; + } + } + proto.end(token); + } + /** @hide */ public void dump(@NonNull PrintWriter pw, @Nullable String prefix, @Nullable String seqSuffix, @NonNull SimpleDateFormat sdf) { diff --git a/core/proto/android/app/appstartinfo.proto b/core/proto/android/app/appstartinfo.proto new file mode 100644 index 000000000000..8c3304137904 --- /dev/null +++ b/core/proto/android/app/appstartinfo.proto @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; +option java_multiple_files = true; + +package android.app; + +import "frameworks/base/core/proto/android/privacy.proto"; +import "frameworks/proto_logging/stats/enums/app/enums.proto"; + +/** + * An android.app.ApplicationStartInfo object. + */ +message ApplicationStartInfoProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int32 pid = 1; + optional int32 real_uid = 2; + optional int32 package_uid = 3; + optional int32 defining_uid = 4; + optional string process_name = 5; + optional AppStartStartupState startup_state = 6; + optional AppStartReasonCode reason = 7; + optional bytes startup_timestamps = 8; + optional AppStartStartType start_type = 9; + optional bytes start_intent = 10; + optional AppStartLaunchMode launch_mode = 11; +} diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto index 025a57d0e334..c5889ba78159 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -20,6 +20,7 @@ package com.android.server.am; import "frameworks/base/core/proto/android/app/activitymanager.proto"; import "frameworks/base/core/proto/android/app/appexitinfo.proto"; +import "frameworks/base/core/proto/android/app/appstartinfo.proto"; import "frameworks/base/core/proto/android/app/notification.proto"; import "frameworks/base/core/proto/android/app/profilerinfo.proto"; import "frameworks/base/core/proto/android/content/component_name.proto"; @@ -1041,3 +1042,23 @@ message AppsExitInfoProto { } repeated Package packages = 2; } + +// sync with com.android.server.am.am.ProcessList.java +message AppsStartInfoProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int64 last_update_timestamp = 1; + message Package { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string package_name = 1; + message User { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int32 uid = 1; + repeated .android.app.ApplicationStartInfoProto app_start_info = 2; + } + repeated User users = 2; + } + repeated Package packages = 2; +} diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 19879db1d22e..684f45e248c1 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -9638,9 +9638,29 @@ public class ActivityManagerService extends IActivityManager.Stub public ParceledListSlice<ApplicationStartInfo> getHistoricalProcessStartReasons( String packageName, int maxNum, int userId) { enforceNotIsolatedCaller("getHistoricalProcessStartReasons"); + // For the simplification, we don't support USER_ALL nor USER_CURRENT here. + if (userId == UserHandle.USER_ALL || userId == UserHandle.USER_CURRENT) { + throw new IllegalArgumentException("Unsupported userId"); + } - final ArrayList<ApplicationStartInfo> results = new ArrayList<ApplicationStartInfo>(); + final int callingPid = Binder.getCallingPid(); + final int callingUid = Binder.getCallingUid(); + mUserController.handleIncomingUser(callingPid, callingUid, userId, true, ALLOW_NON_FULL, + "getHistoricalProcessStartReasons", null); + final ArrayList<ApplicationStartInfo> results = new ArrayList<ApplicationStartInfo>(); + if (!TextUtils.isEmpty(packageName)) { + final int uid = enforceDumpPermissionForPackage(packageName, userId, callingUid, + "getHistoricalProcessStartReasons"); + if (uid != INVALID_UID) { + mProcessList.mAppStartInfoTracker.getStartInfo( + packageName, userId, callingPid, maxNum, results); + } + } else { + // If no package name is given, use the caller's uid as the filter uid. + mProcessList.mAppStartInfoTracker.getStartInfo( + packageName, callingUid, callingPid, maxNum, results); + } return new ParceledListSlice<ApplicationStartInfo>(results); } @@ -9649,6 +9669,14 @@ public class ActivityManagerService extends IActivityManager.Stub public void setApplicationStartInfoCompleteListener( IApplicationStartInfoCompleteListener listener, int userId) { enforceNotIsolatedCaller("setApplicationStartInfoCompleteListener"); + + // For the simplification, we don't support USER_ALL nor USER_CURRENT here. + if (userId == UserHandle.USER_ALL || userId == UserHandle.USER_CURRENT) { + throw new IllegalArgumentException("Unsupported userId"); + } + + final int callingUid = Binder.getCallingUid(); + mProcessList.mAppStartInfoTracker.addStartInfoCompleteListener(listener, callingUid); } @@ -9662,6 +9690,7 @@ public class ActivityManagerService extends IActivityManager.Stub } final int callingUid = Binder.getCallingUid(); + mProcessList.mAppStartInfoTracker.clearStartInfoCompleteListener(callingUid, true); } @Override @@ -9963,6 +9992,8 @@ public class ActivityManagerService extends IActivityManager.Stub pw.println(); if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); + mProcessList.mAppStartInfoTracker.dumpHistoryProcessStartInfo(pw, dumpPackage); + pw.println("-------------------------------------------------------------------------------"); mProcessList.mAppExitInfoTracker.dumpHistoryProcessExitInfo(pw, dumpPackage); } if (dumpPackage == null) { @@ -10359,6 +10390,12 @@ public class ActivityManagerService extends IActivityManager.Stub LockGuard.dump(fd, pw, args); } else if ("users".equals(cmd)) { dumpUsers(pw); + } else if ("start-info".equals(cmd)) { + if (opti < args.length) { + dumpPackage = args[opti]; + opti++; + } + mProcessList.mAppStartInfoTracker.dumpHistoryProcessStartInfo(pw, dumpPackage); } else if ("exit-info".equals(cmd)) { if (opti < args.length) { dumpPackage = args[opti]; diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index a057f32386f6..69bf612f3e54 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -272,6 +272,8 @@ final class ActivityManagerShellCommand extends ShellCommand { return runSetWatchHeap(pw); case "clear-watch-heap": return runClearWatchHeap(pw); + case "clear-start-info": + return runClearStartInfo(pw); case "clear-exit-info": return runClearExitInfo(pw); case "bug-report": @@ -1339,6 +1341,31 @@ final class ActivityManagerShellCommand extends ShellCommand { return 0; } + int runClearStartInfo(PrintWriter pw) throws RemoteException { + mInternal.enforceCallingPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS, + "runClearStartInfo()"); + String opt; + int userId = UserHandle.USER_CURRENT; + String packageName = null; + while ((opt = getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + packageName = opt; + } + } + if (userId == UserHandle.USER_CURRENT) { + UserInfo user = mInterface.getCurrentUser(); + if (user == null) { + return -1; + } + userId = user.id; + } + mInternal.mProcessList.mAppStartInfoTracker + .clearHistoryProcessStartInfo(packageName, userId); + return 0; + } + int runClearExitInfo(PrintWriter pw) throws RemoteException { mInternal.enforceCallingPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS, "runClearExitInfo()"); @@ -4090,6 +4117,7 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" s[ervices] [COMP_SPEC ...]: service state"); pw.println(" allowed-associations: current package association restrictions"); pw.println(" as[sociations]: tracked app associations"); + pw.println(" start-info [PACKAGE_NAME]: historical process start information"); pw.println(" exit-info [PACKAGE_NAME]: historical process exit information"); pw.println(" lmk: stats on low memory killer"); pw.println(" lru: raw LRU process list"); @@ -4265,6 +4293,8 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" above <HEAP-LIMIT> then a heap dump is collected for the user to report."); pw.println(" clear-watch-heap"); pw.println(" Clear the previously set-watch-heap."); + pw.println(" clear-start-info [--user <USER_ID> | all | current] [package]"); + pw.println(" Clear the process start-info for given package"); pw.println(" clear-exit-info [--user <USER_ID> | all | current] [package]"); pw.println(" Clear the process exit-info for given package"); pw.println(" bug-report [--progress | --telephony]"); diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java new file mode 100644 index 000000000000..edca74fae0e4 --- /dev/null +++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java @@ -0,0 +1,989 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static android.app.ApplicationStartInfo.START_TIMESTAMP_LAUNCH; +import static android.os.Process.THREAD_PRIORITY_BACKGROUND; + +import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; + +import android.app.ActivityOptions; +import android.app.ApplicationStartInfo; +import android.app.Flags; +import android.app.IApplicationStartInfoCompleteListener; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.icu.text.SimpleDateFormat; +import android.os.Binder; +import android.os.FileUtils; +import android.os.Handler; +import android.os.IBinder.DeathRecipient; +import android.os.RemoteException; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.AtomicFile; +import android.util.Slog; +import android.util.SparseArray; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; +import android.util.proto.WireTypeMismatchException; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.app.ProcessMap; +import com.android.server.IoThread; +import com.android.server.ServiceThread; +import com.android.server.SystemServiceManager; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiFunction; + +/** A class to manage all the {@link android.app.ApplicationStartInfo} records. */ +public final class AppStartInfoTracker { + private static final String TAG = TAG_WITH_CLASS_NAME ? "AppStartInfoTracker" : TAG_AM; + + /** Interval of persisting the app start info to persistent storage. */ + private static final long APP_START_INFO_PERSIST_INTERVAL = TimeUnit.MINUTES.toMillis(30); + + /** These are actions that the forEach* should take after each iteration */ + private static final int FOREACH_ACTION_NONE = 0; + private static final int FOREACH_ACTION_REMOVE_ITEM = 1; + private static final int FOREACH_ACTION_STOP_ITERATION = 2; + + private static final int APP_START_INFO_HISTORY_LIST_SIZE = 16; + + @VisibleForTesting static final String APP_START_STORE_DIR = "procstartstore"; + + @VisibleForTesting static final String APP_START_INFO_FILE = "procstartinfo"; + + private final Object mLock = new Object(); + + private boolean mEnabled = false; + + /** Initialized in {@link #init} and read-only after that. */ + private ActivityManagerService mService; + + /** Initialized in {@link #init} and read-only after that. */ + private Handler mHandler; + + /** The task to persist app process start info */ + @GuardedBy("mLock") + private Runnable mAppStartInfoPersistTask = null; + + /** + * Last time(in ms) since epoch that the app start info was persisted into persistent storage. + */ + @GuardedBy("mLock") + private long mLastAppStartInfoPersistTimestamp = 0L; + + /** + * Retention policy: keep up to X historical start info per package. + * + * <p>Initialized in {@link #init} and read-only after that. No lock is needed. + */ + private int mAppStartInfoHistoryListSize; + + @GuardedBy("mLock") + private final ProcessMap<AppStartInfoContainer> mData; + + /** UID as key. */ + @GuardedBy("mLock") + private final SparseArray<ApplicationStartInfoCompleteCallback> mCallbacks; + + /** + * Whether or not we've loaded the historical app process start info from persistent storage. + */ + @VisibleForTesting AtomicBoolean mAppStartInfoLoaded = new AtomicBoolean(); + + /** Temporary list being used to filter/sort intermediate results in {@link #getStartInfo}. */ + @GuardedBy("mLock") + final ArrayList<ApplicationStartInfo> mTmpStartInfoList = new ArrayList<>(); + + /** + * The path to the directory which includes the historical proc start info file as specified in + * {@link #mProcStartInfoFile}. + */ + @VisibleForTesting File mProcStartStoreDir; + + /** The path to the historical proc start info file, persisted in the storage. */ + @VisibleForTesting File mProcStartInfoFile; + + AppStartInfoTracker() { + mCallbacks = new SparseArray<>(); + mData = new ProcessMap<AppStartInfoContainer>(); + } + + void init(ActivityManagerService service) { + mService = service; + + ServiceThread thread = + new ServiceThread(TAG + ":handler", THREAD_PRIORITY_BACKGROUND, true /* allowIo */); + thread.start(); + mHandler = new Handler(thread.getLooper()); + + mProcStartStoreDir = new File(SystemServiceManager.ensureSystemDir(), APP_START_STORE_DIR); + if (!FileUtils.createDir(mProcStartStoreDir)) { + Slog.e(TAG, "Unable to create " + mProcStartStoreDir); + return; + } + mProcStartInfoFile = new File(mProcStartStoreDir, APP_START_INFO_FILE); + + mAppStartInfoHistoryListSize = APP_START_INFO_HISTORY_LIST_SIZE; + } + + void onSystemReady() { + mEnabled = Flags.appStartInfo(); + if (!mEnabled) { + return; + } + + registerForUserRemoval(); + registerForPackageRemoval(); + IoThread.getHandler().post(() -> { + loadExistingProcessStartInfo(); + }); + } + + void handleProcessColdStarted(long startTimeNs, HostingRecord hostingRecord, + ProcessRecord app) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + ApplicationStartInfo start = new ApplicationStartInfo(); + addBaseFieldsFromProcessRecord(start, app); + start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); + start.addStartupTimestamp( + ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs); + start.addStartupTimestamp( + ApplicationStartInfo.START_TIMESTAMP_FORK, app.getStartElapsedTime()); + start.setStartType(ApplicationStartInfo.START_TYPE_COLD); + start.setReason(ApplicationStartInfo.START_REASON_OTHER); + addStartInfoLocked(start); + } + } + + public void handleProcessActivityWarmOrHotStarted(long startTimeNs, + ActivityOptions activityOptions, Intent intent) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + ApplicationStartInfo start = new ApplicationStartInfo(); + start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); + start.addStartupTimestamp( + ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs); + start.setIntent(intent); + start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER); + if (activityOptions != null) { + start.setProcessName(activityOptions.getPackageName()); + } + start.setStartType(ApplicationStartInfo.START_TYPE_WARM); + if (intent != null && intent.getCategories() != null + && intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) { + start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER); + } else { + start.setReason(ApplicationStartInfo.START_REASON_START_ACTIVITY); + } + addStartInfoLocked(start); + } + } + + public void handleProcessActivityStartedFromRecents(long startTimeNs, + ActivityOptions activityOptions) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + ApplicationStartInfo start = new ApplicationStartInfo(); + start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); + start.addStartupTimestamp( + ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs); + if (activityOptions != null) { + start.setIntent(activityOptions.getResultData()); + start.setProcessName(activityOptions.getPackageName()); + } + start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER_RECENTS); + start.setStartType(ApplicationStartInfo.START_TYPE_WARM); + addStartInfoLocked(start); + } + } + + public void handleProcessServiceStart(long startTimeNs, ProcessRecord app, + ServiceRecord serviceRecord, HostingRecord hostingRecord, boolean cold) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + ApplicationStartInfo start = new ApplicationStartInfo(); + addBaseFieldsFromProcessRecord(start, app); + start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); + start.addStartupTimestamp( + ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs); + start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD + : ApplicationStartInfo.START_TYPE_WARM); + start.setReason(serviceRecord.permission != null + && serviceRecord.permission.contains("android.permission.BIND_JOB_SERVICE") + ? ApplicationStartInfo.START_REASON_JOB + : ApplicationStartInfo.START_REASON_SERVICE); + start.setIntent(serviceRecord.intent.getIntent()); + addStartInfoLocked(start); + } + } + + public void handleProcessBroadcastStart(long startTimeNs, ProcessRecord app, + BroadcastRecord broadcast, boolean cold) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + ApplicationStartInfo start = new ApplicationStartInfo(); + addBaseFieldsFromProcessRecord(start, app); + start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); + start.addStartupTimestamp( + ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs); + start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD + : ApplicationStartInfo.START_TYPE_WARM); + if (broadcast == null) { + start.setReason(ApplicationStartInfo.START_REASON_BROADCAST); + } else if (broadcast.alarm) { + start.setReason(ApplicationStartInfo.START_REASON_ALARM); + } else if (broadcast.pushMessage || broadcast.pushMessageOverQuota) { + start.setReason(ApplicationStartInfo.START_REASON_PUSH); + } else if (Intent.ACTION_BOOT_COMPLETED.equals(broadcast.intent.getAction())) { + start.setReason(ApplicationStartInfo.START_REASON_BOOT_COMPLETE); + } else { + start.setReason(ApplicationStartInfo.START_REASON_BROADCAST); + } + start.setIntent(broadcast != null ? broadcast.intent : null); + addStartInfoLocked(start); + } + } + + public void handleProcessContentProviderStart(long startTimeNs, ProcessRecord app, + boolean cold) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + ApplicationStartInfo start = new ApplicationStartInfo(); + addBaseFieldsFromProcessRecord(start, app); + start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); + start.addStartupTimestamp( + ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs); + start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD + : ApplicationStartInfo.START_TYPE_WARM); + start.setReason(ApplicationStartInfo.START_REASON_CONTENT_PROVIDER); + addStartInfoLocked(start); + } + } + + public void handleProcessBackupStart(long startTimeNs, ProcessRecord app, + BackupRecord backupRecord, boolean cold) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + ApplicationStartInfo start = new ApplicationStartInfo(); + addBaseFieldsFromProcessRecord(start, app); + start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); + start.addStartupTimestamp( + ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs); + start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD + : ApplicationStartInfo.START_TYPE_WARM); + start.setReason(ApplicationStartInfo.START_REASON_BACKUP); + addStartInfoLocked(start); + } + } + + private void addBaseFieldsFromProcessRecord(ApplicationStartInfo start, ProcessRecord app) { + if (app == null) { + return; + } + final int definingUid = app.getHostingRecord() != null + ? app.getHostingRecord().getDefiningUid() : 0; + start.setPid(app.getPid()); + start.setRealUid(app.uid); + start.setPackageUid(app.info.uid); + start.setDefiningUid(definingUid > 0 ? definingUid : app.info.uid); + start.setProcessName(app.processName); + } + + void reportApplicationOnCreateTimeNanos(ProcessRecord app, long timeNs) { + if (!mEnabled) { + return; + } + addTimestampToStart(app, timeNs, + ApplicationStartInfo.START_TIMESTAMP_APPLICATION_ONCREATE); + } + + void reportBindApplicationTimeNanos(ProcessRecord app, long timeNs) { + addTimestampToStart(app, timeNs, + ApplicationStartInfo.START_TIMESTAMP_BIND_APPLICATION); + } + + void reportFirstFrameTimeNanos(ProcessRecord app, long timeNs) { + if (!mEnabled) { + return; + } + addTimestampToStart(app, timeNs, + ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME); + } + + void reportFullyDrawnTimeNanos(ProcessRecord app, long timeNs) { + if (!mEnabled) { + return; + } + addTimestampToStart(app, timeNs, + ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN); + } + + void reportFullyDrawnTimeNanos(String processName, int uid, long timeNs) { + if (!mEnabled) { + return; + } + addTimestampToStart(processName, uid, timeNs, + ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN); + } + + private void addTimestampToStart(ProcessRecord app, long timeNs, int key) { + addTimestampToStart(app.processName, app.uid, timeNs, key); + } + + private void addTimestampToStart(String processName, int uid, long timeNs, int key) { + synchronized (mLock) { + AppStartInfoContainer container = mData.get(processName, uid); + if (container == null) { + // Record was not created, discard new data. + return; + } + container.addTimestampToStartLocked(key, timeNs); + } + } + + @GuardedBy("mLock") + private ApplicationStartInfo addStartInfoLocked(ApplicationStartInfo raw) { + if (!mAppStartInfoLoaded.get()) { + //records added before initial load from storage will be lost. + Slog.w(TAG, "Skipping saving the start info due to ongoing loading from storage"); + return null; + } + + final ApplicationStartInfo info = new ApplicationStartInfo(raw); + + AppStartInfoContainer container = mData.get(raw.getProcessName(), raw.getRealUid()); + if (container == null) { + container = new AppStartInfoContainer(mAppStartInfoHistoryListSize); + container.mUid = info.getRealUid(); + mData.put(raw.getProcessName(), raw.getRealUid(), container); + } + container.addStartInfoLocked(info); + + schedulePersistProcessStartInfo(false); + + return info; + } + + /** + * Called whenever data is added to a {@link ApplicationStartInfo} object. Checks for + * completeness and triggers callback if a callback has been registered and the object + * is complete. + */ + private void checkCompletenessAndCallback(ApplicationStartInfo startInfo) { + synchronized (mLock) { + if (startInfo.getStartupState() + == ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN) { + ApplicationStartInfoCompleteCallback callback = + mCallbacks.get(startInfo.getRealUid()); + if (callback != null) { + callback.onApplicationStartInfoComplete(startInfo); + } + } + } + } + + void getStartInfo(String packageName, int filterUid, int filterPid, + int maxNum, ArrayList<ApplicationStartInfo> results) { + if (!mEnabled) { + return; + } + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + boolean emptyPackageName = TextUtils.isEmpty(packageName); + if (!emptyPackageName) { + // fast path + AppStartInfoContainer container = mData.get(packageName, filterUid); + if (container != null) { + container.getStartInfoLocked(filterPid, maxNum, results); + } + } else { + // slow path + final ArrayList<ApplicationStartInfo> list = mTmpStartInfoList; + list.clear(); + // get all packages + forEachPackageLocked( + (name, records) -> { + AppStartInfoContainer container = records.get(filterUid); + if (container != null) { + list.addAll(container.mInfos); + } + return AppStartInfoTracker.FOREACH_ACTION_NONE; + }); + + Collections.sort( + list, (a, b) -> + Long.compare(getStartTimestamp(b), getStartTimestamp(a))); + int size = list.size(); + if (maxNum > 0) { + size = Math.min(size, maxNum); + } + for (int i = 0; i < size; i++) { + results.add(list.get(i)); + } + list.clear(); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + final class ApplicationStartInfoCompleteCallback implements DeathRecipient { + private final int mUid; + private final IApplicationStartInfoCompleteListener mCallback; + + ApplicationStartInfoCompleteCallback(IApplicationStartInfoCompleteListener callback, + int uid) { + mCallback = callback; + mUid = uid; + try { + mCallback.asBinder().linkToDeath(this, 0); + } catch (RemoteException e) { + /*ignored*/ + } + } + + void onApplicationStartInfoComplete(ApplicationStartInfo startInfo) { + try { + mCallback.onApplicationStartInfoComplete(startInfo); + } catch (RemoteException e) { + /*ignored*/ + } + clearStartInfoCompleteListener(mUid, true); + } + + void unlinkToDeath() { + mCallback.asBinder().unlinkToDeath(this, 0); + } + + @Override + public void binderDied() { + clearStartInfoCompleteListener(mUid, false); + } + } + + void addStartInfoCompleteListener( + final IApplicationStartInfoCompleteListener listener, final int uid) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + mCallbacks.put(uid, new ApplicationStartInfoCompleteCallback(listener, uid)); + } + } + + void clearStartInfoCompleteListener(final int uid, boolean unlinkDeathRecipient) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + if (unlinkDeathRecipient) { + ApplicationStartInfoCompleteCallback callback = mCallbacks.get(uid); + if (callback != null) { + callback.unlinkToDeath(); + } + } + mCallbacks.remove(uid); + } + } + + @GuardedBy("mLock") + private void forEachPackageLocked( + BiFunction<String, SparseArray<AppStartInfoContainer>, Integer> callback) { + if (callback != null) { + ArrayMap<String, SparseArray<AppStartInfoContainer>> map = mData.getMap(); + for (int i = map.size() - 1; i >= 0; i--) { + switch (callback.apply(map.keyAt(i), map.valueAt(i))) { + case FOREACH_ACTION_REMOVE_ITEM: + map.removeAt(i); + break; + case FOREACH_ACTION_STOP_ITERATION: + i = 0; + break; + case FOREACH_ACTION_NONE: + default: + break; + } + } + } + } + + @GuardedBy("mLock") + private void removePackageLocked(String packageName, int uid, boolean removeUid, int userId) { + ArrayMap<String, SparseArray<AppStartInfoContainer>> map = mData.getMap(); + SparseArray<AppStartInfoContainer> array = map.get(packageName); + if (array == null) { + return; + } + if (userId == UserHandle.USER_ALL) { + mData.getMap().remove(packageName); + } else { + for (int i = array.size() - 1; i >= 0; i--) { + if (UserHandle.getUserId(array.keyAt(i)) == userId) { + array.removeAt(i); + break; + } + } + if (array.size() == 0) { + map.remove(packageName); + } + } + } + + @GuardedBy("mLock") + private void removeByUserIdLocked(final int userId) { + if (userId == UserHandle.USER_ALL) { + mData.getMap().clear(); + return; + } + forEachPackageLocked( + (packageName, records) -> { + for (int i = records.size() - 1; i >= 0; i--) { + if (UserHandle.getUserId(records.keyAt(i)) == userId) { + records.removeAt(i); + break; + } + } + return records.size() == 0 ? FOREACH_ACTION_REMOVE_ITEM : FOREACH_ACTION_NONE; + }); + } + + @VisibleForTesting + void onUserRemoved(int userId) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + removeByUserIdLocked(userId); + schedulePersistProcessStartInfo(true); + } + } + + @VisibleForTesting + void onPackageRemoved(String packageName, int uid, boolean allUsers) { + if (!mEnabled) { + return; + } + if (packageName != null) { + final boolean removeUid = + TextUtils.isEmpty(mService.mPackageManagerInt.getNameForUid(uid)); + synchronized (mLock) { + removePackageLocked( + packageName, + uid, + removeUid, + allUsers ? UserHandle.USER_ALL : UserHandle.getUserId(uid)); + schedulePersistProcessStartInfo(true); + } + } + } + + private void registerForUserRemoval() { + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_USER_REMOVED); + mService.mContext.registerReceiverForAllUsers( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (userId < 1) return; + onUserRemoved(userId); + } + }, + filter, + null, + mHandler); + } + + private void registerForPackageRemoval() { + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addDataScheme("package"); + mService.mContext.registerReceiverForAllUsers( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); + if (replacing) { + return; + } + int uid = intent.getIntExtra(Intent.EXTRA_UID, UserHandle.USER_NULL); + boolean allUsers = + intent.getBooleanExtra(Intent.EXTRA_REMOVED_FOR_ALL_USERS, false); + onPackageRemoved(intent.getData().getSchemeSpecificPart(), uid, allUsers); + } + }, + filter, + null, + mHandler); + } + + /** + * Load the existing {@link android.app.ApplicationStartInfo} records from persistent storage. + */ + @VisibleForTesting + void loadExistingProcessStartInfo() { + if (!mEnabled) { + return; + } + if (!mProcStartInfoFile.canRead()) { + // If file can't be read, mark complete so we can begin accepting new records. + mAppStartInfoLoaded.set(true); + return; + } + + FileInputStream fin = null; + try { + AtomicFile af = new AtomicFile(mProcStartInfoFile); + fin = af.openRead(); + ProtoInputStream proto = new ProtoInputStream(fin); + for (int next = proto.nextField(); + next != ProtoInputStream.NO_MORE_FIELDS; + next = proto.nextField()) { + switch (next) { + case (int) AppsStartInfoProto.LAST_UPDATE_TIMESTAMP: + synchronized (mLock) { + mLastAppStartInfoPersistTimestamp = + proto.readLong(AppsStartInfoProto.LAST_UPDATE_TIMESTAMP); + } + break; + case (int) AppsStartInfoProto.PACKAGES: + loadPackagesFromProto(proto, next); + break; + } + } + } catch (IOException | IllegalArgumentException | WireTypeMismatchException + | ClassNotFoundException e) { + Slog.w(TAG, "Error in loading historical app start info from persistent storage: " + e); + } finally { + if (fin != null) { + try { + fin.close(); + } catch (IOException e) { + } + } + } + mAppStartInfoLoaded.set(true); + } + + private void loadPackagesFromProto(ProtoInputStream proto, long fieldId) + throws IOException, WireTypeMismatchException, ClassNotFoundException { + long token = proto.start(fieldId); + String pkgName = ""; + for (int next = proto.nextField(); + next != ProtoInputStream.NO_MORE_FIELDS; + next = proto.nextField()) { + switch (next) { + case (int) AppsStartInfoProto.Package.PACKAGE_NAME: + pkgName = proto.readString(AppsStartInfoProto.Package.PACKAGE_NAME); + break; + case (int) AppsStartInfoProto.Package.USERS: + AppStartInfoContainer container = + new AppStartInfoContainer(mAppStartInfoHistoryListSize); + int uid = container.readFromProto(proto, AppsStartInfoProto.Package.USERS); + synchronized (mLock) { + mData.put(pkgName, uid, container); + } + break; + } + } + proto.end(token); + } + + /** Persist the existing {@link android.app.ApplicationStartInfo} records to storage. */ + @VisibleForTesting + void persistProcessStartInfo() { + if (!mEnabled) { + return; + } + AtomicFile af = new AtomicFile(mProcStartInfoFile); + FileOutputStream out = null; + long now = System.currentTimeMillis(); + try { + out = af.startWrite(); + ProtoOutputStream proto = new ProtoOutputStream(out); + proto.write(AppsStartInfoProto.LAST_UPDATE_TIMESTAMP, now); + synchronized (mLock) { + forEachPackageLocked( + (packageName, records) -> { + long token = proto.start(AppsStartInfoProto.PACKAGES); + proto.write(AppsStartInfoProto.Package.PACKAGE_NAME, packageName); + int uidArraySize = records.size(); + for (int j = 0; j < uidArraySize; j++) { + try { + records.valueAt(j) + .writeToProto(proto, AppsStartInfoProto.Package.USERS); + } catch (IOException e) { + Slog.w(TAG, "Unable to write app start info into persistent" + + "storage: " + e); + } + } + proto.end(token); + return AppStartInfoTracker.FOREACH_ACTION_NONE; + }); + mLastAppStartInfoPersistTimestamp = now; + } + proto.flush(); + af.finishWrite(out); + } catch (IOException e) { + Slog.w(TAG, "Unable to write historical app start info into persistent storage: " + e); + af.failWrite(out); + } + synchronized (mLock) { + mAppStartInfoPersistTask = null; + } + } + + /** + * Schedule a task to persist the {@link android.app.ApplicationStartInfo} records to storage. + */ + @VisibleForTesting + void schedulePersistProcessStartInfo(boolean immediately) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + if (mAppStartInfoPersistTask == null || immediately) { + if (mAppStartInfoPersistTask != null) { + IoThread.getHandler().removeCallbacks(mAppStartInfoPersistTask); + } + mAppStartInfoPersistTask = this::persistProcessStartInfo; + IoThread.getHandler() + .postDelayed( + mAppStartInfoPersistTask, + immediately ? 0 : APP_START_INFO_PERSIST_INTERVAL); + } + } + } + + /** Helper function for testing only. */ + @VisibleForTesting + void clearProcessStartInfo(boolean removeFile) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + if (mAppStartInfoPersistTask != null) { + IoThread.getHandler().removeCallbacks(mAppStartInfoPersistTask); + mAppStartInfoPersistTask = null; + } + if (removeFile && mProcStartInfoFile != null) { + mProcStartInfoFile.delete(); + } + mData.getMap().clear(); + } + } + + /** + * Helper functions for shell command. + * > adb shell dumpsys activity clear-start-info [package-name] + */ + void clearHistoryProcessStartInfo(String packageName, int userId) { + if (!mEnabled) { + return; + } + Optional<Integer> appId = Optional.empty(); + if (TextUtils.isEmpty(packageName)) { + synchronized (mLock) { + removeByUserIdLocked(userId); + } + } else { + final int uid = + mService.mPackageManagerInt.getPackageUid( + packageName, PackageManager.MATCH_ALL, userId); + appId = Optional.of(UserHandle.getAppId(uid)); + synchronized (mLock) { + removePackageLocked(packageName, uid, true, userId); + } + } + schedulePersistProcessStartInfo(true); + } + + /** + * Helper functions for shell command. + * > adb shell dumpsys activity start-info [package-name] + */ + void dumpHistoryProcessStartInfo(PrintWriter pw, String packageName) { + if (!mEnabled) { + return; + } + pw.println("ACTIVITY MANAGER LRU PROCESSES (dumpsys activity start-info)"); + SimpleDateFormat sdf = new SimpleDateFormat(); + synchronized (mLock) { + pw.println("Last Timestamp of Persistence Into Persistent Storage: " + + sdf.format(new Date(mLastAppStartInfoPersistTimestamp))); + if (TextUtils.isEmpty(packageName)) { + forEachPackageLocked((name, records) -> { + dumpHistoryProcessStartInfoLocked(pw, " ", name, records, sdf); + return AppStartInfoTracker.FOREACH_ACTION_NONE; + }); + } else { + SparseArray<AppStartInfoContainer> array = mData.getMap().get(packageName); + if (array != null) { + dumpHistoryProcessStartInfoLocked(pw, " ", packageName, array, sdf); + } + } + } + } + + @GuardedBy("mLock") + private void dumpHistoryProcessStartInfoLocked(PrintWriter pw, String prefix, + String packageName, SparseArray<AppStartInfoContainer> array, + SimpleDateFormat sdf) { + pw.println(prefix + "package: " + packageName); + int size = array.size(); + for (int i = 0; i < size; i++) { + pw.println(prefix + " Historical Process Start for userId=" + array.keyAt(i)); + array.valueAt(i).dumpLocked(pw, prefix + " ", sdf); + } + } + + /** Convenience method to obtain timestamp of beginning of start.*/ + private static long getStartTimestamp(ApplicationStartInfo startInfo) { + return startInfo.getStartupTimestamps().get(START_TIMESTAMP_LAUNCH); + } + + /** A container class of (@link android.app.ApplicationStartInfo) */ + final class AppStartInfoContainer { + private List<ApplicationStartInfo> mInfos; // Always kept sorted by first timestamp. + private int mMaxCapacity; + private int mUid; + + AppStartInfoContainer(final int maxCapacity) { + mInfos = new ArrayList<ApplicationStartInfo>(); + mMaxCapacity = maxCapacity; + } + + @GuardedBy("mLock") + void getStartInfoLocked( + final int filterPid, final int maxNum, ArrayList<ApplicationStartInfo> results) { + results.addAll(mInfos.size() <= maxNum ? 0 : mInfos.size() - maxNum, mInfos); + } + + @GuardedBy("mLock") + void addStartInfoLocked(ApplicationStartInfo info) { + int size = mInfos.size(); + if (size >= mMaxCapacity) { + // Remove oldest record if size is over max capacity. + int oldestIndex = -1; + long oldestTimeStamp = Long.MAX_VALUE; + for (int i = 0; i < size; i++) { + ApplicationStartInfo startInfo = mInfos.get(i); + if (getStartTimestamp(startInfo) < oldestTimeStamp) { + oldestTimeStamp = getStartTimestamp(startInfo); + oldestIndex = i; + } + } + if (oldestIndex >= 0) { + mInfos.remove(oldestIndex); + } + mInfos.remove(0); + } + mInfos.add(info); + Collections.sort(mInfos, (a, b) -> + Long.compare(getStartTimestamp(b), getStartTimestamp(a))); + } + + @GuardedBy("mLock") + void addTimestampToStartLocked(int key, long timestampNs) { + int index = mInfos.size() - 1; + int startupState = mInfos.get(index).getStartupState(); + if (startupState == ApplicationStartInfo.STARTUP_STATE_STARTED + || key == ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN) { + mInfos.get(index).addStartupTimestamp(key, timestampNs); + } + } + + @GuardedBy("mLock") + void dumpLocked(PrintWriter pw, String prefix, SimpleDateFormat sdf) { + int size = mInfos.size(); + for (int i = 0; i < size; i++) { + mInfos.get(i).dump(pw, prefix + " ", "#" + i, sdf); + } + } + + @GuardedBy("mLock") + void writeToProto(ProtoOutputStream proto, long fieldId) throws IOException { + long token = proto.start(fieldId); + proto.write(AppsStartInfoProto.Package.User.UID, mUid); + int size = mInfos.size(); + for (int i = 0; i < size; i++) { + mInfos.get(i) + .writeToProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO); + } + proto.end(token); + } + + int readFromProto(ProtoInputStream proto, long fieldId) + throws IOException, WireTypeMismatchException, ClassNotFoundException { + long token = proto.start(fieldId); + for (int next = proto.nextField(); + next != ProtoInputStream.NO_MORE_FIELDS; + next = proto.nextField()) { + switch (next) { + case (int) AppsStartInfoProto.Package.User.UID: + mUid = proto.readInt(AppsStartInfoProto.Package.User.UID); + break; + case (int) AppsStartInfoProto.Package.User.APP_START_INFO: + ApplicationStartInfo info = new ApplicationStartInfo(); + info.readFromProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO); + mInfos.add(info); + break; + } + } + proto.end(token); + return mUid; + } + } +} diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 4572766371ec..cf209502d718 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -494,6 +494,10 @@ public final class ProcessList { @GuardedBy("mService") final ProcessMap<AppZygote> mAppZygotes = new ProcessMap<AppZygote>(); + /** Manages the {@link android.app.ApplicationStartInfo} records. */ + @GuardedBy("mAppStartInfoTracker") + final AppStartInfoTracker mAppStartInfoTracker = new AppStartInfoTracker(); + /** * The currently running SDK sandbox processes for a uid. */ @@ -954,12 +958,14 @@ public final class ProcessList { mSystemServerSocketForZygote.getFileDescriptor(), EVENT_INPUT, this::handleZygoteMessages); } + mAppStartInfoTracker.init(mService); mAppExitInfoTracker.init(mService); mImperceptibleKillRunner = new ImperceptibleKillRunner(sKillThread.getLooper()); } } void onSystemReady() { + mAppStartInfoTracker.onSystemReady(); mAppExitInfoTracker.onSystemReady(); } |