Merge "Don't start app transition when there is empty TaskFragment" into sc-v2-dev am: abdb320b70
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15878941 Change-Id: I784a8bf0034e285e228d0916bf7760229d045b87
This commit is contained in:
@@ -553,6 +553,12 @@
|
|||||||
"group": "WM_SHOW_TRANSACTIONS",
|
"group": "WM_SHOW_TRANSACTIONS",
|
||||||
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
|
"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": {
|
"-1499134947": {
|
||||||
"message": "Removing starting %s from %s",
|
"message": "Removing starting %s from %s",
|
||||||
"level": "VERBOSE",
|
"level": "VERBOSE",
|
||||||
|
|||||||
@@ -165,8 +165,8 @@ public class AppTransitionController {
|
|||||||
void handleAppTransitionReady() {
|
void handleAppTransitionReady() {
|
||||||
mTempTransitionReasons.clear();
|
mTempTransitionReasons.clear();
|
||||||
if (!transitionGoodToGo(mDisplayContent.mOpeningApps, mTempTransitionReasons)
|
if (!transitionGoodToGo(mDisplayContent.mOpeningApps, mTempTransitionReasons)
|
||||||
|| !transitionGoodToGo(mDisplayContent.mChangingContainers,
|
|| !transitionGoodToGo(mDisplayContent.mChangingContainers, mTempTransitionReasons)
|
||||||
mTempTransitionReasons)) {
|
|| !transitionGoodToGoForTaskFragments()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");
|
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) {
|
static ActivityRecord getAppFromContainer(WindowContainer wc) {
|
||||||
return wc.asTaskFragment() != null ? wc.asTaskFragment().getTopNonFinishingActivity()
|
return wc.asTaskFragment() != null ? wc.asTaskFragment().getTopNonFinishingActivity()
|
||||||
: wc.asActivityRecord();
|
: wc.asActivityRecord();
|
||||||
@@ -972,11 +979,12 @@ public class AppTransitionController {
|
|||||||
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
|
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
|
||||||
"Checking %d opening apps (frozen=%b timeout=%b)...", apps.size(),
|
"Checking %d opening apps (frozen=%b timeout=%b)...", apps.size(),
|
||||||
mService.mDisplayFrozen, mDisplayContent.mAppTransition.isTimeout());
|
mService.mDisplayFrozen, mDisplayContent.mAppTransition.isTimeout());
|
||||||
|
if (mDisplayContent.mAppTransition.isTimeout()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
final ScreenRotationAnimation screenRotationAnimation = mService.mRoot.getDisplayContent(
|
final ScreenRotationAnimation screenRotationAnimation = mService.mRoot.getDisplayContent(
|
||||||
Display.DEFAULT_DISPLAY).getRotationAnimation();
|
Display.DEFAULT_DISPLAY).getRotationAnimation();
|
||||||
|
|
||||||
if (!mDisplayContent.mAppTransition.isTimeout()) {
|
|
||||||
// Imagine the case where we are changing orientation due to an app transition, but a
|
// 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
|
// 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
|
// change for our transition because we need to wait for the rotation animation to
|
||||||
@@ -1003,7 +1011,6 @@ public class AppTransitionController {
|
|||||||
activity.startingMoved, activity.isRelaunching(),
|
activity.startingMoved, activity.isRelaunching(),
|
||||||
activity.mStartingWindow);
|
activity.mStartingWindow);
|
||||||
|
|
||||||
|
|
||||||
final boolean allDrawn = activity.allDrawn && !activity.isRelaunching();
|
final boolean allDrawn = activity.allDrawn && !activity.isRelaunching();
|
||||||
if (!allDrawn && !activity.startingDisplayed && !activity.startingMoved) {
|
if (!allDrawn && !activity.startingDisplayed && !activity.startingMoved) {
|
||||||
return false;
|
return false;
|
||||||
@@ -1031,12 +1038,49 @@ public class AppTransitionController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the wallpaper is visible, we need to check it's ready too.
|
// If the wallpaper is visible, we need to check it's ready too.
|
||||||
boolean wallpaperReady = !mWallpaperControllerLocked.isWallpaperVisible() ||
|
return !mWallpaperControllerLocked.isWallpaperVisible()
|
||||||
mWallpaperControllerLocked.wallpaperTransitionReady();
|
|| mWallpaperControllerLocked.wallpaperTransitionReady();
|
||||||
if (wallpaperReady) {
|
}
|
||||||
|
|
||||||
|
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 true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
});
|
||||||
|
if (notReady) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2347,11 +2347,6 @@ class Task extends TaskFragment {
|
|||||||
return getRootTask().mTaskId;
|
return getRootTask().mTaskId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
Task getRootTask() {
|
|
||||||
return getRootTaskFragment().asTask();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return the first organized task. */
|
/** @return the first organized task. */
|
||||||
@Nullable
|
@Nullable
|
||||||
Task getOrganizedTask() {
|
Task getOrganizedTask() {
|
||||||
|
|||||||
@@ -459,6 +459,11 @@ class TaskFragment extends WindowContainer<WindowContainer> {
|
|||||||
return parentTaskFragment == null ? this : parentTaskFragment.getRootTaskFragment();
|
return parentTaskFragment == null ? this : parentTaskFragment.getRootTaskFragment();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
Task getRootTask() {
|
||||||
|
return getRootTaskFragment().asTask();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
TaskFragment asTaskFragment() {
|
TaskFragment asTaskFragment() {
|
||||||
return this;
|
return this;
|
||||||
@@ -2153,6 +2158,13 @@ class TaskFragment extends WindowContainer<WindowContainer> {
|
|||||||
return mTaskFragmentOrganizer != null;
|
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 */
|
/** Clear {@link #mLastPausedActivity} for all {@link TaskFragment} children */
|
||||||
void clearLastPausedActivity() {
|
void clearLastPausedActivity() {
|
||||||
forAllTaskFragments(taskFragment -> taskFragment.mLastPausedActivity = null);
|
forAllTaskFragments(taskFragment -> taskFragment.mLastPausedActivity = null);
|
||||||
|
|||||||
@@ -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_OPEN;
|
||||||
import static android.view.WindowManager.TRANSIT_TO_FRONT;
|
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 com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
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.doCallRealMethod;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
@@ -791,4 +795,38 @@ public class AppTransitionControllerTest extends WindowTestsBase {
|
|||||||
verify(mDisplayContent.mAppTransition)
|
verify(mDisplayContent.mAppTransition)
|
||||||
.overridePendingAppTransitionRemote(adapter, false /* sync */);
|
.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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user