summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Yisroel Forta <yforta@google.com> 2024-01-16 03:28:29 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-01-16 03:28:29 +0000
commitfbd39b784c19586566903ff4f567c94c8f2f41ea (patch)
treefe2bbf57f002c61c3829d97ad38c34b8d0df9c3d
parent7ac510970ee92e6f7e31dd88a5df4a6869eac7c9 (diff)
parentd78641332036126b6633a589c5b806336fa66bd9 (diff)
Merge "AppStartInfo API Updates" into main
-rw-r--r--core/api/current.txt4
-rw-r--r--core/java/android/app/ActivityManager.java98
-rw-r--r--core/java/android/app/ApplicationStartInfo.java34
-rw-r--r--core/java/android/app/IActivityManager.aidl5
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java8
-rw-r--r--services/core/java/com/android/server/am/AppStartInfoTracker.java53
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);
+ }
}
}