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
This commit is contained in:
Riddle Hsu
2021-08-13 01:10:30 +08:00
parent 7e62af128a
commit 747432f31f
4 changed files with 23 additions and 9 deletions

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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.

View File

@@ -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]);