diff options
| author | 2020-11-18 14:49:28 +0800 | |
|---|---|---|
| committer | 2020-11-18 15:11:20 +0800 | |
| commit | 59c2e7cf8a88cf6a30737819a4d5cbef76ffd80c (patch) | |
| tree | 4ae13eb42b65d3c8643de575eb665852a5178f13 | |
| parent | e12c50e04242730a54d0abae5e4efc55e5835bea (diff) | |
Add a new launch type for hot start with relaunched activity
An existing activity may be relaunched when moving from background
to front if the current configuration is changed and the activity
cannot handle the change.
The relaunch type was reported as a hot start because its process
is alive and the original activity exists. But the relaunch needs to
destroy the original activity and create a new instance, which may
even take longer time than a warm start. So the case should be
separated from hot start.
Also:
- Make WaitResult#launchState is always populated by
ActivityMetricsLogger, so "am start -W" can get consistent
launch type.
- Remove dead code in ActivityRecord#ensureActivityConfiguration
that it already early returns if !attachedToProcess.
Bug: 172528316
Test: atest ActivityMetricsLaunchObserverTests#testLaunchState
Change-Id: I3ef64ce1e23b2f13d42fa2b12bc31c0cdb4652fc
7 files changed, 91 insertions, 38 deletions
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index dbb3ecd0bc36..5affd6a2dcb3 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -3764,6 +3764,7 @@ message AppStartOccurred { WARM = 1; HOT = 2; COLD = 3; + RELAUNCH = 4; } // The transition type. optional TransitionType type = 3; @@ -3825,6 +3826,7 @@ message AppStartCanceled { WARM = 1; HOT = 2; COLD = 3; + RELAUNCH = 4; } // The transition type. optional TransitionType type = 3; diff --git a/core/java/android/app/WaitResult.java b/core/java/android/app/WaitResult.java index d65be9bded4f..77891e0fd007 100644 --- a/core/java/android/app/WaitResult.java +++ b/core/java/android/app/WaitResult.java @@ -40,14 +40,21 @@ public class WaitResult implements Parcelable { */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"LAUNCH_STATE_"}, value = { + LAUNCH_STATE_UNKNOWN, LAUNCH_STATE_COLD, LAUNCH_STATE_WARM, - LAUNCH_STATE_HOT + LAUNCH_STATE_HOT, + LAUNCH_STATE_RELAUNCH }) public @interface LaunchState { } /** + * Not considered as a launch event, e.g. the activity is already on top. + */ + public static final int LAUNCH_STATE_UNKNOWN = 0; + + /** * Cold launch sequence: a new process has started. */ public static final int LAUNCH_STATE_COLD = 1; @@ -62,6 +69,13 @@ public class WaitResult implements Parcelable { */ public static final int LAUNCH_STATE_HOT = 3; + /** + * Relaunch launch sequence: process reused, but activity has to be destroyed and created. + * E.g. the current device configuration is different from the background activity that will be + * brought to foreground, and the activity doesn't declare to handle the change. + */ + public static final int LAUNCH_STATE_RELAUNCH = 4; + public static final int INVALID_DELAY = -1; public int result; public boolean timeout; @@ -124,6 +138,8 @@ public class WaitResult implements Parcelable { return "WARM"; case LAUNCH_STATE_HOT: return "HOT"; + case LAUNCH_STATE_RELAUNCH: + return "RELAUNCH"; default: return "UNKNOWN (" + type + ")"; } diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index d8d1a6563675..b4ca7c5f6ff1 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -3,8 +3,10 @@ package com.android.server.wm; import static android.app.ActivityManager.START_SUCCESS; import static android.app.ActivityManager.START_TASK_TO_FRONT; import static android.app.ActivityManager.processStateAmToProto; +import static android.app.WaitResult.INVALID_DELAY; import static android.app.WaitResult.LAUNCH_STATE_COLD; import static android.app.WaitResult.LAUNCH_STATE_HOT; +import static android.app.WaitResult.LAUNCH_STATE_RELAUNCH; import static android.app.WaitResult.LAUNCH_STATE_WARM; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; @@ -130,7 +132,6 @@ class ActivityMetricsLogger { * transition, in the case the launch is standalone (e.g. from recents). */ private static final int IGNORE_CALLER = -1; - private static final int INVALID_DELAY = -1; // Preallocated strings we are sending to tron, so we don't have to allocate a new one every // time we log. @@ -220,6 +221,8 @@ class ActivityMetricsLogger { boolean mLoggedStartingWindowDrawn; /** If the any app transitions have been logged as starting. */ boolean mLoggedTransitionStarting; + /** Whether any activity belonging to this transition has relaunched. */ + boolean mRelaunched; /** Non-null if the application has reported drawn but its window hasn't. */ @Nullable Runnable mPendingFullyDrawn; @@ -351,6 +354,7 @@ class ActivityMetricsLogger { */ final int windowsFullyDrawnDelayMs; final int activityRecordIdHashCode; + final boolean relaunched; private TransitionInfoSnapshot(TransitionInfo info) { this(info, info.mLastLaunchedActivity, INVALID_DELAY); @@ -379,6 +383,7 @@ class ActivityMetricsLogger { launchedActivityShortComponentName = launchedActivity.shortComponentName; activityRecordIdHashCode = System.identityHashCode(launchedActivity); this.windowsFullyDrawnDelayMs = windowsFullyDrawnDelayMs; + relaunched = info.mRelaunched; } @WaitResult.LaunchState int getLaunchState() { @@ -386,7 +391,7 @@ class ActivityMetricsLogger { case TYPE_TRANSITION_WARM_LAUNCH: return LAUNCH_STATE_WARM; case TYPE_TRANSITION_HOT_LAUNCH: - return LAUNCH_STATE_HOT; + return relaunched ? LAUNCH_STATE_RELAUNCH : LAUNCH_STATE_HOT; case TYPE_TRANSITION_COLD_LAUNCH: return LAUNCH_STATE_COLD; default: @@ -673,6 +678,13 @@ class ActivityMetricsLogger { } } + void notifyActivityRelaunched(ActivityRecord r) { + final TransitionInfo info = getActiveTransitionInfo(r); + if (info != null) { + info.mRelaunched = true; + } + } + /** Makes sure that the reference to the removed activity is cleared. */ void notifyActivityRemoved(@NonNull ActivityRecord r) { mLastTransitionInfo.remove(r); @@ -800,13 +812,13 @@ class ActivityMetricsLogger { FrameworkStatsLog.APP_START_CANCELED, activity.info.applicationInfo.uid, activity.packageName, - convertAppStartTransitionType(type), + getAppStartTransitionType(type, info.mRelaunched), activity.info.name); if (DEBUG_METRICS) { Slog.i(TAG, String.format("APP_START_CANCELED(%s, %s, %s, %s)", activity.info.applicationInfo.uid, activity.packageName, - convertAppStartTransitionType(type), + getAppStartTransitionType(type, info.mRelaunched), activity.info.name)); } } @@ -871,7 +883,7 @@ class ActivityMetricsLogger { FrameworkStatsLog.APP_START_OCCURRED, info.applicationInfo.uid, info.packageName, - convertAppStartTransitionType(info.type), + getAppStartTransitionType(info.type, info.relaunched), info.launchedActivityName, info.launchedActivityLaunchedFromPackage, isInstantApp, @@ -891,7 +903,7 @@ class ActivityMetricsLogger { Slog.i(TAG, String.format("APP_START_OCCURRED(%s, %s, %s, %s, %s)", info.applicationInfo.uid, info.packageName, - convertAppStartTransitionType(info.type), + getAppStartTransitionType(info.type, info.relaunched), info.launchedActivityName, info.launchedActivityLaunchedFromPackage)); } @@ -918,7 +930,7 @@ class ActivityMetricsLogger { Log.i(TAG, sb.toString()); } - private int convertAppStartTransitionType(int tronType) { + private static int getAppStartTransitionType(int tronType, boolean relaunched) { if (tronType == TYPE_TRANSITION_COLD_LAUNCH) { return FrameworkStatsLog.APP_START_OCCURRED__TYPE__COLD; } @@ -926,17 +938,13 @@ class ActivityMetricsLogger { return FrameworkStatsLog.APP_START_OCCURRED__TYPE__WARM; } if (tronType == TYPE_TRANSITION_HOT_LAUNCH) { - return FrameworkStatsLog.APP_START_OCCURRED__TYPE__HOT; + return relaunched + ? FrameworkStatsLog.APP_START_OCCURRED__TYPE__RELAUNCH + : FrameworkStatsLog.APP_START_OCCURRED__TYPE__HOT; } return FrameworkStatsLog.APP_START_OCCURRED__TYPE__UNKNOWN; } - /** @return the last known window drawn delay of the given activity. */ - int getLastDrawnDelayMs(ActivityRecord r) { - final TransitionInfo info = mLastTransitionInfo.get(r); - return info != null ? info.mWindowsDrawnDelayMs : INVALID_DELAY; - } - /** @see android.app.Activity#reportFullyDrawn */ TransitionInfoSnapshot logAppTransitionReportedDrawn(ActivityRecord r, boolean restoredFromBundle) { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 22ebf306c474..875eba11fbcc 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -226,7 +226,7 @@ import android.app.ActivityOptions; import android.app.PendingIntent; import android.app.PictureInPictureParams; import android.app.ResultInfo; -import android.app.WaitResult.LaunchState; +import android.app.WaitResult; import android.app.servertransaction.ActivityConfigurationChangeItem; import android.app.servertransaction.ActivityLifecycleItem; import android.app.servertransaction.ActivityRelaunchItem; @@ -3131,6 +3131,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } void finishRelaunching() { + mTaskSupervisor.getActivityMetricsLogger().notifyActivityRelaunched(this); unfreezeBounds(); if (mPendingRelaunchCount > 0) { @@ -5436,14 +5437,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A .getActivityMetricsLogger().notifyWindowsDrawn(this, timestampNs); final boolean validInfo = info != null; final int windowsDrawnDelayMs = validInfo ? info.windowsDrawnDelayMs : INVALID_DELAY; - final @LaunchState int launchState = validInfo ? info.getLaunchState() : -1; + final @WaitResult.LaunchState int launchState = + validInfo ? info.getLaunchState() : WaitResult.LAUNCH_STATE_UNKNOWN; // The activity may have been requested to be invisible (another activity has been launched) // so there is no valid info. But if it is the current top activity (e.g. sleeping), the // invalid state is still reported to make sure the waiting result is notified. if (validInfo || this == getDisplayArea().topRunningActivity()) { mTaskSupervisor.reportActivityLaunchedLocked(false /* timeout */, this, windowsDrawnDelayMs, launchState); - mTaskSupervisor.stopWaitingForActivityVisible(this, windowsDrawnDelayMs); + mTaskSupervisor.stopWaitingForActivityVisible(this, windowsDrawnDelayMs, launchState); } finishLaunchTickingLocked(); if (task != null) { @@ -7156,11 +7158,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else { mRelaunchReason = RELAUNCH_REASON_NONE; } - if (!attachedToProcess()) { - ProtoLog.v(WM_DEBUG_CONFIGURATION, - "Config is destroying non-running %s", this); - destroyImmediately("config"); - } else if (mState == PAUSING) { + if (mState == PAUSING) { // A little annoying: we are waiting for this activity to finish pausing. Let's not // do anything now, but just flag that it needs to be restarted when done pausing. ProtoLog.v(WM_DEBUG_CONFIGURATION, diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index f472a5dd7ba9..5208fd5dac3a 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -28,8 +28,6 @@ import static android.app.ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; import static android.app.ActivityManager.START_SUCCESS; import static android.app.ActivityManager.START_TASK_TO_FRONT; import static android.app.ActivityTaskManager.INVALID_TASK_ID; -import static android.app.WaitResult.LAUNCH_STATE_COLD; -import static android.app.WaitResult.LAUNCH_STATE_HOT; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; @@ -819,8 +817,6 @@ class ActivityStarter { break; } case START_TASK_TO_FRONT: { - mRequest.waitResult.launchState = - r.attachedToProcess() ? LAUNCH_STATE_HOT : LAUNCH_STATE_COLD; // ActivityRecord may represent a different activity, but it should not be // in the resumed state. if (r.nowVisible && r.isState(RESUMED)) { diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index e91a6d8e2439..01d77d5b6cc8 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -553,14 +553,16 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // down to the max limit while they are still waiting to finish. mFinishingActivities.remove(r); - stopWaitingForActivityVisible(r, WaitResult.INVALID_DELAY); + stopWaitingForActivityVisible(r); } + /** There is no valid launch time, just stop waiting. */ void stopWaitingForActivityVisible(ActivityRecord r) { - stopWaitingForActivityVisible(r, getActivityMetricsLogger().getLastDrawnDelayMs(r)); + stopWaitingForActivityVisible(r, WaitResult.INVALID_DELAY, WaitResult.LAUNCH_STATE_UNKNOWN); } - void stopWaitingForActivityVisible(ActivityRecord r, long totalTime) { + void stopWaitingForActivityVisible(ActivityRecord r, long totalTime, + @WaitResult.LaunchState int launchState) { boolean changed = false; for (int i = mWaitingForActivityVisible.size() - 1; i >= 0; --i) { final WaitInfo w = mWaitingForActivityVisible.get(i); @@ -570,6 +572,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { result.timeout = false; result.who = w.getComponent(); result.totalTime = totalTime; + result.launchState = launchState; mWaitingForActivityVisible.remove(w); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java index 338ed066e1e4..f1dc098fccf6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java @@ -31,6 +31,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.timeout; import android.app.ActivityOptions; @@ -53,6 +54,7 @@ import org.mockito.ArgumentMatcher; import java.util.Arrays; import java.util.concurrent.TimeUnit; +import java.util.function.ToIntFunction; /** * Tests for the {@link ActivityMetricsLaunchObserver} class. @@ -158,6 +160,41 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { verifyNoMoreInteractions(mLaunchObserver); } + @Test + public void testLaunchState() { + final ToIntFunction<Boolean> launchTemplate = doRelaunch -> { + clearInvocations(mLaunchObserver); + onActivityLaunched(mTopActivity); + notifyTransitionStarting(mTopActivity); + if (doRelaunch) { + mActivityMetricsLogger.notifyActivityRelaunched(mTopActivity); + } + final ActivityMetricsLogger.TransitionInfoSnapshot info = + notifyWindowsDrawn(mTopActivity); + verifyOnActivityLaunchFinished(mTopActivity); + return info.getLaunchState(); + }; + + final WindowProcessController app = mTopActivity.app; + // Assume that the process is started (ActivityBuilder has mocked the returned value of + // ATMS#getProcessController) but the activity has not attached process. + mTopActivity.app = null; + assertWithMessage("Warm launch").that(launchTemplate.applyAsInt(false /* doRelaunch */)) + .isEqualTo(WaitResult.LAUNCH_STATE_WARM); + + mTopActivity.app = app; + assertWithMessage("Hot launch").that(launchTemplate.applyAsInt(false /* doRelaunch */)) + .isEqualTo(WaitResult.LAUNCH_STATE_HOT); + + assertWithMessage("Relaunch").that(launchTemplate.applyAsInt(true /* doRelaunch */)) + .isEqualTo(WaitResult.LAUNCH_STATE_RELAUNCH); + + mTopActivity.app = null; + doReturn(null).when(mAtm).getProcessController(app.mName, app.mUid); + assertWithMessage("Cold launch").that(launchTemplate.applyAsInt(false /* doRelaunch */)) + .isEqualTo(WaitResult.LAUNCH_STATE_COLD); + } + private void onActivityLaunched(ActivityRecord activity) { onIntentStarted(activity.intent); notifyActivityLaunched(START_SUCCESS, activity); @@ -168,15 +205,10 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { @Test public void testOnActivityLaunchFinished() { - // Assume that the process is started (ActivityBuilder has mocked the returned value of - // ATMS#getProcessController) but the activity has not attached process. - mTopActivity.app = null; onActivityLaunched(mTopActivity); notifyTransitionStarting(mTopActivity); - final ActivityMetricsLogger.TransitionInfoSnapshot info = notifyWindowsDrawn(mTopActivity); - assertWithMessage("Warm launch").that(info.getLaunchState()) - .isEqualTo(WaitResult.LAUNCH_STATE_WARM); + notifyWindowsDrawn(mTopActivity); verifyOnActivityLaunchFinished(mTopActivity); verifyNoMoreInteractions(mLaunchObserver); @@ -231,8 +263,6 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { assertWithMessage("Record start source").that(info.sourceType) .isEqualTo(SourceInfo.TYPE_LAUNCHER); assertWithMessage("Record event time").that(info.sourceEventDelayMs).isAtLeast(10); - assertWithMessage("Hot launch").that(info.getLaunchState()) - .isEqualTo(WaitResult.LAUNCH_STATE_HOT); verifyAsync(mLaunchObserver).onReportFullyDrawn(eqProto(mTopActivity), anyLong()); verifyOnActivityLaunchFinished(mTopActivity); |