Loading services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +67 −17 Original line number Diff line number Diff line Loading @@ -162,6 +162,7 @@ import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.pm.SaferIntentUtils; import com.android.server.utils.Slogf; import com.android.server.wm.ActivityMetricsLogger.LaunchingState; import com.android.window.flags.Flags; import java.io.FileDescriptor; import java.io.PrintWriter; Loading Loading @@ -278,7 +279,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { /** Helper for {@link Task#fillTaskInfo}. */ final TaskInfoHelper mTaskInfoHelper = new TaskInfoHelper(); final OpaqueActivityHelper mOpaqueActivityHelper = new OpaqueActivityHelper(); final OpaqueContainerHelper mOpaqueContainerHelper = new OpaqueContainerHelper(); private final ActivityTaskSupervisorHandler mHandler; final Looper mLooper; Loading Loading @@ -2913,41 +2914,90 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } } /** The helper to get the top opaque activity of a container. */ static class OpaqueActivityHelper implements Predicate<ActivityRecord> { /** The helper to calculate whether a container is opaque. */ static class OpaqueContainerHelper implements Predicate<ActivityRecord> { private ActivityRecord mStarting; private boolean mIncludeInvisibleAndFinishing; private boolean mIgnoringInvisibleActivity; private boolean mIgnoringKeyguard; ActivityRecord getOpaqueActivity(@NonNull WindowContainer<?> container) { mIncludeInvisibleAndFinishing = true; mIgnoringKeyguard = true; return container.getActivity(this, true /* traverseTopToBottom */, null /* boundary */); /** Whether the container is opaque. */ boolean isOpaque(@NonNull WindowContainer<?> container) { return isOpaque(container, null /* starting */, true /* ignoringKeyguard */, false /* ignoringInvisibleActivity */); } ActivityRecord getVisibleOpaqueActivity( /** * Whether the container is opaque, but only including visible activities in its * calculation. */ boolean isOpaque( @NonNull WindowContainer<?> container, @Nullable ActivityRecord starting, boolean ignoringKeyguard) { boolean ignoringKeyguard, boolean ignoringInvisibleActivity) { mStarting = starting; mIncludeInvisibleAndFinishing = false; mIgnoringInvisibleActivity = ignoringInvisibleActivity; mIgnoringKeyguard = ignoringKeyguard; final ActivityRecord opaque = container.getActivity(this, true /* traverseTopToBottom */, null /* boundary */); final boolean isOpaque; if (!Flags.enableMultipleDesktopsBackend()) { isOpaque = container.getActivity(this, true /* traverseTopToBottom */, null /* boundary */) != null; } else { isOpaque = isOpaqueInner(container); } mStarting = null; return opaque; return isOpaque; } private boolean isOpaqueInner(@NonNull WindowContainer<?> container) { // If it's a leaf task fragment, then opacity is calculated based on its activities. if (container.asTaskFragment() != null && ((TaskFragment) container).isLeafTaskFragment()) { return container.getActivity(this, true /* traverseTopToBottom */, null /* boundary */) != null; } // When not a leaf, it's considered opaque if any of its opaque children fill this // container, unless the children are adjacent fragments, in which case as long as they // are all opaque then |container| is also considered opaque, even if the adjacent // task fragment aren't filling. for (int i = 0; i < container.getChildCount(); i++) { final WindowContainer<?> child = container.getChildAt(i); if (child.fillsParent() && isOpaque(child)) { return true; } if (child.asTaskFragment() != null && child.asTaskFragment().hasAdjacentTaskFragment()) { final boolean isAnyTranslucent; if (Flags.allowMultipleAdjacentTaskFragments()) { final TaskFragment.AdjacentSet set = child.asTaskFragment().getAdjacentTaskFragments(); isAnyTranslucent = set.forAllTaskFragments( tf -> !isOpaque(tf), null); } else { final TaskFragment adjacent = child.asTaskFragment() .getAdjacentTaskFragment(); isAnyTranslucent = !isOpaque(child) || !isOpaque(adjacent); } if (!isAnyTranslucent) { // This task fragment and all its adjacent task fragments are opaque, // consider it opaque even if it doesn't fill its parent. return true; } } } return false; } @Override public boolean test(ActivityRecord r) { if (!mIncludeInvisibleAndFinishing && r != mStarting if (mIgnoringInvisibleActivity && r != mStarting && ((mIgnoringKeyguard && !r.visibleIgnoringKeyguard) || (!mIgnoringKeyguard && !r.isVisible()))) { // Ignore invisible activities that are not the currently starting activity // (about to be visible). return false; } return r.occludesParent(mIncludeInvisibleAndFinishing /* includingFinishing */); return r.occludesParent(!mIgnoringInvisibleActivity /* includingFinishing */); } } Loading services/core/java/com/android/server/wm/TaskFragment.java +5 −7 Original line number Diff line number Diff line Loading @@ -1195,10 +1195,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { if (!isAttached() || isForceHidden() || isForceTranslucent()) { return true; } // A TaskFragment isn't translucent if it has at least one visible activity that occludes // this TaskFragment. return mTaskSupervisor.mOpaqueActivityHelper.getVisibleOpaqueActivity(this, starting, true /* ignoringKeyguard */) == null; return !mTaskSupervisor.mOpaqueContainerHelper.isOpaque( this, starting, true /* ignoringKeyguard */, true /* ignoringInvisibleActivity */); } /** Loading @@ -1211,7 +1209,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { return true; } // Including finishing Activity if the TaskFragment is becoming invisible in the transition. return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this) == null; return !mTaskSupervisor.mOpaqueContainerHelper.isOpaque(this); } /** Loading @@ -1222,8 +1220,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { if (!isAttached() || isForceHidden() || isForceTranslucent()) { return true; } return mTaskSupervisor.mOpaqueActivityHelper.getVisibleOpaqueActivity(this, null, false /* ignoringKeyguard */) == null; return !mTaskSupervisor.mOpaqueContainerHelper.isOpaque(this, /* starting */ null, false /* ignoringKeyguard */, true /* ignoringInvisibleActivity */); } ActivityRecord getTopNonFinishingActivity() { Loading services/core/java/com/android/server/wm/Transition.java +1 −1 Original line number Diff line number Diff line Loading @@ -505,7 +505,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { final WindowContainer<?> sibling = rootParent.getChildAt(j); if (sibling == transientRoot) break; if (!sibling.getWindowConfiguration().isAlwaysOnTop() && mController.mAtm .mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(sibling) != null) { .mTaskSupervisor.mOpaqueContainerHelper.isOpaque(sibling)) { occludedCount++; break; } Loading services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java +98 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.app.ActivityManager.START_DELIVERED_TO_TOP; import static android.app.ActivityManager.START_TASK_TO_FRONT; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; Loading @@ -44,20 +45,26 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.timeout; import android.annotation.NonNull; import android.app.ActivityOptions; import android.app.WaitResult; import android.app.WindowConfiguration; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; import android.graphics.Rect; import android.os.Binder; import android.os.ConditionVariable; import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.view.Display; import androidx.test.filters.MediumTest; import com.android.window.flags.Flags; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentMatchers; Loading Loading @@ -424,4 +431,95 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase { assertThat(activity.mLaunchCookie).isNull(); verify(mAtm).moveTaskToFrontLocked(any(), eq(null), anyInt(), anyInt(), eq(safeOptions)); } @Test public void testOpaque_leafTask_occludingActivity_isOpaque() { final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build(); activity.setOccludesParent(true); final TaskFragment tf = activity.getTaskFragment(); assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(tf)).isTrue(); } @Test public void testOpaque_leafTask_nonOccludingActivity_isTranslucent() { final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build(); activity.setOccludesParent(false); final TaskFragment tf = activity.getTaskFragment(); assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(tf)).isFalse(); } @Test @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) public void testOpaque_rootTask_translucentFillingChild_isTranslucent() { final Task rootTask = new TaskBuilder(mSupervisor).setOnTop(true).build(); createChildTaskFragment(/* parent */ rootTask, WINDOWING_MODE_FREEFORM, /* opaque */ false, /* filling */ true); assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(rootTask)).isFalse(); } @Test @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) public void testOpaque_rootTask_opaqueAndNotFillingChild_isTranslucent() { final Task rootTask = new TaskBuilder(mSupervisor).setOnTop(true).build(); createChildTaskFragment(/* parent */ rootTask, WINDOWING_MODE_FREEFORM, /* opaque */ true, /* filling */ false); assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(rootTask)).isFalse(); } @Test @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) public void testOpaque_rootTask_opaqueAndFillingChild_isOpaque() { final Task rootTask = new TaskBuilder(mSupervisor).setOnTop(true).build(); createChildTaskFragment(/* parent */ rootTask, WINDOWING_MODE_FREEFORM, /* opaque */ true, /* filling */ true); assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(rootTask)).isTrue(); } @Test @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) public void testOpaque_rootTask_nonFillingOpaqueAdjacentChildren_isOpaque() { final Task rootTask = new TaskBuilder(mSupervisor).setOnTop(true).build(); final TaskFragment tf1 = createChildTaskFragment(/* parent */ rootTask, WINDOWING_MODE_MULTI_WINDOW, /* opaque */ true, /* filling */ false); final TaskFragment tf2 = createChildTaskFragment(/* parent */ rootTask, WINDOWING_MODE_MULTI_WINDOW, /* opaque */ true, /* filling */ false); tf1.setAdjacentTaskFragment(tf2); assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(rootTask)).isTrue(); } @Test @EnableFlags({Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS}) public void testOpaque_rootTask_nonFillingOpaqueAdjacentChildren_multipleAdjacent_isOpaque() { final Task rootTask = new TaskBuilder(mSupervisor).setOnTop(true).build(); final TaskFragment tf1 = createChildTaskFragment(/* parent */ rootTask, WINDOWING_MODE_MULTI_WINDOW, /* opaque */ true, /* filling */ false); final TaskFragment tf2 = createChildTaskFragment(/* parent */ rootTask, WINDOWING_MODE_MULTI_WINDOW, /* opaque */ true, /* filling */ false); final TaskFragment tf3 = createChildTaskFragment(/* parent */ rootTask, WINDOWING_MODE_MULTI_WINDOW, /* opaque */ true, /* filling */ false); tf1.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(tf1, tf2, tf3)); assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(rootTask)).isTrue(); } @NonNull private TaskFragment createChildTaskFragment(@NonNull Task parent, @WindowConfiguration.WindowingMode int windowingMode, boolean opaque, boolean filling) { final ActivityRecord activity = new ActivityBuilder(mAtm) .setCreateTask(true).setParentTask(parent).build(); activity.setOccludesParent(opaque); final TaskFragment tf = activity.getTaskFragment(); tf.setWindowingMode(windowingMode); tf.setBounds(filling ? new Rect() : new Rect(100, 100, 200, 200)); return tf; } } Loading
services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +67 −17 Original line number Diff line number Diff line Loading @@ -162,6 +162,7 @@ import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.pm.SaferIntentUtils; import com.android.server.utils.Slogf; import com.android.server.wm.ActivityMetricsLogger.LaunchingState; import com.android.window.flags.Flags; import java.io.FileDescriptor; import java.io.PrintWriter; Loading Loading @@ -278,7 +279,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { /** Helper for {@link Task#fillTaskInfo}. */ final TaskInfoHelper mTaskInfoHelper = new TaskInfoHelper(); final OpaqueActivityHelper mOpaqueActivityHelper = new OpaqueActivityHelper(); final OpaqueContainerHelper mOpaqueContainerHelper = new OpaqueContainerHelper(); private final ActivityTaskSupervisorHandler mHandler; final Looper mLooper; Loading Loading @@ -2913,41 +2914,90 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } } /** The helper to get the top opaque activity of a container. */ static class OpaqueActivityHelper implements Predicate<ActivityRecord> { /** The helper to calculate whether a container is opaque. */ static class OpaqueContainerHelper implements Predicate<ActivityRecord> { private ActivityRecord mStarting; private boolean mIncludeInvisibleAndFinishing; private boolean mIgnoringInvisibleActivity; private boolean mIgnoringKeyguard; ActivityRecord getOpaqueActivity(@NonNull WindowContainer<?> container) { mIncludeInvisibleAndFinishing = true; mIgnoringKeyguard = true; return container.getActivity(this, true /* traverseTopToBottom */, null /* boundary */); /** Whether the container is opaque. */ boolean isOpaque(@NonNull WindowContainer<?> container) { return isOpaque(container, null /* starting */, true /* ignoringKeyguard */, false /* ignoringInvisibleActivity */); } ActivityRecord getVisibleOpaqueActivity( /** * Whether the container is opaque, but only including visible activities in its * calculation. */ boolean isOpaque( @NonNull WindowContainer<?> container, @Nullable ActivityRecord starting, boolean ignoringKeyguard) { boolean ignoringKeyguard, boolean ignoringInvisibleActivity) { mStarting = starting; mIncludeInvisibleAndFinishing = false; mIgnoringInvisibleActivity = ignoringInvisibleActivity; mIgnoringKeyguard = ignoringKeyguard; final ActivityRecord opaque = container.getActivity(this, true /* traverseTopToBottom */, null /* boundary */); final boolean isOpaque; if (!Flags.enableMultipleDesktopsBackend()) { isOpaque = container.getActivity(this, true /* traverseTopToBottom */, null /* boundary */) != null; } else { isOpaque = isOpaqueInner(container); } mStarting = null; return opaque; return isOpaque; } private boolean isOpaqueInner(@NonNull WindowContainer<?> container) { // If it's a leaf task fragment, then opacity is calculated based on its activities. if (container.asTaskFragment() != null && ((TaskFragment) container).isLeafTaskFragment()) { return container.getActivity(this, true /* traverseTopToBottom */, null /* boundary */) != null; } // When not a leaf, it's considered opaque if any of its opaque children fill this // container, unless the children are adjacent fragments, in which case as long as they // are all opaque then |container| is also considered opaque, even if the adjacent // task fragment aren't filling. for (int i = 0; i < container.getChildCount(); i++) { final WindowContainer<?> child = container.getChildAt(i); if (child.fillsParent() && isOpaque(child)) { return true; } if (child.asTaskFragment() != null && child.asTaskFragment().hasAdjacentTaskFragment()) { final boolean isAnyTranslucent; if (Flags.allowMultipleAdjacentTaskFragments()) { final TaskFragment.AdjacentSet set = child.asTaskFragment().getAdjacentTaskFragments(); isAnyTranslucent = set.forAllTaskFragments( tf -> !isOpaque(tf), null); } else { final TaskFragment adjacent = child.asTaskFragment() .getAdjacentTaskFragment(); isAnyTranslucent = !isOpaque(child) || !isOpaque(adjacent); } if (!isAnyTranslucent) { // This task fragment and all its adjacent task fragments are opaque, // consider it opaque even if it doesn't fill its parent. return true; } } } return false; } @Override public boolean test(ActivityRecord r) { if (!mIncludeInvisibleAndFinishing && r != mStarting if (mIgnoringInvisibleActivity && r != mStarting && ((mIgnoringKeyguard && !r.visibleIgnoringKeyguard) || (!mIgnoringKeyguard && !r.isVisible()))) { // Ignore invisible activities that are not the currently starting activity // (about to be visible). return false; } return r.occludesParent(mIncludeInvisibleAndFinishing /* includingFinishing */); return r.occludesParent(!mIgnoringInvisibleActivity /* includingFinishing */); } } Loading
services/core/java/com/android/server/wm/TaskFragment.java +5 −7 Original line number Diff line number Diff line Loading @@ -1195,10 +1195,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { if (!isAttached() || isForceHidden() || isForceTranslucent()) { return true; } // A TaskFragment isn't translucent if it has at least one visible activity that occludes // this TaskFragment. return mTaskSupervisor.mOpaqueActivityHelper.getVisibleOpaqueActivity(this, starting, true /* ignoringKeyguard */) == null; return !mTaskSupervisor.mOpaqueContainerHelper.isOpaque( this, starting, true /* ignoringKeyguard */, true /* ignoringInvisibleActivity */); } /** Loading @@ -1211,7 +1209,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { return true; } // Including finishing Activity if the TaskFragment is becoming invisible in the transition. return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this) == null; return !mTaskSupervisor.mOpaqueContainerHelper.isOpaque(this); } /** Loading @@ -1222,8 +1220,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { if (!isAttached() || isForceHidden() || isForceTranslucent()) { return true; } return mTaskSupervisor.mOpaqueActivityHelper.getVisibleOpaqueActivity(this, null, false /* ignoringKeyguard */) == null; return !mTaskSupervisor.mOpaqueContainerHelper.isOpaque(this, /* starting */ null, false /* ignoringKeyguard */, true /* ignoringInvisibleActivity */); } ActivityRecord getTopNonFinishingActivity() { Loading
services/core/java/com/android/server/wm/Transition.java +1 −1 Original line number Diff line number Diff line Loading @@ -505,7 +505,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { final WindowContainer<?> sibling = rootParent.getChildAt(j); if (sibling == transientRoot) break; if (!sibling.getWindowConfiguration().isAlwaysOnTop() && mController.mAtm .mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(sibling) != null) { .mTaskSupervisor.mOpaqueContainerHelper.isOpaque(sibling)) { occludedCount++; break; } Loading
services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java +98 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.app.ActivityManager.START_DELIVERED_TO_TOP; import static android.app.ActivityManager.START_TASK_TO_FRONT; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; Loading @@ -44,20 +45,26 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.timeout; import android.annotation.NonNull; import android.app.ActivityOptions; import android.app.WaitResult; import android.app.WindowConfiguration; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; import android.graphics.Rect; import android.os.Binder; import android.os.ConditionVariable; import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.view.Display; import androidx.test.filters.MediumTest; import com.android.window.flags.Flags; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentMatchers; Loading Loading @@ -424,4 +431,95 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase { assertThat(activity.mLaunchCookie).isNull(); verify(mAtm).moveTaskToFrontLocked(any(), eq(null), anyInt(), anyInt(), eq(safeOptions)); } @Test public void testOpaque_leafTask_occludingActivity_isOpaque() { final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build(); activity.setOccludesParent(true); final TaskFragment tf = activity.getTaskFragment(); assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(tf)).isTrue(); } @Test public void testOpaque_leafTask_nonOccludingActivity_isTranslucent() { final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build(); activity.setOccludesParent(false); final TaskFragment tf = activity.getTaskFragment(); assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(tf)).isFalse(); } @Test @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) public void testOpaque_rootTask_translucentFillingChild_isTranslucent() { final Task rootTask = new TaskBuilder(mSupervisor).setOnTop(true).build(); createChildTaskFragment(/* parent */ rootTask, WINDOWING_MODE_FREEFORM, /* opaque */ false, /* filling */ true); assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(rootTask)).isFalse(); } @Test @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) public void testOpaque_rootTask_opaqueAndNotFillingChild_isTranslucent() { final Task rootTask = new TaskBuilder(mSupervisor).setOnTop(true).build(); createChildTaskFragment(/* parent */ rootTask, WINDOWING_MODE_FREEFORM, /* opaque */ true, /* filling */ false); assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(rootTask)).isFalse(); } @Test @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) public void testOpaque_rootTask_opaqueAndFillingChild_isOpaque() { final Task rootTask = new TaskBuilder(mSupervisor).setOnTop(true).build(); createChildTaskFragment(/* parent */ rootTask, WINDOWING_MODE_FREEFORM, /* opaque */ true, /* filling */ true); assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(rootTask)).isTrue(); } @Test @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) public void testOpaque_rootTask_nonFillingOpaqueAdjacentChildren_isOpaque() { final Task rootTask = new TaskBuilder(mSupervisor).setOnTop(true).build(); final TaskFragment tf1 = createChildTaskFragment(/* parent */ rootTask, WINDOWING_MODE_MULTI_WINDOW, /* opaque */ true, /* filling */ false); final TaskFragment tf2 = createChildTaskFragment(/* parent */ rootTask, WINDOWING_MODE_MULTI_WINDOW, /* opaque */ true, /* filling */ false); tf1.setAdjacentTaskFragment(tf2); assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(rootTask)).isTrue(); } @Test @EnableFlags({Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS}) public void testOpaque_rootTask_nonFillingOpaqueAdjacentChildren_multipleAdjacent_isOpaque() { final Task rootTask = new TaskBuilder(mSupervisor).setOnTop(true).build(); final TaskFragment tf1 = createChildTaskFragment(/* parent */ rootTask, WINDOWING_MODE_MULTI_WINDOW, /* opaque */ true, /* filling */ false); final TaskFragment tf2 = createChildTaskFragment(/* parent */ rootTask, WINDOWING_MODE_MULTI_WINDOW, /* opaque */ true, /* filling */ false); final TaskFragment tf3 = createChildTaskFragment(/* parent */ rootTask, WINDOWING_MODE_MULTI_WINDOW, /* opaque */ true, /* filling */ false); tf1.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(tf1, tf2, tf3)); assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(rootTask)).isTrue(); } @NonNull private TaskFragment createChildTaskFragment(@NonNull Task parent, @WindowConfiguration.WindowingMode int windowingMode, boolean opaque, boolean filling) { final ActivityRecord activity = new ActivityBuilder(mAtm) .setCreateTask(true).setParentTask(parent).build(); activity.setOccludesParent(opaque); final TaskFragment tf = activity.getTaskFragment(); tf.setWindowingMode(windowingMode); tf.setBounds(filling ? new Rect() : new Rect(100, 100, 200, 200)); return tf; } }