diff options
author | Issei Suzuki <issei@google.com> | 2019-12-20 12:44:09 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2019-12-20 12:44:09 +0000 |
commit | 8cac70a65e4f270a253c010ab86a288cc747db1f (patch) | |
tree | f4f540b03efd44d85b1d4e6061041d2c2c4efdf4 | |
parent | 944c611bd7c9c00541cf1fc7fe731b5ec41747be (diff) | |
parent | 0ae90d0cca755260b1c4ca532b50fcbc7ba34ef0 (diff) |
Merge "Promote app transition target its ancestor if possible."
5 files changed, 515 insertions, 32 deletions
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 9917cee00eaf..1e98e3afa97c 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -1741,6 +1741,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS_ANIM", "at": "com\/android\/server\/wm\/AppTransition.java" }, + "1460759282": { + "message": "getAnimationTarget in=%s, out=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_APP_TRANSITIONS_ANIM", + "at": "com\/android\/server\/wm\/AppTransitionController.java" + }, "1469292670": { "message": "Changing focus from %s to %s displayId=%d Callers=%s", "level": "VERBOSE", diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index c57ac11723b4..b154da412593 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -228,11 +228,10 @@ final class AccessibilityController { } } - public void onAppWindowTransitionLocked(WindowState windowState, int transition) { - final int displayId = windowState.getDisplayId(); + public void onAppWindowTransitionLocked(int displayId, int transition) { final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId); if (displayMagnifier != null) { - displayMagnifier.onAppWindowTransitionLocked(windowState, transition); + displayMagnifier.onAppWindowTransitionLocked(displayId, transition); } // Not relevant for the window observer. } @@ -446,11 +445,11 @@ final class AccessibilityController { mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_ROTATION_CHANGED); } - public void onAppWindowTransitionLocked(WindowState windowState, int transition) { + public void onAppWindowTransitionLocked(int displayId, int transition) { if (DEBUG_WINDOW_TRANSITIONS) { Slog.i(LOG_TAG, "Window transition: " + AppTransition.appTransitionToString(transition) - + " displayId: " + windowState.getDisplayId()); + + " displayId: " + displayId); } final boolean magnifying = mMagnifedViewport.isMagnifyingLocked(); if (magnifying) { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index bfc626828fa0..91149968e45b 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -4159,7 +4159,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * transition. * * <p class="note><strong>Note:</strong> If the visibility of this {@link ActivityRecord} is - * already set to {@link #visible}, we don't need to update the visibility. So {@code false} is + * already set to {@link #mVisible}, we don't need to update the visibility. So {@code false} is * returned.</p> * * @param visible {@code true} if this {@link ActivityRecord} should become visible, @@ -4169,16 +4169,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A */ boolean shouldApplyAnimation(boolean visible) { // Allow for state update and animation to be applied if: - // * token is transitioning visibility state - // * or the token was marked as hidden and is exiting before we had a chance to play the + // * activity is transitioning visibility state + // * or the activity was marked as hidden and is exiting before we had a chance to play the // transition animation - // * or this is an opening app and windows are being replaced - // * or the token is the opening app and visible while opening task behind existing one. - final DisplayContent displayContent = getDisplayContent(); + // * or this is an opening app and windows are being replaced (e.g. freeform window to + // normal window). return isVisible() != visible || (!isVisible() && mIsExiting) - || (visible && forAllWindows(WindowState::waitingForReplacement, true)) - || (visible && displayContent.mOpeningApps.contains(this) - && displayContent.mAppTransition.getAppTransition() == TRANSIT_TASK_OPEN_BEHIND); + || (visible && forAllWindows(WindowState::waitingForReplacement, true)); } /** diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 6ea0650df37a..6e09b94a909a 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -48,6 +48,7 @@ import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_S import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN; import static com.android.server.wm.AppTransition.isKeyguardGoingAwayTransit; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; +import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; @@ -69,6 +70,8 @@ import android.view.animation.Animation; import com.android.internal.annotations.VisibleForTesting; import com.android.server.protolog.common.ProtoLog; +import java.util.ArrayList; +import java.util.LinkedList; import java.util.function.Predicate; @@ -180,11 +183,7 @@ public class AppTransitionController { final int layoutRedo; mService.mSurfaceAnimationRunner.deferStartingAnimations(); try { - // TODO: Apply an app transition animation on TaskStack instead of ActivityRecord when - // appropriate. - applyAnimations(mDisplayContent.mClosingApps, transit, false /* visible */, - animLp, voiceInteraction); - applyAnimations(mDisplayContent.mOpeningApps, transit, true /* visible */, + applyAnimations(mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, transit, animLp, voiceInteraction); handleClosingApps(); handleOpeningApps(); @@ -350,9 +349,9 @@ public class AppTransitionController { } /** - * Apply an app transition animation on a set of {@link ActivityRecord} + * Apply animation to the set of window containers. * - * @param apps The list of apps to which an app transition animation applies. + * @param wcs The list of {@link WindowContainer}s to which an app transition animation applies. * @param transit The current transition type. * @param visible {@code true} if the apps becomes visible, {@code false} if the apps becomes * invisible. @@ -360,24 +359,150 @@ public class AppTransitionController { * @param voiceInteraction {@code true} if one of the apps in this transition belongs to a voice * interaction session driving task. */ - private void applyAnimations(ArraySet<ActivityRecord> apps, @TransitionType int transit, + private void applyAnimations(ArraySet<WindowContainer> wcs, @TransitionType int transit, boolean visible, LayoutParams animLp, boolean voiceInteraction) { - final int appsCount = apps.size(); + final int appsCount = wcs.size(); for (int i = 0; i < appsCount; i++) { + final WindowContainer wc = wcs.valueAt(i); + wc.applyAnimation(animLp, transit, visible, voiceInteraction); + } + } + + /** + * Find WindowContainers to be animated from a set of opening and closing apps. We will promote + * animation targets to higher level in the window hierarchy if possible. + * + * @param visible {@code true} to get animation targets for opening apps, {@code false} to get + * animation targets for closing apps. + * @return {@link WindowContainer}s to be animated. + */ + @VisibleForTesting + static ArraySet<WindowContainer> getAnimationTargets( + ArraySet<ActivityRecord> openingApps, ArraySet<ActivityRecord> closingApps, + boolean visible) { + + // The candidates of animation targets, which might be able to promote to higher level. + final LinkedList<WindowContainer> candidates = new LinkedList<>(); + final ArraySet<ActivityRecord> apps = visible ? openingApps : closingApps; + for (int i = 0; i < apps.size(); ++i) { final ActivityRecord app = apps.valueAt(i); - if (transit != WindowManager.TRANSIT_UNSET && app.shouldApplyAnimation(visible)) { - ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Changing app %s visible=%b performLayout=%b", + if (app.shouldApplyAnimation(visible)) { + candidates.add(app); + ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, + "Changing app %s visible=%b performLayout=%b", app, app.isVisible(), false); - if (!app.mUseTransferredAnimation) { - app.applyAnimation(animLp, transit, visible, voiceInteraction); + } + } + + if (!WindowManagerService.sHierarchicalAnimations) { + return new ArraySet<>(candidates); + } + + final ArraySet<ActivityRecord> otherApps = visible ? closingApps : openingApps; + // Ancestors of closing apps while finding animation targets for opening apps, or ancestors + // of opening apps while finding animation targets for closing apps. + final ArraySet<WindowContainer> otherAncestors = new ArraySet<>(); + for (int i = 0; i < otherApps.size(); ++i) { + for (WindowContainer wc = otherApps.valueAt(i); wc != null; wc = wc.getParent()) { + otherAncestors.add(wc); + } + } + + // The final animation targets which cannot promote to higher level anymore. + final ArraySet<WindowContainer> targets = new ArraySet<>(); + final ArrayList<WindowContainer> siblings = new ArrayList<>(); + while (!candidates.isEmpty()) { + final WindowContainer current = candidates.removeFirst(); + final WindowContainer parent = current.getParent(); + boolean canPromote = true; + + if (parent == null) { + canPromote = false; + } else { + // In case a descendant of the parent belongs to the other group, we cannot promote + // the animation target from "current" to the parent. + // + // Example: Imagine we're checking if we can animate a Task instead of a set of + // ActivityRecords. In case an activity starts a new activity within a same Task, + // an ActivityRecord of an existing activity belongs to the opening apps, at the + // same time, the other ActivityRecord of a new activity belongs to the closing + // apps. In this case, we cannot promote the animation target to Task level, but + // need to animate each individual activity. + // + // [Task] +- [ActivityRecord1] (in opening apps) + // +- [ActivityRecord2] (in closing apps) + if (otherAncestors.contains(parent)) { + canPromote = false; } - final WindowState window = app.findMainWindow(); - final AccessibilityController accessibilityController = - app.mWmService.mAccessibilityController; - if (window != null && accessibilityController != null) { - accessibilityController.onAppWindowTransitionLocked(window, transit); + + // Find all siblings of the current WindowContainer in "candidates", move them into + // a separate list "siblings", and checks if an animation target can be promoted + // to its parent. + // + // We can promote an animation target to its parent if and only if all visible + // siblings will be animating. + // + // Example: Imagine that a Task contains two visible activity record, but only one + // of them is included in the opening apps and the other belongs to neither opening + // or closing apps. This happens when an activity launches another translucent + // activity in the same Task. In this case, we cannot animate Task, but have to + // animate each activity, otherwise an activity behind the translucent activity also + // animates. + // + // [Task] +- [ActivityRecord1] (visible, in opening apps) + // +- [ActivityRecord2] (visible, not in opening apps) + siblings.clear(); + for (int j = 0; j < parent.getChildCount(); ++j) { + final WindowContainer sibling = parent.getChildAt(j); + if (sibling == current || candidates.remove(sibling)) { + siblings.add(sibling); + } else if (sibling.isVisible()) { + canPromote = false; + } } } + + if (canPromote) { + candidates.add(parent); + } else { + targets.addAll(siblings); + } + } + ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "getAnimationTarget in=%s, out=%s", + apps, targets); + return targets; + } + + /** + * Apply an app transition animation based on a set of {@link ActivityRecord} + * + * @param openingApps The list of opening apps to which an app transition animation applies. + * @param closingApps The list of closing apps to which an app transition animation applies. + * @param transit The current transition type. + * @param animLp Layout parameters in which an app transition animation runs. + * @param voiceInteraction {@code true} if one of the apps in this transition belongs to a voice + * interaction session driving task. + */ + private void applyAnimations(ArraySet<ActivityRecord> openingApps, + ArraySet<ActivityRecord> closingApps, @TransitionType int transit, + LayoutParams animLp, boolean voiceInteraction) { + if (transit == WindowManager.TRANSIT_UNSET + || (openingApps.isEmpty() && closingApps.isEmpty())) { + return; + } + + final ArraySet<WindowContainer> openingWcs = getAnimationTargets( + openingApps, closingApps, true /* visible */); + final ArraySet<WindowContainer> closingWcs = getAnimationTargets( + openingApps, closingApps, false /* visible */); + applyAnimations(openingWcs, transit, true /* visible */, animLp, voiceInteraction); + applyAnimations(closingWcs, transit, false /* visible */, animLp, voiceInteraction); + + final AccessibilityController accessibilityController = + mDisplayContent.mWmService.mAccessibilityController; + if (accessibilityController != null) { + accessibilityController.onAppWindowTransitionLocked( + mDisplayContent.getDisplayId(), transit); } } 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 d415f25baab9..1311889d5605 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN; import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE; import static android.view.WindowManager.TRANSIT_TASK_CLOSE; @@ -29,6 +30,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.platform.test.annotations.Presubmit; +import android.util.ArraySet; import android.view.WindowManager; import androidx.test.filters.FlakyTest; @@ -116,4 +118,358 @@ public class AppTransitionControllerTest extends WindowTestsBase { assertTrue(mAppTransitionController.isTransitWithinTask(TRANSIT_ACTIVITY_OPEN, task)); assertFalse(mAppTransitionController.isTransitWithinTask(TRANSIT_TASK_OPEN, task)); } + + @Test + public void testGetAnimationTargets_noHierarchicalAnimations() { + WindowManagerService.sHierarchicalAnimations = false; + + // [DisplayContent] -+- [TaskStack1] - [Task1] - [ActivityRecord1] (opening, invisible) + // +- [TaskStack2] - [Task2] - [ActivityRecord2] (closing, visible) + final ActivityStack stack1 = createTaskStackOnDisplay(mDisplayContent); + final ActivityRecord activity1 = WindowTestUtils.createTestActivityRecord(stack1); + activity1.setVisible(false); + activity1.mVisibleRequested = true; + + final ActivityStack stack2 = createTaskStackOnDisplay(mDisplayContent); + final ActivityRecord activity2 = WindowTestUtils.createTestActivityRecord(stack2); + + final ArraySet<ActivityRecord> opening = new ArraySet<>(); + opening.add(activity1); + final ArraySet<ActivityRecord> closing = new ArraySet<>(); + closing.add(activity2); + + // Don't promote when the flag is disabled. + assertEquals( + new ArraySet<>(new WindowContainer[]{activity1}), + AppTransitionController.getAnimationTargets( + opening, closing, true /* visible */)); + assertEquals( + new ArraySet<>(new WindowContainer[]{activity2}), + AppTransitionController.getAnimationTargets( + opening, closing, false /* visible */)); + } + + @Test + public void testGetAnimationTargets_visibilityAlreadyUpdated() { + // [DisplayContent] -+- [TaskStack1] - [Task1] - [ActivityRecord1] (opening, visible) + // +- [TaskStack2] - [Task2] - [ActivityRecord2] (closing, invisible) + final ActivityStack stack1 = createTaskStackOnDisplay(mDisplayContent); + final ActivityRecord activity1 = WindowTestUtils.createTestActivityRecord(stack1); + + final ActivityStack stack2 = createTaskStackOnDisplay(mDisplayContent); + final ActivityRecord activity2 = WindowTestUtils.createTestActivityRecord(stack2); + activity2.setVisible(false); + activity2.mVisibleRequested = false; + + final ArraySet<ActivityRecord> opening = new ArraySet<>(); + opening.add(activity1); + final ArraySet<ActivityRecord> closing = new ArraySet<>(); + closing.add(activity2); + + // No animation, since visibility of the opening and closing apps are already updated + // outside of AppTransition framework. + WindowManagerService.sHierarchicalAnimations = false; + assertEquals( + new ArraySet<>(), + AppTransitionController.getAnimationTargets( + opening, closing, true /* visible */)); + assertEquals( + new ArraySet<>(), + AppTransitionController.getAnimationTargets( + opening, closing, false /* visible */)); + + WindowManagerService.sHierarchicalAnimations = true; + assertEquals( + new ArraySet<>(), + AppTransitionController.getAnimationTargets( + opening, closing, true /* visible */)); + assertEquals( + new ArraySet<>(), + AppTransitionController.getAnimationTargets( + opening, closing, false /* visible */)); + } + + @Test + public void testGetAnimationTargets_exitingBeforeTransition() { + final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); + final ActivityRecord activity = WindowTestUtils.createTestActivityRecord(stack); + activity.setVisible(false); + activity.mIsExiting = true; + + final ArraySet<ActivityRecord> closing = new ArraySet<>(); + closing.add(activity); + + // Animate closing apps even if it's not visible when it is exiting before we had a chance + // to play the transition animation. + WindowManagerService.sHierarchicalAnimations = false; + assertEquals( + new ArraySet<>(new WindowContainer[]{activity}), + AppTransitionController.getAnimationTargets( + new ArraySet<>(), closing, false /* visible */)); + + WindowManagerService.sHierarchicalAnimations = true; + assertEquals( + new ArraySet<>(new WindowContainer[]{stack}), + AppTransitionController.getAnimationTargets( + new ArraySet<>(), closing, false /* visible */)); + } + + @Test + public void testGetAnimationTargets_windowsAreBeingReplaced() { + // [DisplayContent] -+- [TaskStack1] - [Task1] - [ActivityRecord1] (opening, visible) + // +- [AppWindow1] (being-replaced) + // +- [TaskStack2] - [Task2] - [ActivityRecord2] (closing, invisible) + // +- [AppWindow2] (being-replaced) + final ActivityStack stack1 = createTaskStackOnDisplay(mDisplayContent); + final ActivityRecord activity1 = WindowTestUtils.createTestActivityRecord(stack1); + final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams( + TYPE_BASE_APPLICATION); + attrs.setTitle("AppWindow1"); + final WindowTestUtils.TestWindowState appWindow1 = createWindowState(attrs, activity1); + appWindow1.mWillReplaceWindow = true; + activity1.addWindow(appWindow1); + + final ActivityStack stack2 = createTaskStackOnDisplay(mDisplayContent); + final ActivityRecord activity2 = WindowTestUtils.createTestActivityRecord(stack2); + activity2.setVisible(false); + activity2.mVisibleRequested = false; + attrs.setTitle("AppWindow2"); + final WindowTestUtils.TestWindowState appWindow2 = createWindowState(attrs, activity2); + appWindow2.mWillReplaceWindow = true; + activity2.addWindow(appWindow2); + + final ArraySet<ActivityRecord> opening = new ArraySet<>(); + opening.add(activity1); + final ArraySet<ActivityRecord> closing = new ArraySet<>(); + closing.add(activity2); + + // Animate opening apps even if it's already visible in case its windows are being replaced. + // Don't animate closing apps if it's already invisible even though its windows are being + // replaced. + WindowManagerService.sHierarchicalAnimations = false; + assertEquals( + new ArraySet<>(new WindowContainer[]{activity1}), + AppTransitionController.getAnimationTargets( + opening, closing, true /* visible */)); + assertEquals( + new ArraySet<>(new WindowContainer[]{}), + AppTransitionController.getAnimationTargets( + opening, closing, false /* visible */)); + + WindowManagerService.sHierarchicalAnimations = true; + assertEquals( + new ArraySet<>(new WindowContainer[]{stack1}), + AppTransitionController.getAnimationTargets( + opening, closing, true /* visible */)); + assertEquals( + new ArraySet<>(new WindowContainer[]{}), + AppTransitionController.getAnimationTargets( + opening, closing, false /* visible */)); + } + + @Test + public void testGetAnimationTargets_openingClosingInDifferentTask() { + WindowManagerService.sHierarchicalAnimations = true; + + // [DisplayContent] -+- [TaskStack1] - [Task1] -+- [ActivityRecord1] (opening, invisible) + // | +- [ActivityRecord2] (invisible) + // | + // +- [TaskStack2] - [Task2] -+- [ActivityRecord3] (closing, visible) + // +- [ActivityRecord4] (invisible) + final ActivityStack stack1 = createTaskStackOnDisplay(mDisplayContent); + final Task task1 = createTaskInStack(stack1, 0 /* userId */); + final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask( + mDisplayContent, task1); + activity1.setVisible(false); + activity1.mVisibleRequested = true; + final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask( + mDisplayContent, task1); + activity2.setVisible(false); + activity2.mVisibleRequested = false; + + final ActivityStack stack2 = createTaskStackOnDisplay(mDisplayContent); + final Task task2 = createTaskInStack(stack2, 0 /* userId */); + final ActivityRecord activity3 = WindowTestUtils.createActivityRecordInTask( + mDisplayContent, task2); + final ActivityRecord activity4 = WindowTestUtils.createActivityRecordInTask( + mDisplayContent, task2); + activity4.setVisible(false); + activity4.mVisibleRequested = false; + + final ArraySet<ActivityRecord> opening = new ArraySet<>(); + opening.add(activity1); + final ArraySet<ActivityRecord> closing = new ArraySet<>(); + closing.add(activity3); + + // Promote animation targets to TaskStack level. Invisible ActivityRecords don't affect + // promotion decision. + assertEquals( + new ArraySet<>(new WindowContainer[]{stack1}), + AppTransitionController.getAnimationTargets( + opening, closing, true /* visible */)); + assertEquals( + new ArraySet<>(new WindowContainer[]{stack2}), + AppTransitionController.getAnimationTargets( + opening, closing, false /* visible */)); + } + + @Test + public void testGetAnimationTargets_openingClosingInSameTask() { + WindowManagerService.sHierarchicalAnimations = true; + + // [DisplayContent] - [TaskStack] - [Task] -+- [ActivityRecord1] (opening, invisible) + // +- [ActivityRecord2] (closing, visible) + final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stack, 0 /* userId */); + final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask( + mDisplayContent, task); + activity1.setVisible(false); + activity1.mVisibleRequested = true; + final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask( + mDisplayContent, task); + + final ArraySet<ActivityRecord> opening = new ArraySet<>(); + opening.add(activity1); + final ArraySet<ActivityRecord> closing = new ArraySet<>(); + closing.add(activity2); + + // Don't promote an animation target to Task level, since the same task contains both + // opening and closing app. + assertEquals( + new ArraySet<>(new WindowContainer[]{activity1}), + AppTransitionController.getAnimationTargets( + opening, closing, true /* visible */)); + assertEquals( + new ArraySet<>(new WindowContainer[]{activity2}), + AppTransitionController.getAnimationTargets( + opening, closing, false /* visible */)); + } + + @Test + public void testGetAnimationTargets_animateOnlyTranslucentApp() { + WindowManagerService.sHierarchicalAnimations = true; + + // [DisplayContent] -+- [TaskStack1] - [Task1] -+- [ActivityRecord1] (opening, invisible) + // | +- [ActivityRecord2] (visible) + // | + // +- [TaskStack2] - [Task2] -+- [ActivityRecord3] (closing, visible) + // +- [ActivityRecord4] (visible) + + final ActivityStack stack1 = createTaskStackOnDisplay(mDisplayContent); + final Task task1 = createTaskInStack(stack1, 0 /* userId */); + final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask( + mDisplayContent, task1); + activity1.setVisible(false); + activity1.mVisibleRequested = true; + activity1.setOccludesParent(false); + + final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask( + mDisplayContent, task1); + + final ActivityStack stack2 = createTaskStackOnDisplay(mDisplayContent); + final Task task2 = createTaskInStack(stack2, 0 /* userId */); + final ActivityRecord activity3 = WindowTestUtils.createActivityRecordInTask( + mDisplayContent, task2); + activity3.setOccludesParent(false); + final ActivityRecord activity4 = WindowTestUtils.createActivityRecordInTask( + mDisplayContent, task2); + + final ArraySet<ActivityRecord> opening = new ArraySet<>(); + opening.add(activity1); + final ArraySet<ActivityRecord> closing = new ArraySet<>(); + closing.add(activity3); + + // Don't promote an animation target to Task level, since opening (closing) app is + // translucent and is displayed over other non-animating app. + assertEquals( + new ArraySet<>(new WindowContainer[]{activity1}), + AppTransitionController.getAnimationTargets( + opening, closing, true /* visible */)); + assertEquals( + new ArraySet<>(new WindowContainer[]{activity3}), + AppTransitionController.getAnimationTargets( + opening, closing, false /* visible */)); + } + + @Test + public void testGetAnimationTargets_animateTranslucentAndOpaqueApps() { + WindowManagerService.sHierarchicalAnimations = true; + + // [DisplayContent] -+- [TaskStack1] - [Task1] -+- [ActivityRecord1] (opening, invisible) + // | +- [ActivityRecord2] (opening, invisible) + // | + // +- [TaskStack2] - [Task2] -+- [ActivityRecord3] (closing, visible) + // +- [ActivityRecord4] (closing, visible) + + final ActivityStack stack1 = createTaskStackOnDisplay(mDisplayContent); + final Task task1 = createTaskInStack(stack1, 0 /* userId */); + final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask( + mDisplayContent, task1); + activity1.setVisible(false); + activity1.mVisibleRequested = true; + activity1.setOccludesParent(false); + + final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask( + mDisplayContent, task1); + activity2.setVisible(false); + activity2.mVisibleRequested = true; + + final ActivityStack stack2 = createTaskStackOnDisplay(mDisplayContent); + final Task task2 = createTaskInStack(stack2, 0 /* userId */); + final ActivityRecord activity3 = WindowTestUtils.createActivityRecordInTask( + mDisplayContent, task2); + activity3.setOccludesParent(false); + final ActivityRecord activity4 = WindowTestUtils.createActivityRecordInTask( + mDisplayContent, task2); + + final ArraySet<ActivityRecord> opening = new ArraySet<>(); + opening.add(activity1); + opening.add(activity2); + final ArraySet<ActivityRecord> closing = new ArraySet<>(); + closing.add(activity3); + closing.add(activity4); + + // Promote animation targets to TaskStack level even though opening (closing) app is + // translucent as long as all visible siblings animate at the same time. + assertEquals( + new ArraySet<>(new WindowContainer[]{stack1}), + AppTransitionController.getAnimationTargets( + opening, closing, true /* visible */)); + assertEquals( + new ArraySet<>(new WindowContainer[]{stack2}), + AppTransitionController.getAnimationTargets( + opening, closing, false /* visible */)); + } + + @Test + public void testGetAnimationTargets_stackContainsMultipleTasks() { + WindowManagerService.sHierarchicalAnimations = true; + + // [DisplayContent] - [TaskStack] -+- [Task1] - [ActivityRecord1] (opening, invisible) + // +- [Task2] - [ActivityRecord2] (closing, visible) + final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task1 = createTaskInStack(stack, 0 /* userId */); + final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask( + mDisplayContent, task1); + activity1.setVisible(false); + activity1.mVisibleRequested = true; + final Task task2 = createTaskInStack(stack, 0 /* userId */); + final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask( + mDisplayContent, task2); + + final ArraySet<ActivityRecord> opening = new ArraySet<>(); + opening.add(activity1); + final ArraySet<ActivityRecord> closing = new ArraySet<>(); + closing.add(activity2); + + // Promote animation targets up to Task level, not beyond. + assertEquals( + new ArraySet<>(new WindowContainer[]{task1}), + AppTransitionController.getAnimationTargets( + opening, closing, true /* visible */)); + assertEquals( + new ArraySet<>(new WindowContainer[]{task2}), + AppTransitionController.getAnimationTargets( + opening, closing, false /* visible */)); + } } |