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