diff options
5 files changed, 156 insertions, 67 deletions
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 119c939135a6..0bfc4a851948 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -103,12 +103,6 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/TaskFragment.java" }, - "-2006946193": { - "message": "setClientVisible: %s clientVisible=%b Callers=%s", - "level": "VERBOSE", - "group": "WM_DEBUG_APP_TRANSITIONS", - "at": "com\/android\/server\/wm\/ActivityRecord.java" - }, "-2002500255": { "message": "Defer removing snapshot surface in %dms", "level": "VERBOSE", @@ -553,6 +547,12 @@ "group": "WM_SHOW_TRANSACTIONS", "at": "com\/android\/server\/wm\/RootWindowContainer.java" }, + "-1501564055": { + "message": "Organized TaskFragment is not ready= %s", + "level": "VERBOSE", + "group": "WM_DEBUG_APP_TRANSITIONS", + "at": "com\/android\/server\/wm\/AppTransitionController.java" + }, "-1499134947": { "message": "Removing starting %s from %s", "level": "VERBOSE", diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index c0b69794966b..82377b34d22a 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -165,8 +165,8 @@ public class AppTransitionController { void handleAppTransitionReady() { mTempTransitionReasons.clear(); if (!transitionGoodToGo(mDisplayContent.mOpeningApps, mTempTransitionReasons) - || !transitionGoodToGo(mDisplayContent.mChangingContainers, - mTempTransitionReasons)) { + || !transitionGoodToGo(mDisplayContent.mChangingContainers, mTempTransitionReasons) + || !transitionGoodToGoForTaskFragments()) { return; } Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady"); @@ -599,6 +599,13 @@ public class AppTransitionController { } } + @Nullable + static Task findRootTaskFromContainer(WindowContainer wc) { + return wc.asTaskFragment() != null ? wc.asTaskFragment().getRootTask() + : wc.asActivityRecord().getRootTask(); + } + + @Nullable static ActivityRecord getAppFromContainer(WindowContainer wc) { return wc.asTaskFragment() != null ? wc.asTaskFragment().getTopNonFinishingActivity() : wc.asActivityRecord(); @@ -972,71 +979,108 @@ public class AppTransitionController { ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Checking %d opening apps (frozen=%b timeout=%b)...", apps.size(), mService.mDisplayFrozen, mDisplayContent.mAppTransition.isTimeout()); - + if (mDisplayContent.mAppTransition.isTimeout()) { + return true; + } final ScreenRotationAnimation screenRotationAnimation = mService.mRoot.getDisplayContent( Display.DEFAULT_DISPLAY).getRotationAnimation(); - if (!mDisplayContent.mAppTransition.isTimeout()) { - // Imagine the case where we are changing orientation due to an app transition, but a - // previous orientation change is still in progress. We won't process the orientation - // change for our transition because we need to wait for the rotation animation to - // finish. - // If we start the app transition at this point, we will interrupt it halfway with a - // new rotation animation after the old one finally finishes. It's better to defer the - // app transition. - if (screenRotationAnimation != null && screenRotationAnimation.isAnimating() - && mDisplayContent.getDisplayRotation().needsUpdate()) { - ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, - "Delaying app transition for screen rotation animation to finish"); + // Imagine the case where we are changing orientation due to an app transition, but a + // previous orientation change is still in progress. We won't process the orientation + // change for our transition because we need to wait for the rotation animation to + // finish. + // If we start the app transition at this point, we will interrupt it halfway with a + // new rotation animation after the old one finally finishes. It's better to defer the + // app transition. + if (screenRotationAnimation != null && screenRotationAnimation.isAnimating() + && mDisplayContent.getDisplayRotation().needsUpdate()) { + ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, + "Delaying app transition for screen rotation animation to finish"); + return false; + } + for (int i = 0; i < apps.size(); i++) { + WindowContainer wc = apps.valueAt(i); + final ActivityRecord activity = getAppFromContainer(wc); + if (activity == null) { + continue; + } + ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, + "Check opening app=%s: allDrawn=%b startingDisplayed=%b " + + "startingMoved=%b isRelaunching()=%b startingWindow=%s", + activity, activity.allDrawn, activity.startingDisplayed, + activity.startingMoved, activity.isRelaunching(), + activity.mStartingWindow); + + final boolean allDrawn = activity.allDrawn && !activity.isRelaunching(); + if (!allDrawn && !activity.startingDisplayed && !activity.startingMoved) { return false; } - for (int i = 0; i < apps.size(); i++) { - WindowContainer wc = apps.valueAt(i); - final ActivityRecord activity = getAppFromContainer(wc); - if (activity == null) { - continue; - } - ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, - "Check opening app=%s: allDrawn=%b startingDisplayed=%b " - + "startingMoved=%b isRelaunching()=%b startingWindow=%s", - activity, activity.allDrawn, activity.startingDisplayed, - activity.startingMoved, activity.isRelaunching(), - activity.mStartingWindow); + if (allDrawn) { + outReasons.put(activity, APP_TRANSITION_WINDOWS_DRAWN); + } else { + outReasons.put(activity, + activity.mStartingData instanceof SplashScreenStartingData + ? APP_TRANSITION_SPLASH_SCREEN + : APP_TRANSITION_SNAPSHOT); + } + } + + // We also need to wait for the specs to be fetched, if needed. + if (mDisplayContent.mAppTransition.isFetchingAppTransitionsSpecs()) { + ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "isFetchingAppTransitionSpecs=true"); + return false; + } + if (!mDisplayContent.mUnknownAppVisibilityController.allResolved()) { + ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "unknownApps is not empty: %s", + mDisplayContent.mUnknownAppVisibilityController.getDebugMessage()); + return false; + } - final boolean allDrawn = activity.allDrawn && !activity.isRelaunching(); - if (!allDrawn && !activity.startingDisplayed && !activity.startingMoved) { - return false; - } - if (allDrawn) { - outReasons.put(activity, APP_TRANSITION_WINDOWS_DRAWN); - } else { - outReasons.put(activity, - activity.mStartingData instanceof SplashScreenStartingData - ? APP_TRANSITION_SPLASH_SCREEN - : APP_TRANSITION_SNAPSHOT); - } - } + // If the wallpaper is visible, we need to check it's ready too. + return !mWallpaperControllerLocked.isWallpaperVisible() + || mWallpaperControllerLocked.wallpaperTransitionReady(); + } - // We also need to wait for the specs to be fetched, if needed. - if (mDisplayContent.mAppTransition.isFetchingAppTransitionsSpecs()) { - ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "isFetchingAppTransitionSpecs=true"); - return false; - } + private boolean transitionGoodToGoForTaskFragments() { + if (mDisplayContent.mAppTransition.isTimeout()) { + return true; + } - if (!mDisplayContent.mUnknownAppVisibilityController.allResolved()) { - ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "unknownApps is not empty: %s", - mDisplayContent.mUnknownAppVisibilityController.getDebugMessage()); - return false; - } + // Check all Tasks in this transition. This is needed because new TaskFragment created for + // launching activity may not be in the tracking lists, but we still want to wait for the + // activity launch to start the transition. + final ArraySet<Task> rootTasks = new ArraySet<>(); + for (int i = mDisplayContent.mOpeningApps.size() - 1; i >= 0; i--) { + rootTasks.add(mDisplayContent.mOpeningApps.valueAt(i).getRootTask()); + } + for (int i = mDisplayContent.mClosingApps.size() - 1; i >= 0; i--) { + rootTasks.add(mDisplayContent.mClosingApps.valueAt(i).getRootTask()); + } + for (int i = mDisplayContent.mChangingContainers.size() - 1; i >= 0; i--) { + rootTasks.add( + findRootTaskFromContainer(mDisplayContent.mChangingContainers.valueAt(i))); + } - // If the wallpaper is visible, we need to check it's ready too. - boolean wallpaperReady = !mWallpaperControllerLocked.isWallpaperVisible() || - mWallpaperControllerLocked.wallpaperTransitionReady(); - if (wallpaperReady) { - return true; + // Organized TaskFragment can be empty for two situations: + // 1. New created and is waiting for Activity launch. In this case, we want to wait for + // the Activity launch to trigger the transition. + // 2. Last Activity is just removed. In this case, we want to wait for organizer to + // remove the TaskFragment because it may also want to change other TaskFragments in + // the same transition. + for (int i = rootTasks.size() - 1; i >= 0; i--) { + final Task rootTask = rootTasks.valueAt(i); + final boolean notReady = rootTask.forAllLeafTaskFragments(taskFragment -> { + if (!taskFragment.isReadyToTransit()) { + ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Organized TaskFragment is not ready= %s", + taskFragment); + return true; + } + return false; + }); + if (notReady) { + return false; } - return false; } return true; } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index a3626d215cf4..3f6708d1843c 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2347,11 +2347,6 @@ class Task extends TaskFragment { return getRootTask().mTaskId; } - @Nullable - Task getRootTask() { - return getRootTaskFragment().asTask(); - } - /** @return the first organized task. */ @Nullable Task getOrganizedTask() { diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index a902ca913773..77e7e535504d 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -459,6 +459,11 @@ class TaskFragment extends WindowContainer<WindowContainer> { return parentTaskFragment == null ? this : parentTaskFragment.getRootTaskFragment(); } + @Nullable + Task getRootTask() { + return getRootTaskFragment().asTask(); + } + @Override TaskFragment asTaskFragment() { return this; @@ -2156,6 +2161,13 @@ class TaskFragment extends WindowContainer<WindowContainer> { return mTaskFragmentOrganizer != null; } + boolean isReadyToTransit() { + // We don't want to start the transition if the organized TaskFragment is empty, unless + // it is requested to be removed. + return !isOrganizedTaskFragment() || getTopNonFinishingActivity() != null + || mIsRemovalRequested; + } + /** Clear {@link #mLastPausedActivity} for all {@link TaskFragment} children */ void clearLastPausedActivity() { forAllTaskFragments(taskFragment -> taskFragment.mLastPausedActivity = null); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java index 0d0cec7f5dce..d6d7f07b2ef3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java @@ -31,13 +31,17 @@ import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.os.Binder; @@ -791,4 +795,38 @@ public class AppTransitionControllerTest extends WindowTestsBase { verify(mDisplayContent.mAppTransition) .overridePendingAppTransitionRemote(adapter, false /* sync */); } + + @Test + public void testTransitionGoodToGoForTaskFragments() { + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + final Task task = createTask(mDisplayContent); + final TaskFragment changeTaskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .createActivityCount(1) + .setOrganizer(organizer) + .build(); + final TaskFragment emptyTaskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .setOrganizer(organizer) + .build(); + changeTaskFragment.getTopMostActivity().allDrawn = true; + mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_CHANGE, 0); + mDisplayContent.mChangingContainers.add(changeTaskFragment); + spyOn(mDisplayContent.mAppTransition); + spyOn(emptyTaskFragment); + + mDisplayContent.mAppTransitionController.handleAppTransitionReady(); + + // Transition not ready because there is an empty non-finishing TaskFragment. + verify(mDisplayContent.mAppTransition, never()).goodToGo(anyInt(), any()); + + doReturn(true).when(emptyTaskFragment).hasChild(); + emptyTaskFragment.remove(false /* withTransition */, "test"); + + mDisplayContent.mAppTransitionController.handleAppTransitionReady(); + + // Transition ready because the empty (no running activity) TaskFragment is requested to be + // removed. + verify(mDisplayContent.mAppTransition).goodToGo(anyInt(), any()); + } }
\ No newline at end of file |