Merge "Don't start app transition when there is empty TaskFragment" into sc-v2-dev

This commit is contained in:
Chris Li
2021-09-24 03:34:03 +00:00
committed by Android (Google) Code Review
5 changed files with 163 additions and 68 deletions

View File

@@ -553,6 +553,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",

View File

@@ -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,72 +979,109 @@ 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");
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;
}
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;
}
// If the wallpaper is visible, we need to check it's ready too.
boolean wallpaperReady = !mWallpaperControllerLocked.isWallpaperVisible() ||
mWallpaperControllerLocked.wallpaperTransitionReady();
if (wallpaperReady) {
return true;
}
// 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;
}
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;
}
// If the wallpaper is visible, we need to check it's ready too.
return !mWallpaperControllerLocked.isWallpaperVisible()
|| mWallpaperControllerLocked.wallpaperTransitionReady();
}
private boolean transitionGoodToGoForTaskFragments() {
if (mDisplayContent.mAppTransition.isTimeout()) {
return true;
}
// 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)));
}
// 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 true;
}

View File

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

View File

@@ -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;
@@ -2153,6 +2158,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);

View File

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