diff options
| author | 2024-01-16 03:28:29 +0000 | |
|---|---|---|
| committer | 2024-01-16 03:28:29 +0000 | |
| commit | fbd39b784c19586566903ff4f567c94c8f2f41ea (patch) | |
| tree | fe2bbf57f002c61c3829d97ad38c34b8d0df9c3d | |
| parent | 7ac510970ee92e6f7e31dd88a5df4a6869eac7c9 (diff) | |
| parent | d78641332036126b6633a589c5b806336fa66bd9 (diff) | |
Merge "AppStartInfo API Updates" into main
| -rw-r--r-- | core/api/current.txt | 4 | ||||
| -rw-r--r-- | core/java/android/app/ActivityManager.java | 98 | ||||
| -rw-r--r-- | core/java/android/app/ApplicationStartInfo.java | 34 | ||||
| -rw-r--r-- | core/java/android/app/IActivityManager.aidl | 5 | ||||
| -rw-r--r-- | services/core/java/com/android/server/am/ActivityManagerService.java | 8 | ||||
| -rw-r--r-- | services/core/java/com/android/server/am/AppStartInfoTracker.java | 53 |
6 files changed, 148 insertions, 54 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 46c8f8208883..4f1742dd2470 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -4625,9 +4625,9 @@ package android.app { public class ActivityManager { method public int addAppTask(@NonNull android.app.Activity, @NonNull android.content.Intent, @Nullable android.app.ActivityManager.TaskDescription, @NonNull android.graphics.Bitmap); + method @FlaggedApi("android.app.app_start_info") public void addApplicationStartInfoCompletionListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.ApplicationStartInfo>); method @FlaggedApi("android.app.app_start_info") public void addStartInfoTimestamp(@IntRange(from=android.app.ApplicationStartInfo.START_TIMESTAMP_RESERVED_RANGE_DEVELOPER_START, to=android.app.ApplicationStartInfo.START_TIMESTAMP_RESERVED_RANGE_DEVELOPER) int, long); method public void appNotResponding(@NonNull String); - method @FlaggedApi("android.app.app_start_info") public void clearApplicationStartInfoCompletionListener(); method public boolean clearApplicationUserData(); method public void clearWatchHeapLimit(); method @RequiresPermission(android.Manifest.permission.DUMP) public void dumpPackageState(java.io.FileDescriptor, String); @@ -4661,8 +4661,8 @@ package android.app { method @RequiresPermission(android.Manifest.permission.KILL_BACKGROUND_PROCESSES) public void killBackgroundProcesses(String); method @RequiresPermission(android.Manifest.permission.REORDER_TASKS) public void moveTaskToFront(int, int); method @RequiresPermission(android.Manifest.permission.REORDER_TASKS) public void moveTaskToFront(int, int, android.os.Bundle); + method @FlaggedApi("android.app.app_start_info") public void removeApplicationStartInfoCompletionListener(@NonNull java.util.function.Consumer<android.app.ApplicationStartInfo>); method @Deprecated public void restartPackage(String); - method @FlaggedApi("android.app.app_start_info") public void setApplicationStartInfoCompletionListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.ApplicationStartInfo>); method public void setProcessStateSummary(@Nullable byte[]); method public static void setVrThread(int); method public void setWatchHeapLimit(long); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 6b7f4880e2f0..ebb5ba0e7b0f 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4052,10 +4052,28 @@ public class ActivityManager { } } + private final ArrayList<AppStartInfoCallbackWrapper> mAppStartInfoCallbacks = + new ArrayList<>(); + @Nullable + private IApplicationStartInfoCompleteListener mAppStartInfoCompleteListener = null; + + private static final class AppStartInfoCallbackWrapper { + @NonNull final Executor mExecutor; + @NonNull final Consumer<ApplicationStartInfo> mListener; + + AppStartInfoCallbackWrapper(@NonNull final Executor executor, + @NonNull final Consumer<ApplicationStartInfo> listener) { + mExecutor = executor; + mListener = listener; + } + } + /** - * Sets a callback to be notified when the {@link ApplicationStartInfo} records of this startup + * Adds a callback to be notified when the {@link ApplicationStartInfo} records of this startup * are complete. * + * <p class="note"> Note: callback will be removed automatically after being triggered.</p> + * * <p class="note"> Note: callback will not wait for {@link Activity#reportFullyDrawn} to occur. * Timestamp for fully drawn may be added after callback occurs. Set callback after invoking * {@link Activity#reportFullyDrawn} if timestamp for fully drawn is required.</p> @@ -4073,33 +4091,77 @@ public class ActivityManager { * @throws IllegalArgumentException if executor or listener are null. */ @FlaggedApi(Flags.FLAG_APP_START_INFO) - public void setApplicationStartInfoCompletionListener(@NonNull final Executor executor, + public void addApplicationStartInfoCompletionListener(@NonNull final Executor executor, @NonNull final Consumer<ApplicationStartInfo> listener) { Preconditions.checkNotNull(executor, "executor cannot be null"); Preconditions.checkNotNull(listener, "listener cannot be null"); - IApplicationStartInfoCompleteListener callback = - new IApplicationStartInfoCompleteListener.Stub() { - @Override - public void onApplicationStartInfoComplete(ApplicationStartInfo applicationStartInfo) { - executor.execute(() -> listener.accept(applicationStartInfo)); + synchronized (mAppStartInfoCallbacks) { + for (int i = 0; i < mAppStartInfoCallbacks.size(); i++) { + if (listener.equals(mAppStartInfoCallbacks.get(i).mListener)) { + return; + } + } + if (mAppStartInfoCompleteListener == null) { + mAppStartInfoCompleteListener = new IApplicationStartInfoCompleteListener.Stub() { + @Override + public void onApplicationStartInfoComplete( + ApplicationStartInfo applicationStartInfo) { + synchronized (mAppStartInfoCallbacks) { + for (int i = 0; i < mAppStartInfoCallbacks.size(); i++) { + final AppStartInfoCallbackWrapper callback = + mAppStartInfoCallbacks.get(i); + callback.mExecutor.execute(() -> callback.mListener.accept( + applicationStartInfo)); + } + mAppStartInfoCallbacks.clear(); + mAppStartInfoCompleteListener = null; + } + } + }; + boolean succeeded = false; + try { + getService().addApplicationStartInfoCompleteListener( + mAppStartInfoCompleteListener, mContext.getUserId()); + succeeded = true; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + if (succeeded) { + mAppStartInfoCallbacks.add(new AppStartInfoCallbackWrapper(executor, listener)); + } else { + mAppStartInfoCompleteListener = null; + mAppStartInfoCallbacks.clear(); + } + } else { + mAppStartInfoCallbacks.add(new AppStartInfoCallbackWrapper(executor, listener)); } - }; - try { - getService().setApplicationStartInfoCompleteListener(callback, mContext.getUserId()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); } } /** - * Removes the callback set by {@link #setApplicationStartInfoCompletionListener} if there is one. + * Removes the provided callback set by {@link #addApplicationStartInfoCompletionListener}. */ @FlaggedApi(Flags.FLAG_APP_START_INFO) - public void clearApplicationStartInfoCompletionListener() { - try { - getService().clearApplicationStartInfoCompleteListener(mContext.getUserId()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + public void removeApplicationStartInfoCompletionListener( + @NonNull final Consumer<ApplicationStartInfo> listener) { + Preconditions.checkNotNull(listener, "listener cannot be null"); + synchronized (mAppStartInfoCallbacks) { + for (int i = 0; i < mAppStartInfoCallbacks.size(); i++) { + final AppStartInfoCallbackWrapper callback = mAppStartInfoCallbacks.get(i); + if (listener.equals(callback.mListener)) { + mAppStartInfoCallbacks.remove(i); + break; + } + } + if (mAppStartInfoCompleteListener != null && mAppStartInfoCallbacks.isEmpty()) { + try { + getService().removeApplicationStartInfoCompleteListener( + mAppStartInfoCompleteListener, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + mAppStartInfoCompleteListener = null; + } } } diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java index 656feb0401d6..cc3eac1bbf01 100644 --- a/core/java/android/app/ApplicationStartInfo.java +++ b/core/java/android/app/ApplicationStartInfo.java @@ -39,12 +39,17 @@ import java.io.ObjectOutputStream; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.Iterator; import java.util.Map; -import java.util.Set; /** - * Provide information related to a processes startup. + * Describes information related to an application process's startup. + * + * <p> + * Many aspects concerning why and how an applications process was started are valuable for apps + * both for logging and for potential behavior changes. Reason for process start, start type, + * start times, throttling, and other useful diagnostic data can be obtained from + * {@link ApplicationStartInfo} records. + * </p> */ @FlaggedApi(Flags.FLAG_APP_START_INFO) public final class ApplicationStartInfo implements Parcelable { @@ -552,13 +557,12 @@ public final class ApplicationStartInfo implements Parcelable { dest.writeInt(mDefiningUid); dest.writeString(mProcessName); dest.writeInt(mReason); - dest.writeInt(mStartupTimestampsNs.size()); - Set<Map.Entry<Integer, Long>> timestampEntrySet = mStartupTimestampsNs.entrySet(); - Iterator<Map.Entry<Integer, Long>> iter = timestampEntrySet.iterator(); - while (iter.hasNext()) { - Map.Entry<Integer, Long> entry = iter.next(); - dest.writeInt(entry.getKey()); - dest.writeLong(entry.getValue()); + dest.writeInt(mStartupTimestampsNs == null ? 0 : mStartupTimestampsNs.size()); + if (mStartupTimestampsNs != null) { + for (int i = 0; i < mStartupTimestampsNs.size(); i++) { + dest.writeInt(mStartupTimestampsNs.keyAt(i)); + dest.writeLong(mStartupTimestampsNs.valueAt(i)); + } } dest.writeInt(mStartType); dest.writeParcelable(mStartIntent, flags); @@ -740,13 +744,11 @@ public final class ApplicationStartInfo implements Parcelable { sb.append(" intent=").append(mStartIntent.toString()) .append('\n'); } - if (mStartupTimestampsNs.size() > 0) { + if (mStartupTimestampsNs != null && mStartupTimestampsNs.size() > 0) { sb.append(" timestamps: "); - Set<Map.Entry<Integer, Long>> timestampEntrySet = mStartupTimestampsNs.entrySet(); - Iterator<Map.Entry<Integer, Long>> iter = timestampEntrySet.iterator(); - while (iter.hasNext()) { - Map.Entry<Integer, Long> entry = iter.next(); - sb.append(entry.getKey()).append("=").append(entry.getValue()).append(" "); + for (int i = 0; i < mStartupTimestampsNs.size(); i++) { + sb.append(mStartupTimestampsNs.keyAt(i)).append("=").append(mStartupTimestampsNs + .valueAt(i)).append(" "); } sb.append('\n'); } diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 260e9859c72d..139275477063 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -715,7 +715,7 @@ interface IActivityManager { * @param listener A listener to for the callback upon completion of startup data collection. * @param userId The userId in the multi-user environment. */ - void setApplicationStartInfoCompleteListener(IApplicationStartInfoCompleteListener listener, + void addApplicationStartInfoCompleteListener(IApplicationStartInfoCompleteListener listener, int userId); @@ -724,7 +724,8 @@ interface IActivityManager { * * @param userId The userId in the multi-user environment. */ - void clearApplicationStartInfoCompleteListener(int userId); + void removeApplicationStartInfoCompleteListener(IApplicationStartInfoCompleteListener listener, + int userId); /** diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index e583a6cd6b1f..5366e735cabf 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -9858,7 +9858,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override - public void setApplicationStartInfoCompleteListener( + public void addApplicationStartInfoCompleteListener( IApplicationStartInfoCompleteListener listener, int userId) { enforceNotIsolatedCaller("setApplicationStartInfoCompleteListener"); @@ -9873,7 +9873,8 @@ public class ActivityManagerService extends IActivityManager.Stub @Override - public void clearApplicationStartInfoCompleteListener(int userId) { + public void removeApplicationStartInfoCompleteListener( + IApplicationStartInfoCompleteListener listener, int userId) { enforceNotIsolatedCaller("clearApplicationStartInfoCompleteListener"); // For the simplification, we don't support USER_ALL nor USER_CURRENT here. @@ -9882,7 +9883,8 @@ public class ActivityManagerService extends IActivityManager.Stub } final int callingUid = Binder.getCallingUid(); - mProcessList.getAppStartInfoTracker().clearStartInfoCompleteListener(callingUid, true); + mProcessList.getAppStartInfoTracker().removeStartInfoCompleteListener(listener, callingUid, + true); } @Override diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java index 82e554e67b7e..b90e5cd11216 100644 --- a/services/core/java/com/android/server/am/AppStartInfoTracker.java +++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java @@ -119,7 +119,7 @@ public final class AppStartInfoTracker { /** UID as key. */ @GuardedBy("mLock") - private final SparseArray<ApplicationStartInfoCompleteCallback> mCallbacks; + private final SparseArray<ArrayList<ApplicationStartInfoCompleteCallback>> mCallbacks; /** * Whether or not we've loaded the historical app process start info from persistent storage. @@ -465,11 +465,18 @@ public final class AppStartInfoTracker { synchronized (mLock) { if (startInfo.getStartupState() == ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN) { - ApplicationStartInfoCompleteCallback callback = + final List<ApplicationStartInfoCompleteCallback> callbacks = mCallbacks.get(startInfo.getRealUid()); - if (callback != null) { - callback.onApplicationStartInfoComplete(startInfo); + if (callbacks == null) { + return; } + final int size = callbacks.size(); + for (int i = 0; i < size; i++) { + if (callbacks.get(i) != null) { + callbacks.get(i).onApplicationStartInfoComplete(startInfo); + } + } + mCallbacks.remove(startInfo.getRealUid()); } } } @@ -542,7 +549,6 @@ public final class AppStartInfoTracker { } catch (RemoteException e) { /*ignored*/ } - clearStartInfoCompleteListener(mUid, true); } void unlinkToDeath() { @@ -551,7 +557,7 @@ public final class AppStartInfoTracker { @Override public void binderDied() { - clearStartInfoCompleteListener(mUid, false); + removeStartInfoCompleteListener(mCallback, mUid, false); } } @@ -561,22 +567,43 @@ public final class AppStartInfoTracker { if (!mEnabled) { return; } - mCallbacks.put(uid, new ApplicationStartInfoCompleteCallback(listener, uid)); + ArrayList<ApplicationStartInfoCompleteCallback> callbacks = mCallbacks.get(uid); + if (callbacks == null) { + mCallbacks.set(uid, + callbacks = new ArrayList<ApplicationStartInfoCompleteCallback>()); + } + callbacks.add(new ApplicationStartInfoCompleteCallback(listener, uid)); } } - void clearStartInfoCompleteListener(final int uid, boolean unlinkDeathRecipient) { + void removeStartInfoCompleteListener( + final IApplicationStartInfoCompleteListener listener, final int uid, + boolean unlinkDeathRecipient) { synchronized (mLock) { if (!mEnabled) { return; } - if (unlinkDeathRecipient) { - ApplicationStartInfoCompleteCallback callback = mCallbacks.get(uid); - if (callback != null) { - callback.unlinkToDeath(); + final ArrayList<ApplicationStartInfoCompleteCallback> callbacks = mCallbacks.get(uid); + if (callbacks == null) { + return; + } + final int size = callbacks.size(); + int index; + for (index = 0; index < size; index++) { + final ApplicationStartInfoCompleteCallback callback = callbacks.get(index); + if (callback.mCallback == listener) { + if (unlinkDeathRecipient) { + callback.unlinkToDeath(); + } + break; } } - mCallbacks.remove(uid); + if (index < size) { + callbacks.remove(index); + } + if (callbacks.isEmpty()) { + mCallbacks.remove(uid); + } } } |