diff --git a/api/current.txt b/api/current.txt index 41e17b7d21d0a..a2588f50b2f97 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3611,6 +3611,7 @@ package android.app { method public android.net.Uri getReferrer(); method public int getRequestedOrientation(); method public final android.view.SearchEvent getSearchEvent(); + method public long getStartInitiatedTime(); method public int getTaskId(); method public final java.lang.CharSequence getTitle(); method public final int getTitleColor(); diff --git a/api/system-current.txt b/api/system-current.txt index 489567ec69aae..a6a4ab850bfcb 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -3737,6 +3737,7 @@ package android.app { method public android.net.Uri getReferrer(); method public int getRequestedOrientation(); method public final android.view.SearchEvent getSearchEvent(); + method public long getStartInitiatedTime(); method public int getTaskId(); method public final java.lang.CharSequence getTitle(); method public final int getTitleColor(); diff --git a/api/test-current.txt b/api/test-current.txt index 27b266ded7a7f..86592586f9f60 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -3613,6 +3613,7 @@ package android.app { method public android.net.Uri getReferrer(); method public int getRequestedOrientation(); method public final android.view.SearchEvent getSearchEvent(); + method public long getStartInitiatedTime(); method public int getTaskId(); method public final java.lang.CharSequence getTitle(); method public final int getTitleColor(); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 06291abb69d43..6019d95ffc7ef 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -18,6 +18,7 @@ package android.app; import android.metrics.LogMaker; import android.graphics.Rect; +import android.os.SystemClock; import android.view.ViewRootImpl.ActivityConfigCallback; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; @@ -7416,6 +7417,25 @@ public class Activity extends ContextThemeWrapper } } + /** + * Return the timestamp at which this activity start was last initiated by the system in the + * {@link SystemClock#uptimeMillis()} time base. + * + * This can be used to understand how much time is taken for an activity to be started and + * displayed to the user. + * + * @return timestamp at which this activity start was initiated by the system + * or {@code 0} if for any reason the timestamp could not be retrieved. + */ + public long getStartInitiatedTime() { + try { + return ActivityManager.getService().getActivityStartInitiatedTime(mToken); + } catch (RemoteException e) { + Log.e(TAG, "Failed to call getActivityStartTime", e); + return 0; + } + } + class HostCallbacks extends FragmentHostCallback { public HostCallbacks() { super(Activity.this /*activity*/); diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 079bbcdd4951c..b98cd8565d135 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -640,6 +640,8 @@ interface IActivityManager { */ void backgroundWhitelistUid(int uid); + long getActivityStartInitiatedTime(IBinder token); + // WARNING: when these transactions are updated, check if they are any callers on the native // side. If so, make sure they are using the correct transaction ids and arguments. // If a transaction which will also be used on the native side is being inserted, add it diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 35654d76dee5b..05001d529e733 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -23809,6 +23809,15 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @Override + public long getActivityStartInitiatedTime(IBinder token) { + final ActivityRecord r = ActivityRecord.forTokenLocked(token); + if (r != null) { + return r.mStartInitiatedTimeMs; + } + return 0; + } + void updateApplicationInfoLocked(@NonNull List packagesToUpdate, int userId) { final PackageManagerInternal packageManager = getPackageManagerInternalLocked(); final boolean updateFrameworkRes = packagesToUpdate.contains("android"); diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index cfbd2b5e28be2..f621bc4fa6d2a 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -340,6 +340,12 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo // handle calculating override configuration from the bounds. private final Rect mBounds = new Rect(); + /** + * Denotes the timestamp at which this activity start was last initiated in the + * {@link SystemClock#uptimeMillis()} time base. + */ + long mStartInitiatedTimeMs; + /** * Temp configs used in {@link #ensureActivityConfigurationLocked(int, boolean)} */ @@ -498,6 +504,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo pw.print(" forceNewConfig="); pw.println(forceNewConfig); pw.print(prefix); pw.print("mActivityType="); pw.println(activityTypeToString(mActivityType)); + pw.print(prefix); pw.print("mStartInitiatedTimeMs="); + TimeUtils.formatDuration(mStartInitiatedTimeMs, now, pw); if (requestedVrComponent != null) { pw.print(prefix); pw.print("requestedVrComponent="); diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 56594d3fdf482..8f1c20330e53a 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -244,6 +244,7 @@ class ActivityStarter { ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity, ActivityStackSupervisor.ActivityContainer container, TaskRecord inTask) { + final long activityStartTime = SystemClock.uptimeMillis(); int err = ActivityManager.START_SUCCESS; ProcessRecord callerApp = null; @@ -478,6 +479,7 @@ class ActivityStarter { callingPackage, intent, resolvedType, aInfo, mService.getGlobalConfiguration(), resultRecord, resultWho, requestCode, componentSpecified, voiceSession != null, mSupervisor, container, options, sourceRecord); + r.mStartInitiatedTimeMs = activityStartTime; if (outActivity != null) { outActivity[0] = r; } @@ -1029,6 +1031,7 @@ class ActivityStarter { // so make sure the task now has the identity of the new intent. top.getTask().setIntent(mStartActivity); } + top.mStartInitiatedTimeMs = mStartActivity.mStartInitiatedTimeMs; ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity, top.getTask()); top.deliverNewIntentLocked(mCallingUid, mStartActivity.intent, mStartActivity.launchedFromPackage); @@ -1052,6 +1055,7 @@ class ActivityStarter { setTaskFromIntentActivity(reusedActivity); if (!mAddingToTask && mReuseTask == null) { + reusedActivity.mStartInitiatedTimeMs = mStartActivity.mStartInitiatedTimeMs; // We didn't do anything... but it was needed (a.k.a., client don't use that // intent!) And for paranoia, make sure we have correctly resumed the top activity. resumeTargetStackIfNeeded(); @@ -1084,6 +1088,7 @@ class ActivityStarter { || mLaunchSingleTop || mLaunchSingleTask); if (dontStart) { ActivityStack.logStartActivity(AM_NEW_INTENT, top, top.getTask()); + top.mStartInitiatedTimeMs = mStartActivity.mStartInitiatedTimeMs; // For paranoia, make sure we have correctly resumed the top activity. topStack.mLastPausedActivity = null; if (mDoResume) { @@ -1664,6 +1669,7 @@ class ActivityStarter { // desires. if (((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0 || mLaunchSingleTop) && intentActivity.realActivity.equals(mStartActivity.realActivity)) { + intentActivity.mStartInitiatedTimeMs = mStartActivity.mStartInitiatedTimeMs; ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity, intentActivity.getTask()); if (intentActivity.frontOfTask) {