summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/etc/services.core.protolog.json12
-rw-r--r--services/core/java/com/android/server/wm/AppTransitionController.java156
-rw-r--r--services/core/java/com/android/server/wm/Task.java5
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java38
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