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
This commit is contained in:
Chris Li
2023-02-09 14:26:45 +08:00
parent cce2be380b
commit 32e6070218
3 changed files with 232 additions and 42 deletions

View File

@@ -904,37 +904,46 @@ class TaskFragment extends WindowContainer<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() {

View File

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

View File

@@ -700,15 +700,15 @@ public class TransitionTests extends WindowTestsBase {
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> 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<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> 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<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> 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<Transition.ChangeInfo> 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<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> 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<Transition.ChangeInfo> 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<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> 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<Transition.ChangeInfo> 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<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> 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<Transition.ChangeInfo> 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