AML: Abort launching metrics if no activities will be drawn
Otherwise it may report a large launch time on an invisible
activity that depends on when to trigger next transition.
Bug: 123355661
Test: atest ActivityMetricsLaunchObserverTests# \
testOnActivityLaunchCancelled_finishedBeforeDrawn
Change-Id: I6f936929a5ed6ade0e6350d9fde8229d6aaad558
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 9d41d97..04c2083 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -403,8 +403,7 @@
if (launchedActivity != null && launchedActivity.mDrawn) {
// Launched activity is already visible. We cannot measure windows drawn delay.
- reset(true /* abort */, info, "launched activity already visible",
- 0L /* timestampNs */);
+ abort(info, "launched activity already visible");
return;
}
@@ -422,8 +421,7 @@
if ((!isLoggableResultCode(resultCode) || launchedActivity == null || !processSwitch
|| windowingMode == WINDOWING_MODE_UNDEFINED) && !otherWindowModesLaunching) {
// Failed to launch or it was not a process switch, so we don't care about the timing.
- reset(true /* abort */, info, "failed to launch or not a process switch",
- 0L /* timestampNs */);
+ abort(info, "failed to launch or not a process switch");
return;
} else if (otherWindowModesLaunching) {
// Don't log this windowing mode but continue with the other windowing modes.
@@ -469,8 +467,7 @@
final WindowingModeTransitionInfoSnapshot infoSnapshot =
new WindowingModeTransitionInfoSnapshot(info);
if (allWindowsDrawn() && mLoggedTransitionStarting) {
- reset(false /* abort */, info, "notifyWindowsDrawn - all windows drawn",
- timestampNs /* timestampNs */);
+ reset(false /* abort */, info, "notifyWindowsDrawn - all windows drawn", timestampNs);
}
return infoSnapshot;
}
@@ -544,10 +541,11 @@
mHandler.obtainMessage(MSG_CHECK_VISIBILITY, args).sendToTarget();
}
- private boolean hasVisibleNonFinishingActivity(TaskRecord t) {
+ /** @return {@code true} if the given task has an activity will be drawn. */
+ private static boolean hasActivityToBeDrawn(TaskRecord t) {
for (int i = t.getChildCount() - 1; i >= 0; --i) {
final ActivityRecord r = t.getChildAt(i);
- if (r.visible && !r.finishing) {
+ if (r.visible && !r.mDrawn && !r.finishing) {
return true;
}
}
@@ -574,19 +572,20 @@
return;
}
- // Check if there is any activity in the task that is visible and not finishing. If the
- // launched activity finished before it is drawn and if there is another activity in
- // the task then that activity will be draw on screen.
- if (hasVisibleNonFinishingActivity(t)) {
+ // If the task of the launched activity contains any activity to be drawn, then the
+ // window drawn event should report later to complete the transition. Otherwise all
+ // activities in this task may be finished, invisible or drawn, so the transition event
+ // should be cancelled.
+ if (hasActivityToBeDrawn(t)) {
return;
}
if (DEBUG_METRICS) Slog.i(TAG, "notifyVisibilityChanged to invisible activity=" + r);
logAppTransitionCancel(info);
- mWindowingModeTransitionInfo.remove(r.getWindowingMode());
- if (mWindowingModeTransitionInfo.size() == 0) {
- reset(true /* abort */, info, "notifyVisibilityChanged to invisible",
- 0L /* timestampNs */);
+ // Abort if this is the only one active transition.
+ if (mWindowingModeTransitionInfo.size() == 1
+ && mWindowingModeTransitionInfo.get(r.getWindowingMode()) != null) {
+ abort(info, "notifyVisibilityChanged to invisible");
}
}
}
@@ -622,19 +621,25 @@
&& mWindowingModeTransitionInfo.size() > 0;
}
+ /** Aborts tracking of current launch metrics. */
+ private void abort(WindowingModeTransitionInfo info, String cause) {
+ reset(true /* abort */, info, cause, 0L /* timestampNs */);
+ }
+
private void reset(boolean abort, WindowingModeTransitionInfo info, String cause,
- long timestampNs) {
+ long timestampNs) {
+ final boolean isAnyTransitionActive = isAnyTransitionActive();
if (DEBUG_METRICS) {
- Slog.i(TAG,
- "reset abort=" + abort + ",cause=" + cause + ",timestamp=" + timestampNs);
+ Slog.i(TAG, "reset abort=" + abort + " cause=" + cause + " timestamp=" + timestampNs
+ + " active=" + isAnyTransitionActive);
}
- if (!abort && isAnyTransitionActive()) {
+ if (!abort && isAnyTransitionActive) {
logAppTransitionMultiEvents();
}
stopLaunchTrace(info);
// Ignore reset-after reset.
- if (isAnyTransitionActive()) {
+ if (isAnyTransitionActive) {
// LaunchObserver callbacks.
if (abort) {
launchObserverNotifyActivityLaunchCancelled(info);
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 03367db..7c867b6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -149,9 +149,7 @@
public void testOnActivityLaunchFinished() {
onActivityLaunched();
- mActivityMetricsLogger.notifyTransitionStarting(new SparseIntArray(),
- SystemClock.elapsedRealtimeNanos());
-
+ notifyTransitionStarting();
notifyWindowsDrawn(mTopActivity);
verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mTopActivity), anyLong());
@@ -159,10 +157,10 @@
}
@Test
- public void testOnActivityLaunchCancelled() {
+ public void testOnActivityLaunchCancelled_hasDrawn() {
onActivityLaunched();
- mTopActivity.mDrawn = true;
+ mTopActivity.visible = mTopActivity.mDrawn = true;
// Cannot time already-visible activities.
mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT, mTopActivity);
@@ -172,6 +170,28 @@
}
@Test
+ public void testOnActivityLaunchCancelled_finishedBeforeDrawn() {
+ mTopActivity.visible = mTopActivity.mDrawn = true;
+
+ // Suppress resume when creating the record because we want to notify logger manually.
+ mSupervisor.beginDeferResume();
+ // Create an activity with different process that meets process switch.
+ final ActivityRecord noDrawnActivity = new ActivityBuilder(mService)
+ .setTask(mTopActivity.getTaskRecord())
+ .setProcessName("other")
+ .build();
+ mSupervisor.readyToResume();
+
+ mActivityMetricsLogger.notifyActivityLaunching(noDrawnActivity.intent);
+ mActivityMetricsLogger.notifyActivityLaunched(START_SUCCESS, noDrawnActivity);
+
+ noDrawnActivity.destroyIfPossible("test");
+ mActivityMetricsLogger.notifyVisibilityChanged(noDrawnActivity);
+
+ verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(noDrawnActivity));
+ }
+
+ @Test
public void testOnReportFullyDrawn() {
onActivityLaunched();
@@ -184,16 +204,21 @@
private void onActivityLaunchedTrampoline() {
onIntentStarted();
- mActivityMetricsLogger.notifyActivityLaunched(START_SUCCESS, mTopActivity);
-
- verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(mTopActivity), anyInt());
-
- // A second, distinct, activity launch is coalesced into the the current app launch sequence
mActivityMetricsLogger.notifyActivityLaunched(START_SUCCESS, mTrampolineActivity);
+ verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(mTrampolineActivity), anyInt());
+
+ // A second, distinct, activity launch is coalesced into the current app launch sequence.
+ mActivityMetricsLogger.notifyActivityLaunched(START_SUCCESS, mTopActivity);
+
verifyNoMoreInteractions(mLaunchObserver);
}
+ private void notifyTransitionStarting() {
+ mActivityMetricsLogger.notifyTransitionStarting(new SparseIntArray(),
+ SystemClock.elapsedRealtimeNanos());
+ }
+
private void notifyWindowsDrawn(ActivityRecord r) {
mActivityMetricsLogger.notifyWindowsDrawn(r.getWindowingMode(),
SystemClock.elapsedRealtimeNanos());
@@ -203,15 +228,12 @@
public void testOnActivityLaunchFinishedTrampoline() {
onActivityLaunchedTrampoline();
- mActivityMetricsLogger.notifyTransitionStarting(new SparseIntArray(),
- SystemClock.elapsedRealtimeNanos());
-
+ notifyTransitionStarting();
notifyWindowsDrawn(mTrampolineActivity);
notifyWindowsDrawn(mTopActivity);
- verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mTrampolineActivity),
- anyLong());
+ verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mTopActivity), anyLong());
verifyNoMoreInteractions(mLaunchObserver);
}
@@ -219,12 +241,12 @@
public void testOnActivityLaunchCancelledTrampoline() {
onActivityLaunchedTrampoline();
- mTrampolineActivity.mDrawn = true;
+ mTopActivity.mDrawn = true;
// Cannot time already-visible activities.
- mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT, mTrampolineActivity);
+ mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT, mTopActivity);
- verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(mTrampolineActivity));
+ verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(mTopActivity));
verifyNoMoreInteractions(mLaunchObserver);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index 80f0851..78db6c9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -118,7 +118,8 @@
private ComponentName mComponent;
private String mTargetActivity;
private TaskRecord mTaskRecord;
- private int mUid;
+ private String mProcessName = "name";
+ private int mUid = 12345;
private boolean mCreateTask;
private ActivityStack mStack;
private int mActivityFlags;
@@ -175,6 +176,11 @@
return this;
}
+ ActivityBuilder setProcessName(String name) {
+ mProcessName = name;
+ return this;
+ }
+
ActivityBuilder setUid(int uid) {
mUid = uid;
return this;
@@ -235,6 +241,7 @@
aInfo.applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
aInfo.applicationInfo.packageName = mComponent.getPackageName();
aInfo.applicationInfo.uid = mUid;
+ aInfo.processName = mProcessName;
aInfo.packageName = mComponent.getPackageName();
if (mTargetActivity != null) {
aInfo.targetActivity = mTargetActivity;
@@ -268,7 +275,7 @@
}
final WindowProcessController wpc = new WindowProcessController(mService,
- mService.mContext.getApplicationInfo(), "name", 12345,
+ mService.mContext.getApplicationInfo(), mProcessName, mUid,
UserHandle.getUserId(12345), mock(Object.class),
mock(WindowProcessListener.class));
wpc.setThread(mock(IApplicationThread.class));