Never resume activities that are partially occluded

Activities should not be resumed if they are partially occluded by
transparent activities on top. The existing checks did not take into
account other transparent stacks.

Bug: 123937780
Bug: 123991542
Test: atest WmTests:ActivityRecordTests#testShouldResume_stackVisibility
Test: atest WmTests:ActivityStackTests
Test: Launch Calendar, press HOME, launch Calendar again.
Test: go/wm-smoke
Change-Id: I7e4dfeeb72144e7c71e3d53c595c8ac632ad4027
(cherry picked from commit 0c869cc257884deafa4ce1340985663a1a2801d0)
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
index 62421ac..f882fde 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -40,6 +40,7 @@
 import static com.android.server.am.ActivityDisplayProto.SINGLE_TASK_INSTANCE;
 import static com.android.server.am.ActivityDisplayProto.STACKS;
 import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
+import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE;
 import static com.android.server.wm.ActivityStackSupervisor.TAG_TASKS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STACK;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STATES;
@@ -575,7 +576,8 @@
             final ActivityStack stack = mStacks.get(stackNdx);
             final ActivityRecord resumedActivity = stack.getResumedActivity();
             if (resumedActivity != null
-                    && (!stack.shouldBeVisible(resuming) || !stack.isFocusable())) {
+                    && (stack.getVisibility(resuming) != STACK_VISIBILITY_VISIBLE
+                        || !stack.isFocusable())) {
                 if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack +
                         " mResumedActivity=" + resumedActivity);
                 someActivityPaused |= stack.startPausingLocked(userLeaving, false, resuming,
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index ef40648..4faf910 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -109,6 +109,7 @@
 import static com.android.server.wm.ActivityStack.LAUNCH_TICK;
 import static com.android.server.wm.ActivityStack.LAUNCH_TICK_MSG;
 import static com.android.server.wm.ActivityStack.PAUSE_TIMEOUT_MSG;
+import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE;
 import static com.android.server.wm.ActivityStack.STOP_TIMEOUT_MSG;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONFIGURATION;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_FOCUS;
@@ -2054,8 +2055,10 @@
      * @param activeActivity the activity that is active or just completed pause action. We won't
      *                       resume if this activity is active.
      */
-    private boolean shouldResumeActivity(ActivityRecord activeActivity) {
-        return shouldMakeActive(activeActivity) && isFocusable() && !isState(RESUMED);
+    @VisibleForTesting
+    boolean shouldResumeActivity(ActivityRecord activeActivity) {
+        return shouldMakeActive(activeActivity) && isFocusable() && !isState(RESUMED)
+                && getActivityStack().getVisibility(activeActivity) == STACK_VISIBILITY_VISIBLE;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 6f07be8..cc78588 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -108,6 +108,7 @@
 
 import static java.lang.Integer.MAX_VALUE;
 
+import android.annotation.IntDef;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
@@ -223,6 +224,22 @@
     // How many activities have to be scheduled to stop to force a stop pass.
     private static final int MAX_STOPPING_TO_FORCE = 3;
 
+    @IntDef(prefix = {"STACK_VISIBILITY"}, value = {
+            STACK_VISIBILITY_VISIBLE,
+            STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+            STACK_VISIBILITY_INVISIBLE,
+    })
+    @interface StackVisibility {}
+
+    /** Stack is visible. No other stacks on top that fully or partially occlude it. */
+    static final int STACK_VISIBILITY_VISIBLE = 0;
+
+    /** Stack is partially occluded by other translucent stack(s) on top of it. */
+    static final int STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT = 1;
+
+    /** Stack is completely invisible. */
+    static final int STACK_VISIBILITY_INVISIBLE = 2;
+
     @Override
     protected int getChildCount() {
         return mTaskHistory.size();
@@ -1959,14 +1976,28 @@
      * @param starting The currently starting activity or null if there is none.
      */
     boolean shouldBeVisible(ActivityRecord starting) {
+        return getVisibility(starting) != STACK_VISIBILITY_INVISIBLE;
+    }
+
+    /**
+     * Returns true if the stack should be visible.
+     *
+     * @param starting The currently starting activity or null if there is none.
+     */
+    @StackVisibility
+    int getVisibility(ActivityRecord starting) {
         if (!isAttached() || mForceHidden) {
-            return false;
+            return STACK_VISIBILITY_INVISIBLE;
         }
 
         final ActivityDisplay display = getDisplay();
         boolean gotSplitScreenStack = false;
         boolean gotOpaqueSplitScreenPrimary = false;
         boolean gotOpaqueSplitScreenSecondary = false;
+        boolean gotTranslucentFullscreen = false;
+        boolean gotTranslucentSplitScreenPrimary = false;
+        boolean gotTranslucentSplitScreenSecondary = false;
+        boolean shouldBeVisible = true;
         final int windowingMode = getWindowingMode();
         final boolean isAssistantType = isActivityTypeAssistant();
         for (int i = display.getChildCount() - 1; i >= 0; --i) {
@@ -1975,8 +2006,9 @@
             if (other == this) {
                 // Should be visible if there is no other stack occluding it, unless it doesn't
                 // have any running activities, not starting one and not home stack.
-                return hasRunningActivities || isInStackLocked(starting) != null
+                shouldBeVisible = hasRunningActivities || isInStackLocked(starting) != null
                         || isActivityTypeHome();
+                break;
             }
 
             if (!hasRunningActivities) {
@@ -1996,51 +2028,79 @@
                 if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                     if (activityType == ACTIVITY_TYPE_HOME
                             || (activityType == ACTIVITY_TYPE_ASSISTANT
-                                    && mWindowManager.getRecentsAnimationController() != null)) {
-                       return true;
+                                && mWindowManager.getRecentsAnimationController() != null)) {
+                        break;
                     }
                 }
                 if (other.isStackTranslucent(starting)) {
                     // Can be visible behind a translucent fullscreen stack.
+                    gotTranslucentFullscreen = true;
                     continue;
                 }
-                return false;
+                return STACK_VISIBILITY_INVISIBLE;
             } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
                     && !gotOpaqueSplitScreenPrimary) {
                 gotSplitScreenStack = true;
-                gotOpaqueSplitScreenPrimary =
-                        !other.isStackTranslucent(starting);
+                gotTranslucentSplitScreenPrimary = other.isStackTranslucent(starting);
+                gotOpaqueSplitScreenPrimary = !gotTranslucentSplitScreenPrimary;
                 if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
                         && gotOpaqueSplitScreenPrimary) {
                     // Can not be visible behind another opaque stack in split-screen-primary mode.
-                    return false;
+                    return STACK_VISIBILITY_INVISIBLE;
                 }
             } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
                     && !gotOpaqueSplitScreenSecondary) {
                 gotSplitScreenStack = true;
-                gotOpaqueSplitScreenSecondary =
-                        !other.isStackTranslucent(starting);
+                gotTranslucentSplitScreenSecondary = other.isStackTranslucent(starting);
+                gotOpaqueSplitScreenSecondary = !gotTranslucentSplitScreenSecondary;
                 if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
                         && gotOpaqueSplitScreenSecondary) {
                     // Can not be visible behind another opaque stack in split-screen-secondary mode.
-                    return false;
+                    return STACK_VISIBILITY_INVISIBLE;
                 }
             }
             if (gotOpaqueSplitScreenPrimary && gotOpaqueSplitScreenSecondary) {
                 // Can not be visible if we are in split-screen windowing mode and both halves of
                 // the screen are opaque.
-                return false;
+                return STACK_VISIBILITY_INVISIBLE;
             }
             if (isAssistantType && gotSplitScreenStack) {
                 // Assistant stack can't be visible behind split-screen. In addition to this not
                 // making sense, it also works around an issue here we boost the z-order of the
                 // assistant window surfaces in window manager whenever it is visible.
-                return false;
+                return STACK_VISIBILITY_INVISIBLE;
             }
         }
 
-        // Well, nothing is stopping you from being visible...
-        return true;
+        if (!shouldBeVisible) {
+            return STACK_VISIBILITY_INVISIBLE;
+        }
+
+        // Handle cases when there can be a translucent split-screen stack on top.
+        switch (windowingMode) {
+            case WINDOWING_MODE_FULLSCREEN:
+                if (gotTranslucentSplitScreenPrimary || gotTranslucentSplitScreenSecondary) {
+                    // At least one of the split-screen stacks that covers this one is translucent.
+                    return STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
+                }
+                break;
+            case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY:
+                if (gotTranslucentSplitScreenPrimary) {
+                    // Covered by translucent primary split-screen on top.
+                    return STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
+                }
+                break;
+            case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY:
+                if (gotTranslucentSplitScreenSecondary) {
+                    // Covered by translucent secondary split-screen on top.
+                    return STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
+                }
+                break;
+        }
+
+        // Lastly - check if there is a translucent fullscreen stack on top.
+        return gotTranslucentFullscreen ? STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT
+                : STACK_VISIBILITY_VISIBLE;
     }
 
     final int rankTaskLayers(int baseLayer) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 192915f..4073ff1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -24,6 +24,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM;
@@ -34,6 +35,9 @@
 import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
 import static com.android.server.wm.ActivityStack.ActivityState.STOPPED;
 import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_MOVING;
+import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_INVISIBLE;
+import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE;
+import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -381,6 +385,21 @@
     }
 
     @Test
+    public void testShouldResume_stackVisibility() {
+        mActivity.setState(ActivityStack.ActivityState.STOPPED, "Testing");
+        spyOn(mStack);
+
+        doReturn(STACK_VISIBILITY_VISIBLE).when(mStack).getVisibility(null);
+        assertEquals(true, mActivity.shouldResumeActivity(null /* activeActivity */));
+
+        doReturn(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT).when(mStack).getVisibility(null);
+        assertEquals(false, mActivity.shouldResumeActivity(null /* activeActivity */));
+
+        doReturn(STACK_VISIBILITY_INVISIBLE).when(mStack).getVisibility(null);
+        assertEquals(false, mActivity.shouldResumeActivity(null /* activeActivity */));
+    }
+
+    @Test
     public void testPushConfigurationWhenLaunchTaskBehind() throws Exception {
         mActivity.setState(ActivityStack.ActivityState.STOPPED, "Testing");
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index 986943a..822700f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -37,6 +37,9 @@
 import static com.android.server.wm.ActivityStack.ActivityState.STOPPED;
 import static com.android.server.wm.ActivityStack.ActivityState.STOPPING;
 import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING;
+import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_INVISIBLE;
+import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE;
+import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
 
@@ -321,12 +324,23 @@
         assertFalse(homeStack.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
+        assertEquals(STACK_VISIBILITY_INVISIBLE, homeStack.getVisibility(null /* starting */));
+        assertEquals(STACK_VISIBILITY_VISIBLE,
+                splitScreenPrimary.getVisibility(null /* starting */));
+        assertEquals(STACK_VISIBILITY_VISIBLE,
+                splitScreenSecondary.getVisibility(null /* starting */));
 
         // Home stack should be visible if one of the halves of split-screen is translucent.
         splitScreenPrimary.setIsTranslucent(true);
         assertTrue(homeStack.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
+        assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+                homeStack.getVisibility(null /* starting */));
+        assertEquals(STACK_VISIBILITY_VISIBLE,
+                splitScreenPrimary.getVisibility(null /* starting */));
+        assertEquals(STACK_VISIBILITY_VISIBLE,
+                splitScreenSecondary.getVisibility(null /* starting */));
 
         final TestActivityStack splitScreenSecondary2 =
                 createStackForShouldBeVisibleTest(mDefaultDisplay,
@@ -336,12 +350,20 @@
         splitScreenSecondary2.setIsTranslucent(false);
         assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
+        assertEquals(STACK_VISIBILITY_INVISIBLE,
+                splitScreenSecondary.getVisibility(null /* starting */));
+        assertEquals(STACK_VISIBILITY_VISIBLE,
+                splitScreenSecondary2.getVisibility(null /* starting */));
 
         // First split-screen secondary should be visible behind another translucent split-screen
         // secondary.
         splitScreenSecondary2.setIsTranslucent(true);
         assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
+        assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+                splitScreenSecondary.getVisibility(null /* starting */));
+        assertEquals(STACK_VISIBILITY_VISIBLE,
+                splitScreenSecondary2.getVisibility(null /* starting */));
 
         final TestActivityStack assistantStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */);
@@ -352,6 +374,14 @@
         assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */));
         assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
         assertFalse(splitScreenSecondary2.shouldBeVisible(null /* starting */));
+        assertEquals(STACK_VISIBILITY_VISIBLE,
+                assistantStack.getVisibility(null /* starting */));
+        assertEquals(STACK_VISIBILITY_INVISIBLE,
+                splitScreenPrimary.getVisibility(null /* starting */));
+        assertEquals(STACK_VISIBILITY_INVISIBLE,
+                splitScreenSecondary.getVisibility(null /* starting */));
+        assertEquals(STACK_VISIBILITY_INVISIBLE,
+                splitScreenSecondary2.getVisibility(null /* starting */));
 
         // Split-screen stacks should be visible behind a translucent fullscreen stack.
         assistantStack.setIsTranslucent(true);
@@ -359,6 +389,14 @@
         assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
+        assertEquals(STACK_VISIBILITY_VISIBLE,
+                assistantStack.getVisibility(null /* starting */));
+        assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+                splitScreenPrimary.getVisibility(null /* starting */));
+        assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+                splitScreenSecondary.getVisibility(null /* starting */));
+        assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+                splitScreenSecondary2.getVisibility(null /* starting */));
 
         // Assistant stack shouldn't be visible behind translucent split-screen stack
         assistantStack.setIsTranslucent(false);
@@ -369,6 +407,113 @@
         assertFalse(assistantStack.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
+        assertEquals(STACK_VISIBILITY_INVISIBLE,
+                assistantStack.getVisibility(null /* starting */));
+        assertEquals(STACK_VISIBILITY_VISIBLE,
+                splitScreenPrimary.getVisibility(null /* starting */));
+        assertEquals(STACK_VISIBILITY_INVISIBLE,
+                splitScreenSecondary.getVisibility(null /* starting */));
+        assertEquals(STACK_VISIBILITY_VISIBLE,
+                splitScreenSecondary2.getVisibility(null /* starting */));
+    }
+
+    @Test
+    public void testGetVisibility_FullscreenBehindTranslucent() {
+        final TestActivityStack bottomStack =
+                createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+                        false /* translucent */);
+        final TestActivityStack translucentStack =
+                createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+                        true /* translucent */);
+
+        assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+                bottomStack.getVisibility(null /* starting */));
+        assertEquals(STACK_VISIBILITY_VISIBLE,
+                translucentStack.getVisibility(null /* starting */));
+    }
+
+    @Test
+    public void testGetVisibility_FullscreenBehindTranslucentAndOpaque() {
+        final TestActivityStack bottomStack =
+                createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+                        false /* translucent */);
+        final TestActivityStack translucentStack =
+                createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+                        true /* translucent */);
+        final TestActivityStack opaqueStack =
+                createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+                        false /* translucent */);
+
+        assertEquals(STACK_VISIBILITY_INVISIBLE, bottomStack.getVisibility(null /* starting */));
+        assertEquals(STACK_VISIBILITY_INVISIBLE,
+                translucentStack.getVisibility(null /* starting */));
+        assertEquals(STACK_VISIBILITY_VISIBLE, opaqueStack.getVisibility(null /* starting */));
+    }
+
+    @Test
+    public void testGetVisibility_FullscreenBehindOpaqueAndTranslucent() {
+        final TestActivityStack bottomStack =
+                createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+                        false /* translucent */);
+        final TestActivityStack opaqueStack =
+                createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+                        false /* translucent */);
+        final TestActivityStack translucentStack =
+                createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+                        true /* translucent */);
+
+        assertEquals(STACK_VISIBILITY_INVISIBLE, bottomStack.getVisibility(null /* starting */));
+        assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+                opaqueStack.getVisibility(null /* starting */));
+        assertEquals(STACK_VISIBILITY_VISIBLE,
+                translucentStack.getVisibility(null /* starting */));
+    }
+
+    @Test
+    public void testGetVisibility_FullscreenTranslucentBehindTranslucent() {
+        final TestActivityStack bottomTranslucentStack =
+                createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+                        true /* translucent */);
+        final TestActivityStack translucentStack =
+                createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+                        true /* translucent */);
+
+        assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+                bottomTranslucentStack.getVisibility(null /* starting */));
+        assertEquals(STACK_VISIBILITY_VISIBLE,
+                translucentStack.getVisibility(null /* starting */));
+    }
+
+    @Test
+    public void testGetVisibility_FullscreenTranslucentBehindOpaque() {
+        final TestActivityStack bottomTranslucentStack =
+                createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+                        true /* translucent */);
+        final TestActivityStack opaqueStack =
+                createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+                        false /* translucent */);
+
+        assertEquals(STACK_VISIBILITY_INVISIBLE,
+                bottomTranslucentStack.getVisibility(null /* starting */));
+        assertEquals(STACK_VISIBILITY_VISIBLE, opaqueStack.getVisibility(null /* starting */));
+    }
+
+    @Test
+    public void testGetVisibility_FullscreenBehindTranslucentAndPip() {
+        final TestActivityStack bottomStack =
+                createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+                        false /* translucent */);
+        final TestActivityStack translucentStack =
+                createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
+                        true /* translucent */);
+        final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+
+        assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+                bottomStack.getVisibility(null /* starting */));
+        assertEquals(STACK_VISIBILITY_VISIBLE,
+                translucentStack.getVisibility(null /* starting */));
+        assertEquals(STACK_VISIBILITY_VISIBLE, pinnedStack.getVisibility(null /* starting */));
     }
 
     @Test
@@ -628,6 +773,14 @@
         assertFalse(assistantStack.shouldBeVisible(null /* starting */));
     }
 
+    private TestActivityStack createStandardStackForVisibilityTest(int windowingMode,
+            boolean translucent) {
+        final TestActivityStack stack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+                windowingMode, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        stack.setIsTranslucent(translucent);
+        return stack;
+    }
+
     @SuppressWarnings("TypeParameterUnusedInFormals")
     private <T extends ActivityStack> T createStackForShouldBeVisibleTest(
             ActivityDisplay display, int windowingMode, int activityType, boolean onTop) {