From 32e6070218d4f2ce69631cc01e6b5a16b7fa5991 Mon Sep 17 00:00:00 2001 From: Chris Li Date: Thu, 9 Feb 2023 14:26:45 +0800 Subject: [PATCH] Check finishing opaque activity for closing TaskFragment Before, when the TaskFragment and its children Activity is finishing together, we always consider the TaskFragment as translucent. Now, when the TaskFragment is closing, we also check if there is any finishing Activity that is opaque. Fix: 268433205 Test: atest WmTests:TransitionTests Change-Id: I6ebeb2713aeb64717e53fb2c507dfb11611e4ec5 --- .../com/android/server/wm/TaskFragment.java | 37 ++-- .../com/android/server/wm/Transition.java | 33 +-- .../android/server/wm/TransitionTests.java | 204 ++++++++++++++++-- 3 files changed, 232 insertions(+), 42 deletions(-) diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 294e90b858a41..c2afeaf8990e0 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -904,37 +904,46 @@ class TaskFragment extends WindowContainer { * starting (about to be visible) activity that is fullscreen (opaque). * @param starting The currently starting activity or null if there is none. */ - @VisibleForTesting - boolean isTranslucent(ActivityRecord starting) { + boolean isTranslucent(@Nullable ActivityRecord starting) { if (!isAttached() || isForceHidden() || isForceTranslucent()) { return true; } final PooledPredicate p = PooledLambda.obtainPredicate(TaskFragment::isOpaqueActivity, - PooledLambda.__(ActivityRecord.class), starting); + PooledLambda.__(ActivityRecord.class), starting, false /* including*/); final ActivityRecord opaque = getActivity(p); p.recycle(); return opaque == null; } - private static boolean isOpaqueActivity(ActivityRecord r, ActivityRecord starting) { - if (r.finishing) { - // We don't factor in finishing activities when determining translucency since - // they will be gone soon. - return false; + /** + * Whether the TaskFragment should be treated as translucent for the current transition. + * This is different from {@link #isTranslucent(ActivityRecord)} as this function also checks + * finishing activities when the TaskFragment itself is becoming invisible. + */ + boolean isTranslucentForTransition() { + if (!isAttached() || isForceHidden() || isForceTranslucent()) { + return true; } + // Including finishing Activity if the TaskFragment is becoming invisible in the transition. + final boolean includingFinishing = !isVisibleRequested(); + final PooledPredicate p = PooledLambda.obtainPredicate(TaskFragment::isOpaqueActivity, + PooledLambda.__(ActivityRecord.class), null /* starting */, includingFinishing); + final ActivityRecord opaque = getActivity(p); + p.recycle(); + return opaque == null; + } + private static boolean isOpaqueActivity(@NonNull ActivityRecord r, + @Nullable ActivityRecord starting, boolean includingFinishing) { if (!r.visibleIgnoringKeyguard && r != starting) { // Also ignore invisible activities that are not the currently starting // activity (about to be visible). return false; } - if (r.occludesParent()) { - // Root task isn't translucent if it has at least one fullscreen activity - // that is visible. - return true; - } - return false; + // TaskFragment isn't translucent if it has at least one fullscreen activity that is + // visible. + return r.occludesParent(includingFinishing); } ActivityRecord getTopNonFinishingActivity() { diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index d212aeeb8d6b3..bfca6925d8957 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -1447,22 +1447,25 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { private static boolean isTranslucent(@NonNull WindowContainer wc) { final TaskFragment taskFragment = wc.asTaskFragment(); - if (taskFragment != null) { - if (taskFragment.isTranslucent(null /* starting */)) { - return true; - } - final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment(); - if (adjacentTaskFragment != null) { - // Treat the TaskFragment as translucent if its adjacent TF is, otherwise everything - // behind two adjacent TaskFragments are occluded. - return adjacentTaskFragment.isTranslucent(null /* starting */); - } + if (taskFragment == null) { + return !wc.fillsParent(); + } + + // Check containers differently as they are affected by child visibility. + + if (taskFragment.isTranslucentForTransition()) { + // TaskFragment doesn't contain occluded ActivityRecord. + return true; + } + final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment(); + if (adjacentTaskFragment != null) { + // When the TaskFragment has an adjacent TaskFragment, sibling behind them should be + // hidden unless any of them are translucent. + return adjacentTaskFragment.isTranslucentForTransition(); + } else { + // Non-filling without adjacent is considered as translucent. + return !wc.fillsParent(); } - // TODO(b/172695805): hierarchical check. This is non-trivial because for containers - // it is effected by child visibility but needs to work even - // before visibility is committed. This means refactoring some - // checks to use requested visibility. - return !wc.fillsParent(); } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 95348a0437802..6b5ff66466f07 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -700,15 +700,15 @@ public class TransitionTests extends WindowTestsBase { ArrayMap changes = transition.mChanges; ArraySet participants = transition.mParticipants; - final Task newTask = createTask(mDisplayContent); - doReturn(false).when(newTask).isTranslucent(any()); final Task oldTask = createTask(mDisplayContent); - doReturn(false).when(oldTask).isTranslucent(any()); + final Task newTask = createTask(mDisplayContent); final ActivityRecord closing = createActivityRecord(oldTask); closing.setOccludesParent(true); + closing.visibleIgnoringKeyguard = true; final ActivityRecord opening = createActivityRecord(newTask); - opening.setOccludesParent(false); + opening.setOccludesParent(true); + opening.visibleIgnoringKeyguard = true; // Start states. changes.put(newTask, new Transition.ChangeInfo(newTask, false /* vis */, true /* exChg */)); changes.put(oldTask, new Transition.ChangeInfo(oldTask, true /* vis */, false /* exChg */)); @@ -716,7 +716,7 @@ public class TransitionTests extends WindowTestsBase { changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, false /* exChg */)); fillChangeMap(changes, newTask); // End states. - closing.setVisibleRequested(true); + closing.setVisibleRequested(false); opening.setVisibleRequested(true); final int transit = transition.mType; @@ -731,8 +731,8 @@ public class TransitionTests extends WindowTestsBase { assertEquals(2, info.getChanges().size()); assertEquals(transit, info.getType()); - assertTrue((info.getChanges().get(0).getFlags() & FLAG_TRANSLUCENT) == 0); - assertTrue((info.getChanges().get(1).getFlags() & FLAG_TRANSLUCENT) == 0); + assertFalse(info.getChanges().get(0).hasFlags(FLAG_TRANSLUCENT)); + assertFalse(info.getChanges().get(1).hasFlags(FLAG_TRANSLUCENT)); } @Test @@ -741,15 +741,15 @@ public class TransitionTests extends WindowTestsBase { ArrayMap changes = transition.mChanges; ArraySet participants = transition.mParticipants; - final Task newTask = createTask(mDisplayContent); - doReturn(true).when(newTask).isTranslucent(any()); final Task oldTask = createTask(mDisplayContent); - doReturn(false).when(oldTask).isTranslucent(any()); + final Task newTask = createTask(mDisplayContent); final ActivityRecord closing = createActivityRecord(oldTask); closing.setOccludesParent(true); + closing.visibleIgnoringKeyguard = true; final ActivityRecord opening = createActivityRecord(newTask); opening.setOccludesParent(false); + opening.visibleIgnoringKeyguard = true; // Start states. changes.put(newTask, new Transition.ChangeInfo(newTask, false /* vis */, true /* exChg */)); changes.put(oldTask, new Transition.ChangeInfo(oldTask, true /* vis */, false /* exChg */)); @@ -757,7 +757,7 @@ public class TransitionTests extends WindowTestsBase { changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, false /* exChg */)); fillChangeMap(changes, newTask); // End states. - closing.setVisibleRequested(true); + closing.setVisibleRequested(false); opening.setVisibleRequested(true); final int transit = transition.mType; @@ -772,8 +772,186 @@ public class TransitionTests extends WindowTestsBase { assertEquals(2, info.getChanges().size()); assertEquals(transit, info.getType()); - assertTrue((info.getChanges().get(0).getFlags() & FLAG_TRANSLUCENT) != 0); - assertTrue((info.getChanges().get(1).getFlags() & FLAG_TRANSLUCENT) == 0); + assertTrue(info.getChanges().get(0).hasFlags(FLAG_TRANSLUCENT)); + assertFalse(info.getChanges().get(1).hasFlags(FLAG_TRANSLUCENT)); + } + + @Test + public void testOpenOpaqueTaskFragment() { + final Transition transition = createTestTransition(TRANSIT_OPEN); + ArrayMap changes = transition.mChanges; + ArraySet participants = transition.mParticipants; + + final Task task = createTask(mDisplayContent); + final TaskFragment closingTaskFragment = createTaskFragmentWithActivity(task); + final TaskFragment openingTaskFragment = createTaskFragmentWithActivity(task); + + final ActivityRecord closing = closingTaskFragment.getTopMostActivity(); + closing.setOccludesParent(true); + closing.visibleIgnoringKeyguard = true; + final ActivityRecord opening = openingTaskFragment.getTopMostActivity(); + opening.setOccludesParent(true); + opening.visibleIgnoringKeyguard = true; + // Start states. + changes.put(openingTaskFragment, new Transition.ChangeInfo(openingTaskFragment, + false /* vis */, true /* exChg */)); + changes.put(closingTaskFragment, new Transition.ChangeInfo(closingTaskFragment, + true /* vis */, false /* exChg */)); + changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */)); + changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, false /* exChg */)); + fillChangeMap(changes, openingTaskFragment); + // End states. + closing.setVisibleRequested(false); + opening.setVisibleRequested(true); + + final int transit = transition.mType; + int flags = 0; + + // Check basic both tasks participating + participants.add(closingTaskFragment); + participants.add(openingTaskFragment); + ArrayList targets = + Transition.calculateTargets(participants, changes); + TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT); + assertEquals(2, info.getChanges().size()); + assertEquals(transit, info.getType()); + + assertFalse(info.getChanges().get(0).hasFlags(FLAG_TRANSLUCENT)); + assertFalse(info.getChanges().get(1).hasFlags(FLAG_TRANSLUCENT)); + } + + @Test + public void testOpenTranslucentTaskFragment() { + final Transition transition = createTestTransition(TRANSIT_OPEN); + ArrayMap changes = transition.mChanges; + ArraySet participants = transition.mParticipants; + + final Task task = createTask(mDisplayContent); + final TaskFragment closingTaskFragment = createTaskFragmentWithActivity(task); + final TaskFragment openingTaskFragment = createTaskFragmentWithActivity(task); + + final ActivityRecord closing = closingTaskFragment.getTopMostActivity(); + closing.setOccludesParent(true); + closing.visibleIgnoringKeyguard = true; + final ActivityRecord opening = openingTaskFragment.getTopMostActivity(); + opening.setOccludesParent(false); + opening.visibleIgnoringKeyguard = true; + // Start states. + changes.put(openingTaskFragment, new Transition.ChangeInfo(openingTaskFragment, + false /* vis */, true /* exChg */)); + changes.put(closingTaskFragment, new Transition.ChangeInfo(closingTaskFragment, + true /* vis */, false /* exChg */)); + changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */)); + changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, false /* exChg */)); + fillChangeMap(changes, openingTaskFragment); + // End states. + closing.setVisibleRequested(false); + opening.setVisibleRequested(true); + + final int transit = transition.mType; + int flags = 0; + + // Check basic both tasks participating + participants.add(closingTaskFragment); + participants.add(openingTaskFragment); + ArrayList targets = + Transition.calculateTargets(participants, changes); + TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT); + assertEquals(2, info.getChanges().size()); + assertEquals(transit, info.getType()); + + assertTrue(info.getChanges().get(0).hasFlags(FLAG_TRANSLUCENT)); + assertFalse(info.getChanges().get(1).hasFlags(FLAG_TRANSLUCENT)); + } + + @Test + public void testCloseOpaqueTaskFragment_withFinishingActivity() { + final Transition transition = createTestTransition(TRANSIT_CLOSE); + ArrayMap changes = transition.mChanges; + ArraySet participants = transition.mParticipants; + + final Task task = createTask(mDisplayContent); + final TaskFragment openingTaskFragment = createTaskFragmentWithActivity(task); + final TaskFragment closingTaskFragment = createTaskFragmentWithActivity(task); + + final ActivityRecord opening = openingTaskFragment.getTopMostActivity(); + opening.setOccludesParent(true); + opening.visibleIgnoringKeyguard = true; + final ActivityRecord closing = closingTaskFragment.getTopMostActivity(); + closing.setOccludesParent(true); + closing.visibleIgnoringKeyguard = true; + closing.finishing = true; + // Start states. + changes.put(openingTaskFragment, new Transition.ChangeInfo(openingTaskFragment, + false /* vis */, true /* exChg */)); + changes.put(closingTaskFragment, new Transition.ChangeInfo(closingTaskFragment, + true /* vis */, false /* exChg */)); + changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */)); + changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, false /* exChg */)); + fillChangeMap(changes, openingTaskFragment); + // End states. + closing.setVisibleRequested(false); + opening.setVisibleRequested(true); + + final int transit = transition.mType; + int flags = 0; + + // Check basic both tasks participating + participants.add(closingTaskFragment); + participants.add(openingTaskFragment); + ArrayList targets = + Transition.calculateTargets(participants, changes); + TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT); + assertEquals(2, info.getChanges().size()); + assertEquals(transit, info.getType()); + + assertFalse(info.getChanges().get(0).hasFlags(FLAG_TRANSLUCENT)); + assertFalse(info.getChanges().get(1).hasFlags(FLAG_TRANSLUCENT)); + } + + @Test + public void testCloseTranslucentTaskFragment_withFinishingActivity() { + final Transition transition = createTestTransition(TRANSIT_CLOSE); + ArrayMap changes = transition.mChanges; + ArraySet participants = transition.mParticipants; + + final Task task = createTask(mDisplayContent); + final TaskFragment openingTaskFragment = createTaskFragmentWithActivity(task); + final TaskFragment closingTaskFragment = createTaskFragmentWithActivity(task); + + final ActivityRecord opening = openingTaskFragment.getTopMostActivity(); + opening.setOccludesParent(true); + opening.visibleIgnoringKeyguard = true; + final ActivityRecord closing = closingTaskFragment.getTopMostActivity(); + closing.setOccludesParent(false); + closing.visibleIgnoringKeyguard = true; + closing.finishing = true; + // Start states. + changes.put(openingTaskFragment, new Transition.ChangeInfo(openingTaskFragment, + false /* vis */, true /* exChg */)); + changes.put(closingTaskFragment, new Transition.ChangeInfo(closingTaskFragment, + true /* vis */, false /* exChg */)); + changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */)); + changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, false /* exChg */)); + fillChangeMap(changes, openingTaskFragment); + // End states. + closing.setVisibleRequested(false); + opening.setVisibleRequested(true); + + final int transit = transition.mType; + int flags = 0; + + // Check basic both tasks participating + participants.add(closingTaskFragment); + participants.add(openingTaskFragment); + ArrayList targets = + Transition.calculateTargets(participants, changes); + TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT); + assertEquals(2, info.getChanges().size()); + assertEquals(transit, info.getType()); + + assertTrue(info.getChanges().get(0).hasFlags(FLAG_TRANSLUCENT)); + assertFalse(info.getChanges().get(1).hasFlags(FLAG_TRANSLUCENT)); } @Test