From 747432f31f76fd05af08a98c759a2515cdb4b808 Mon Sep 17 00:00:00 2001 From: Riddle Hsu Date: Fri, 13 Aug 2021 01:10:30 +0800 Subject: [PATCH] Avoid canceling WaitInfo by trampoline activity An invisible trampoline activity may not animate, so it may be destroyed earlier than the next activity launched by the trampoline. If the WaitInfo aborts by matching the component name of the trampoline when it is destroyed, the launch time becomes unknown. The case happens when the trampoline activity and the next main activity are in different tasks. Because both the activity and its parent are not animating, it will be scheduled to destroy. Also keep invisible initializing pending drawn records when the transition is started, because it depends on the drawn time of starting window, the next activity may not yet update visibility. Bug: 193112389 Test: AmStartOptionsTests ActivityTaskSupervisorTests# \ testReportWaitingActivityLaunched ActivityMetricsLaunchObserverTests# \ testDoNotCountInvisibleActivityToBeDrawn Change-Id: I53c2aa6fd604bb12d539176296663a40ba762d22 --- .../android/server/wm/ActivityMetricsLogger.java | 15 +++++++++++---- .../android/server/wm/ActivityTaskSupervisor.java | 5 ++++- .../wm/ActivityMetricsLaunchObserverTests.java | 1 + .../server/wm/ActivityTaskSupervisorTests.java | 11 +++++++---- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index 20958aa1c9b15..ce1592d87fcda 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -187,6 +187,10 @@ class ActivityMetricsLogger { return mAssociatedTransitionInfo != null && mAssociatedTransitionInfo.allDrawn(); } + boolean hasActiveTransitionInfo() { + return mAssociatedTransitionInfo != null; + } + boolean contains(ActivityRecord r) { return mAssociatedTransitionInfo != null && mAssociatedTransitionInfo.contains(r); } @@ -336,10 +340,11 @@ class ActivityMetricsLogger { } /** Only keep the records which can be drawn. */ - void updatePendingDraw() { + void updatePendingDraw(boolean keepInitializing) { for (int i = mPendingDrawActivities.size() - 1; i >= 0; i--) { final ActivityRecord r = mPendingDrawActivities.get(i); - if (!r.mVisibleRequested) { + if (!r.mVisibleRequested + && !(keepInitializing && r.isState(ActivityRecord.State.INITIALIZING))) { if (DEBUG_METRICS) Slog.i(TAG, "Discard pending draw " + r); mPendingDrawActivities.remove(i); } @@ -663,7 +668,7 @@ class ActivityMetricsLogger { // visible such as after the top task is finished. for (int i = mTransitionInfoList.size() - 2; i >= 0; i--) { final TransitionInfo prevInfo = mTransitionInfoList.get(i); - prevInfo.updatePendingDraw(); + prevInfo.updatePendingDraw(false /* keepInitializing */); if (prevInfo.allDrawn()) { abort(prevInfo, "nothing will be drawn"); } @@ -749,7 +754,9 @@ class ActivityMetricsLogger { info.mCurrentTransitionDelayMs = info.calculateDelay(timestampNs); info.mReason = activityToReason.valueAt(index); info.mLoggedTransitionStarting = true; - info.updatePendingDraw(); + // Do not remove activity in initializing state because the transition may be started + // by starting window. The initializing activity may be requested to visible soon. + info.updatePendingDraw(true /* keepInitializing */); if (info.allDrawn()) { done(false /* abort */, info, "notifyTransitionStarting - all windows drawn", timestampNs); diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 50ac4330facd3..e593c1c1e6562 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -2596,7 +2596,10 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } boolean matches(ActivityRecord r) { - return mTargetComponent.equals(r.mActivityComponent) || mLaunchingState.contains(r); + if (!mLaunchingState.hasActiveTransitionInfo()) { + return mTargetComponent.equals(r.mActivityComponent); + } + return mLaunchingState.contains(r); } void dump(PrintWriter pw, String prefix) { 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 b282cd7ce1cbc..89a126b92bfa7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java @@ -404,6 +404,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { // Another round without setting visibility of the trampoline activity. onActivityLaunchedTrampoline(); + mTrampolineActivity.setState(ActivityRecord.State.PAUSING, "test"); notifyWindowsDrawn(mTopActivity); // If the transition can start, the invisible activities should be discarded and the launch // event be reported successfully. diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java index 0d177c14427b8..26a68821a672a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java @@ -49,6 +49,7 @@ import androidx.test.filters.MediumTest; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentMatchers; import java.util.concurrent.TimeUnit; @@ -108,16 +109,18 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase { final ActivityMetricsLogger.LaunchingState launchingState = new ActivityMetricsLogger.LaunchingState(); spyOn(launchingState); - doReturn(true).when(launchingState).contains(eq(secondActivity)); + doReturn(true).when(launchingState).hasActiveTransitionInfo(); + doReturn(true).when(launchingState).contains( + ArgumentMatchers.argThat(r -> r == firstActivity || r == secondActivity)); // The test case already runs inside global lock, so above thread can only execute after // this waiting method that releases the lock. mSupervisor.waitActivityVisibleOrLaunched(taskToFrontWait, firstActivity, launchingState); // Assert that the thread is finished. assertTrue(condition.block(TIMEOUT_MS)); - assertEquals(taskToFrontWait.result, START_TASK_TO_FRONT); - assertEquals(taskToFrontWait.who, secondActivity.mActivityComponent); - assertEquals(taskToFrontWait.launchState, WaitResult.LAUNCH_STATE_HOT); + assertEquals(START_TASK_TO_FRONT, taskToFrontWait.result); + assertEquals(secondActivity.mActivityComponent, taskToFrontWait.who); + assertEquals(WaitResult.LAUNCH_STATE_HOT, taskToFrontWait.launchState); // START_TASK_TO_FRONT means that another component will be visible, so the component // should not be assigned as the first activity. assertNull(launchedComponent[0]);