OSDN Git Service

Simplified ActivityStack.shouldBeVisible()
authorWale Ogunwale <ogunwale@google.com>
Wed, 18 Oct 2017 22:19:59 +0000 (15:19 -0700)
committerWale Ogunwale <ogunwale@google.com>
Tue, 24 Oct 2017 17:50:26 +0000 (10:50 -0700)
The method can now be simplified since we have a stack per task so
if a stack should be visible is now a question of if it is occluded
by another stack.

Also,
- Fixed an issue where the windowing mode of the primary split-screen
stack was changing to split-screen-secondary instead of fullscreen
when we are exiting split-screen mode because we are not allowed to
to create fullscreen stack when there is a primary split-screen stack. We
now clear the reference to the primary split-screen stack when exiting
split-screen mode.
- Re-worked windowing mode resolution to be inside ActivityDisplay
object since the determination of the windowing mode is dependant on the
display.

Test: bit FrameworksServicesTests:com.android.server.am.ActivityStackTests
Test: Existing tests pass.
Test: go/wm-smoke
Bug: 64146578
Fixes: 67914671
Change-Id: I7e8cfe49fbf6a5836ded022bb11adcde58ae689c

services/core/java/com/android/server/am/ActivityDisplay.java
services/core/java/com/android/server/am/ActivityManagerService.java
services/core/java/com/android/server/am/ActivityRecord.java
services/core/java/com/android/server/am/ActivityStack.java
services/core/java/com/android/server/am/ActivityStackSupervisor.java
services/core/java/com/android/server/am/TaskRecord.java
services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java

index 089db87..29073cb 100644 (file)
@@ -38,7 +38,9 @@ import static com.android.server.am.proto.ActivityDisplayProto.CONFIGURATION_CON
 import static com.android.server.am.proto.ActivityDisplayProto.STACKS;
 import static com.android.server.am.proto.ActivityDisplayProto.ID;
 
+import android.annotation.Nullable;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions;
 import android.app.WindowConfiguration;
 import android.util.IntArray;
 import android.util.Slog;
@@ -206,6 +208,18 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> {
     }
 
     /**
+     * Returns an existing stack compatible with the input params or creates one
+     * if a compatible stack doesn't exist.
+     * @see #getOrCreateStack(int, int, boolean)
+     */
+    <T extends ActivityStack> T getOrCreateStack(@Nullable ActivityRecord r,
+            @Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, int activityType,
+            boolean onTop) {
+        final int windowingMode = resolveWindowingMode(r, options, candidateTask, activityType);
+        return getOrCreateStack(windowingMode, activityType, onTop);
+    }
+
+    /**
      * Creates a stack matching the input windowing mode and activity type on this display.
      * @param windowingMode The windowing mode the stack should be created in. If
      *                      {@link WindowConfiguration#WINDOWING_MODE_UNDEFINED} then the stack will
@@ -235,7 +249,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> {
         }
 
         final ActivityManagerService service = mSupervisor.mService;
-        if (!mSupervisor.isWindowingModeSupported(windowingMode, service.mSupportsMultiWindow,
+        if (!isWindowingModeSupported(windowingMode, service.mSupportsMultiWindow,
                 service.mSupportsSplitScreenMultiWindow, service.mSupportsFreeformWindowManagement,
                 service.mSupportsPictureInPicture, activityType)) {
             throw new IllegalArgumentException("Can't create stack for unsupported windowingMode="
@@ -252,35 +266,29 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> {
             }
         }
 
-        windowingMode = updateWindowingModeForSplitScreenIfNeeded(windowingMode, activityType);
-
         final int stackId = mSupervisor.getNextStackId();
+        return createStackUnchecked(windowingMode, activityType, stackId, onTop);
+    }
 
-        final T stack = createStackUnchecked(windowingMode, activityType, stackId, onTop);
+    @VisibleForTesting
+    <T extends ActivityStack> T createStackUnchecked(int windowingMode, int activityType,
+            int stackId, boolean onTop) {
+        if (windowingMode == WINDOWING_MODE_PINNED) {
+            return (T) new PinnedActivityStack(this, stackId, mSupervisor, onTop);
+        }
+        final T stack = (T) new ActivityStack(
+                        this, stackId, mSupervisor, windowingMode, activityType, onTop);
 
         if (mDisplayId == DEFAULT_DISPLAY && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
-            // Make sure recents stack exist when creating a dock stack as it normally need to be on
-            // the other side of the docked stack and we make visibility decisions based on that.
+            // Make sure recents stack exist when creating a dock stack as it normally needs to be
+            // on the other side of the docked stack and we make visibility decisions based on that.
             // TODO: Not sure if this is needed after we change to calculate visibility based on
             // stack z-order vs. id.
             getOrCreateStack(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_RECENTS, onTop);
         }
-
         return stack;
     }
 
-    @VisibleForTesting
-    <T extends ActivityStack> T createStackUnchecked(int windowingMode, int activityType,
-            int stackId, boolean onTop) {
-        switch (windowingMode) {
-            case WINDOWING_MODE_PINNED:
-                return (T) new PinnedActivityStack(this, stackId, mSupervisor, onTop);
-            default:
-                return (T) new ActivityStack(
-                        this, stackId, mSupervisor, windowingMode, activityType, onTop);
-        }
-    }
-
     /**
      * Removes stacks in the input windowing modes from the system if they are of activity type
      * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
@@ -372,6 +380,105 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> {
         }
     }
 
+    /**
+     * Returns true if the {@param windowingMode} is supported based on other parameters passed in.
+     * @param windowingMode The windowing mode we are checking support for.
+     * @param supportsMultiWindow If we should consider support for multi-window mode in general.
+     * @param supportsSplitScreen If we should consider support for split-screen multi-window.
+     * @param supportsFreeform If we should consider support for freeform multi-window.
+     * @param supportsPip If we should consider support for picture-in-picture mutli-window.
+     * @param activityType The activity type under consideration.
+     * @return true if the windowing mode is supported.
+     */
+    private boolean isWindowingModeSupported(int windowingMode, boolean supportsMultiWindow,
+            boolean supportsSplitScreen, boolean supportsFreeform, boolean supportsPip,
+            int activityType) {
+
+        if (windowingMode == WINDOWING_MODE_UNDEFINED
+                || windowingMode == WINDOWING_MODE_FULLSCREEN) {
+            return true;
+        }
+        if (!supportsMultiWindow) {
+            return false;
+        }
+
+        if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
+            return supportsSplitScreen && WindowConfiguration.supportSplitScreenWindowingMode(
+                    windowingMode, activityType);
+        }
+
+        if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) {
+            return false;
+        }
+
+        if (!supportsPip && windowingMode == WINDOWING_MODE_PINNED) {
+            return false;
+        }
+        return true;
+    }
+
+    int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
+            @Nullable TaskRecord task, int activityType) {
+
+        // First preference if the windowing mode in the activity options if set.
+        int windowingMode = (options != null)
+                ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
+
+        // If windowing mode is unset, then next preference is the candidate task, then the
+        // activity record.
+        if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+            if (task != null) {
+                windowingMode = task.getWindowingMode();
+            }
+            if (windowingMode == WINDOWING_MODE_UNDEFINED && r != null) {
+                windowingMode = r.getWindowingMode();
+            }
+            if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+                // Use the display's windowing mode.
+                windowingMode = getWindowingMode();
+            }
+        }
+
+        // Make sure the windowing mode we are trying to use makes sense for what is supported.
+        final ActivityManagerService service = mSupervisor.mService;
+        boolean supportsMultiWindow = service.mSupportsMultiWindow;
+        boolean supportsSplitScreen = service.mSupportsSplitScreenMultiWindow;
+        boolean supportsFreeform = service.mSupportsFreeformWindowManagement;
+        boolean supportsPip = service.mSupportsPictureInPicture;
+        if (supportsMultiWindow) {
+            if (task != null) {
+                supportsMultiWindow = task.isResizeable();
+                supportsSplitScreen = task.supportsSplitScreenWindowingMode();
+                // TODO: Do we need to check for freeform and Pip support here?
+            } else if (r != null) {
+                supportsMultiWindow = r.isResizeable();
+                supportsSplitScreen = r.supportsSplitScreenWindowingMode();
+                supportsFreeform = r.supportsFreeform();
+                supportsPip = r.supportsPictureInPicture();
+            }
+        }
+
+        final boolean inSplitScreenMode = hasSplitScreenPrimaryStack();
+        if (!inSplitScreenMode
+                && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
+            // Switch to fullscreen windowing mode if we are not in split-screen mode and we are
+            // trying to launch in split-screen secondary.
+            windowingMode = WINDOWING_MODE_FULLSCREEN;
+        } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN
+                && supportsSplitScreen) {
+            windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+        }
+
+        if (windowingMode != WINDOWING_MODE_UNDEFINED
+                && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
+                supportsFreeform, supportsPip, activityType)) {
+            return windowingMode;
+        }
+        // Return the display's windowing mode
+        return getWindowingMode();
+    }
+
     /** Returns the top visible stack activity type that isn't in the exclude windowing mode. */
     int getTopVisibleStackActivityType(int excludeWindowingMode) {
         for (int i = mStacks.size() - 1; i >= 0; --i) {
@@ -408,6 +515,14 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> {
         }
     }
 
+    /** We are in the process of exiting split-screen mode. */
+    void onExitingSplitScreenMode() {
+        // Remove reference to the primary-split-screen stack so it no longer has any effect on the
+        // display. For example, we want to be able to create fullscreen stack for standard activity
+        // types when exiting split-screen mode.
+        mSplitScreenPrimaryStack = null;
+    }
+
     ActivityStack getSplitScreenPrimaryStack() {
         return mSplitScreenPrimaryStack;
     }
@@ -424,21 +539,6 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> {
         return mPinnedStack != null;
     }
 
-    int updateWindowingModeForSplitScreenIfNeeded(int windowingMode, int activityType) {
-        final boolean inSplitScreenMode = hasSplitScreenPrimaryStack();
-        if (!inSplitScreenMode
-                && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
-            // Switch to fullscreen windowing mode if we are not in split-screen mode and we are
-            // trying to launch in split-screen secondary.
-            return WINDOWING_MODE_FULLSCREEN;
-        } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN
-                && WindowConfiguration.supportSplitScreenWindowingMode(
-                windowingMode, activityType)) {
-            return WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-        }
-        return windowingMode;
-    }
-
     @Override
     public String toString() {
         return "ActivityDisplay={" + mDisplayId + " numStacks=" + mStacks.size() + "}";
index e1e53b3..eed1e6b 100644 (file)
@@ -31,6 +31,7 @@ import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
@@ -10318,7 +10319,8 @@ public class ActivityManagerService extends IActivityManager.Stub
             }
             // TODO(multi-display): Have the caller pass in the windowing mode and activity type.
             final ActivityStack stack = display.createStack(
-                    WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /*onTop*/);
+                    WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD,
+                    ON_TOP);
             return (stack != null) ? stack.mStackId : INVALID_STACK_ID;
         }
     }
index 2c72a4d..b885eab 100644 (file)
@@ -165,6 +165,7 @@ import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.IApplicationToken;
 import android.view.WindowManager.LayoutParams;
 
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ResolverActivity;
 import com.android.internal.content.ReferrerIntent;
@@ -232,7 +233,9 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
     final String processName; // process where this component wants to run
     final String taskAffinity; // as per ActivityInfo.taskAffinity
     final boolean stateNotNeeded; // As per ActivityInfo.flags
-    boolean fullscreen; // covers the full screen?
+    boolean fullscreen; // The activity is opaque and fills the entire space of this task.
+    // TODO: See if it possible to combine this with the fullscreen field.
+    final boolean hasWallpaper; // Has a wallpaper window as a background.
     final boolean noDisplay;  // activity is not displayed?
     private final boolean componentSpecified;  // did caller specify an explicit component?
     final boolean rootVoiceInteraction;  // was this the root activity of a voice interaction?
@@ -883,9 +886,14 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
 
         Entry ent = AttributeCache.instance().get(packageName,
                 realTheme, com.android.internal.R.styleable.Window, userId);
-        fullscreen = ent != null && !ActivityInfo.isTranslucentOrFloating(ent.array);
-        noDisplay = ent != null && ent.array.getBoolean(
-                com.android.internal.R.styleable.Window_windowNoDisplay, false);
+        if (ent != null) {
+            fullscreen = !ActivityInfo.isTranslucentOrFloating(ent.array);
+            hasWallpaper = ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false);
+            noDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false);
+        } else {
+            hasWallpaper = false;
+            noDisplay = false;
+        }
 
         setActivityType(_componentSpecified, _launchedFromUid, _intent, options, sourceRecord);
 
index 941c371..f05243b 100644 (file)
@@ -489,22 +489,19 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
             activityType = ACTIVITY_TYPE_STANDARD;
         }
         final ActivityDisplay display = getDisplay();
-        if (display != null) {
-            if (activityType == ACTIVITY_TYPE_STANDARD
+        if (display != null && activityType == ACTIVITY_TYPE_STANDARD
                     && windowingMode == WINDOWING_MODE_UNDEFINED) {
-                // Standard activity types will mostly take on the windowing mode of the display if
-                // one isn't specified, so look-up a compatible stack based on the display's
-                // windowing mode.
-                windowingMode = display.getWindowingMode();
-            }
-            windowingMode =
-                    display.updateWindowingModeForSplitScreenIfNeeded(windowingMode, activityType);
+            // Standard activity types will mostly take on the windowing mode of the display if one
+            // isn't specified, so look-up a compatible stack based on the display's windowing mode.
+            windowingMode = display.getWindowingMode();
         }
         return super.isCompatible(windowingMode, activityType);
     }
 
     /** Adds the stack to specified display and calls WindowManager to do the same. */
     void reparent(ActivityDisplay activityDisplay, boolean onTop) {
+        // TODO: We should probably resolve the windowing mode for the stack on the new display here
+        // so that it end up in a compatible mode in the new display. e.g. split-screen secondary.
         removeFromDisplay();
         mTmpRect2.setEmpty();
         postAddToDisplay(activityDisplay, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop);
@@ -1496,25 +1493,19 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
         }
     }
 
-    /** Returns true if the stack contains a fullscreen task. */
-    private boolean hasFullscreenTask() {
-        for (int i = mTaskHistory.size() - 1; i >= 0; --i) {
-            final TaskRecord task = mTaskHistory.get(i);
-            if (task.mFullscreen) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     /**
      * Returns true if the stack is translucent and can have other contents visible behind it if
      * needed. A stack is considered translucent if it don't contain a visible or
      * starting (about to be visible) activity that is fullscreen (opaque).
      * @param starting The currently starting activity or null if there is none.
+     * TODO: Can be removed once we are no longer using returnToType for back functionality
      * @param stackBehind The stack directly behind this one.
      */
-    private boolean isStackTranslucent(ActivityRecord starting, ActivityStack stackBehind) {
+    @VisibleForTesting
+    boolean isStackTranslucent(ActivityRecord starting, ActivityStack stackBehind) {
+        if (!isAttached() || mForceHidden) {
+            return true;
+        }
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
             final TaskRecord task = mTaskHistory.get(taskNdx);
             final ArrayList<ActivityRecord> activities = task.mActivities;
@@ -1533,7 +1524,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
                     continue;
                 }
 
-                if (r.fullscreen) {
+                if (r.fullscreen || r.hasWallpaper) {
                     // Stack isn't translucent if it has at least one fullscreen activity
                     // that is visible.
                     return false;
@@ -1572,129 +1563,68 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
         if (!isAttached() || mForceHidden) {
             return false;
         }
-
-        final ActivityDisplay display = getDisplay();
-        if (isTopStackOnDisplay() || mStackSupervisor.isFocusedStack(this)) {
-            return true;
-        }
-
-        final int stackIndex = display.getIndexOf(this);
-
-        // Check position and visibility of this stack relative to the front stack on its display.
-        final ActivityStack topStack = getDisplay().getTopStack();
-        final int windowingMode = getWindowingMode();
-        final int activityType = getActivityType();
-
-        if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
-            // If the assistant stack is focused and translucent, then the docked stack is always
-            // visible
-            if (topStack.isActivityTypeAssistant()) {
-                return topStack.isStackTranslucent(starting, this);
-            }
+        if (mStackSupervisor.isFocusedStack(this)) {
             return true;
         }
 
-        // Set home stack to invisible when it is below but not immediately below the docked stack
-        // A case would be if recents stack exists but has no tasks and is below the docked stack
-        // and home stack is below recents
-        if (activityType == ACTIVITY_TYPE_HOME) {
-            final ActivityStack splitScreenStack = display.getSplitScreenPrimaryStack();
-            int dockedStackIndex = display.getIndexOf(splitScreenStack);
-            if (dockedStackIndex > stackIndex && stackIndex != dockedStackIndex - 1) {
-                return false;
-            }
-        }
-
-        // Find the first stack behind front stack that actually got something visible.
-        int stackBehindTopIndex = display.getIndexOf(topStack) - 1;
-        while (stackBehindTopIndex >= 0 &&
-                display.getChildAt(stackBehindTopIndex).topRunningActivityLocked() == null) {
-            stackBehindTopIndex--;
-        }
-        final ActivityStack stackBehindTop = (stackBehindTopIndex >= 0)
-                ? display.getChildAt(stackBehindTopIndex) : null;
-        int stackBehindTopWindowingMode = WINDOWING_MODE_UNDEFINED;
-        int stackBehindTopActivityType = ACTIVITY_TYPE_UNDEFINED;
-        if (stackBehindTop != null) {
-            stackBehindTopWindowingMode = stackBehindTop.getWindowingMode();
-            stackBehindTopActivityType = stackBehindTop.getActivityType();
-        }
-
-        final boolean alwaysOnTop = topStack.getWindowConfiguration().isAlwaysOnTop();
-        if (topStack.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY || alwaysOnTop) {
-            if (this == stackBehindTop) {
-                // Stacks directly behind the docked or pinned stack are always visible.
-                return true;
-            } else if (alwaysOnTop && stackIndex == stackBehindTopIndex - 1) {
-                // Otherwise, this stack can also be visible if it is directly behind a docked stack
-                // or translucent assistant stack behind an always-on-top top-most stack
-                if (stackBehindTopWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
-                    return true;
-                } else if (stackBehindTopActivityType == ACTIVITY_TYPE_ASSISTANT) {
-                    return stackBehindTop.isStackTranslucent(starting, this);
-                }
-            }
+        final ActivityRecord top = topRunningActivityLocked();
+        if (top == null && isInStackLocked(starting) == null && !isTopStackOnDisplay()) {
+            // Shouldn't be visible if you don't have any running activities, not starting one, and
+            // not the top stack on display.
+            return false;
         }
 
-        if (topStack.isBackdropToTranslucentActivity()
-                && topStack.isStackTranslucent(starting, stackBehindTop)) {
-            // Stacks behind the fullscreen or assistant stack with a translucent activity are
-            // always visible so they can act as a backdrop to the translucent activity.
-            // For example, dialog activities
-            if (stackIndex == stackBehindTopIndex) {
+        final ActivityDisplay display = getDisplay();
+        boolean gotOpaqueSplitScreenPrimary = false;
+        boolean gotOpaqueSplitScreenSecondary = false;
+        final int windowingMode = getWindowingMode();
+        for (int i = display.getChildCount() - 1; i >= 0; --i) {
+            final ActivityStack other = display.getChildAt(i);
+            if (other == this) {
+                // Should be visible if there is no other stack occluding it.
                 return true;
             }
-            if (stackBehindTopIndex >= 0) {
-                if ((stackBehindTopWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                        || stackBehindTopWindowingMode == WINDOWING_MODE_PINNED)
-                        && stackIndex == (stackBehindTopIndex - 1)) {
-                    // The stack behind the docked or pinned stack is also visible so we can have a
-                    // complete backdrop to the translucent activity when the docked stack is up.
-                    return true;
-                }
-            }
-        }
-
-        if (isOnHomeDisplay()) {
-            // Visibility of any stack on default display should have been determined by the
-            // conditions above.
-            return false;
-        }
 
-        final int stackCount = display.getChildCount();
-        for (int i = stackIndex + 1; i < stackCount; i++) {
-            final ActivityStack stack = display.getChildAt(i);
+            final int otherWindowingMode = other.getWindowingMode();
+            // TODO: Can be removed once we are no longer using returnToType for back functionality
+            final ActivityStack stackBehind = i > 0 ? display.getChildAt(i - 1) : null;
 
-            if (!stack.mFullscreen && !stack.hasFullscreenTask()) {
-                continue;
-            }
-
-            if (!stack.isDynamicStacksVisibleBehindAllowed()) {
-                // These stacks can't have any dynamic stacks visible behind them.
+            if (otherWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+                if (other.isStackTranslucent(starting, stackBehind)) {
+                    // Can be visible behind a translucent fullscreen stack.
+                    continue;
+                }
                 return false;
+            } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                    && !gotOpaqueSplitScreenPrimary) {
+                gotOpaqueSplitScreenPrimary =
+                        !other.isStackTranslucent(starting, stackBehind);
+                if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                        && gotOpaqueSplitScreenPrimary) {
+                    // Can not be visible behind another opaque stack in split-screen-primary mode.
+                    return false;
+                }
+            } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+                    && !gotOpaqueSplitScreenSecondary) {
+                gotOpaqueSplitScreenSecondary =
+                        !other.isStackTranslucent(starting, stackBehind);
+                if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+                        && gotOpaqueSplitScreenSecondary) {
+                    // Can not be visible behind another opaque stack in split-screen-secondary mode.
+                    return false;
+                }
             }
-
-            if (!stack.isStackTranslucent(starting, null /* stackBehind */)) {
+            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;
             }
         }
 
+        // Well, nothing is stopping you from being visible...
         return true;
     }
 
-    private boolean isBackdropToTranslucentActivity() {
-        if (isActivityTypeAssistant()) {
-            return true;
-        }
-        final int windowingMode = getWindowingMode();
-        return windowingMode == WINDOWING_MODE_FULLSCREEN
-                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-    }
-
-    private boolean isDynamicStacksVisibleBehindAllowed() {
-        return isActivityTypeAssistant() || getWindowingMode() == WINDOWING_MODE_PINNED;
-    }
-
     final int rankTaskLayers(int baseLayer) {
         int layer = 0;
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
@@ -5263,7 +5193,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
     public String toString() {
         return "ActivityStack{" + Integer.toHexString(System.identityHashCode(this))
                 + " stackId=" + mStackId + " type=" + activityTypeToString(getActivityType())
-                + " mode=" + windowingModeToString(getWindowingMode()) + ", "
+                + " mode=" + windowingModeToString(getWindowingMode())
+                + " visible=" + shouldBeVisible(null /* starting */) + ", "
                 + mTaskHistory.size() + " tasks}";
     }
 
index c15b5e2..7a4a0d4 100644 (file)
@@ -110,7 +110,6 @@ import android.app.AppOpsManager;
 import android.app.ProfilerInfo;
 import android.app.ResultInfo;
 import android.app.WaitResult;
-import android.app.WindowConfiguration;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -223,13 +222,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
     // at the top of its container (e.g. stack).
     static final boolean ON_TOP = true;
 
-    // Used to indicate that an objects (e.g. task) removal from its container
-    // (e.g. stack) is due to it moving to another container.
-    static final boolean MOVING = true;
-
-    // Force the focus to change to the stack we are moving a task to..
-    static final boolean FORCE_FOCUS = true;
-
     // Don't execute any calls to resume.
     static final boolean DEFER_RESUME = true;
 
@@ -405,6 +397,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
      * object each time.
      */
     private final Rect tempRect = new Rect();
+    private final ActivityOptions mTmpOptions = ActivityOptions.makeBasic();
 
     // The default minimal size that will be used if the activity doesn't specify its minimal size.
     // It will be calculated when the default display gets added.
@@ -2186,89 +2179,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
         return null;
     }
 
-    /**
-     * Returns true if the {@param windowingMode} is supported based on other parameters passed in.
-     * @param windowingMode The windowing mode we are checking support for.
-     * @param supportsMultiWindow If we should consider support for multi-window mode in general.
-     * @param supportsSplitScreen If we should consider support for split-screen multi-window.
-     * @param supportsFreeform If we should consider support for freeform multi-window.
-     * @param supportsPip If we should consider support for picture-in-picture mutli-window.
-     * @param activityType The activity type under consideration.
-     * @return true if the windowing mode is supported.
-     */
-    boolean isWindowingModeSupported(int windowingMode, boolean supportsMultiWindow,
-            boolean supportsSplitScreen, boolean supportsFreeform, boolean supportsPip,
-            int activityType) {
-
-        if (windowingMode == WINDOWING_MODE_UNDEFINED
-                || windowingMode == WINDOWING_MODE_FULLSCREEN) {
-            return true;
-        }
-        if (!supportsMultiWindow) {
-            return false;
-        }
-
-        if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
-            return supportsSplitScreen && WindowConfiguration.supportSplitScreenWindowingMode(
-                    windowingMode, activityType);
-        }
-
-        if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) {
-            return false;
-        }
-
-        if (!supportsPip && windowingMode == WINDOWING_MODE_PINNED) {
-            return false;
-        }
-        return true;
-    }
-
-    private int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
-            @Nullable TaskRecord task, int activityType) {
-
-        // First preference if the windowing mode in the activity options if set.
-        int windowingMode = (options != null)
-                ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
-
-        // If windowing mode is unset, then next preference is the candidate task, then the
-        // activity record.
-        if (windowingMode == WINDOWING_MODE_UNDEFINED) {
-            if (task != null) {
-                windowingMode = task.getWindowingMode();
-            }
-            if (windowingMode == WINDOWING_MODE_UNDEFINED && r != null) {
-                windowingMode = r.getWindowingMode();
-            }
-        }
-
-        // Make sure the windowing mode we are trying to use makes sense for what is supported.
-        boolean supportsMultiWindow = mService.mSupportsMultiWindow;
-        boolean supportsSplitScreen = mService.mSupportsSplitScreenMultiWindow;
-        boolean supportsFreeform = mService.mSupportsFreeformWindowManagement;
-        boolean supportsPip = mService.mSupportsPictureInPicture;
-        if (supportsMultiWindow) {
-            if (task != null) {
-                supportsMultiWindow = task.isResizeable();
-                supportsSplitScreen = task.supportsSplitScreenWindowingMode();
-                // TODO: Do we need to check for freeform and Pip support here?
-            } else if (r != null) {
-                supportsMultiWindow = r.isResizeable();
-                supportsSplitScreen = r.supportsSplitScreenWindowingMode();
-                supportsFreeform = r.supportsFreeform();
-                supportsPip = r.supportsPictureInPicture();
-            }
-        }
-
-        if (windowingMode != WINDOWING_MODE_UNDEFINED
-                && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
-                supportsFreeform, supportsPip, activityType)) {
-            return windowingMode;
-        }
-        // Return root/systems windowing mode
-        return getWindowingMode();
-    }
-
     int resolveActivityType(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
             @Nullable TaskRecord task) {
         // Preference is given to the activity type for the activity then the task since the type
@@ -2329,7 +2239,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
         }
 
         final int activityType = resolveActivityType(r, options, candidateTask);
-        int windowingMode = resolveWindowingMode(r, options, candidateTask, activityType);
         T stack = null;
 
         // Next preference for stack goes to the display Id set in the activity options or the
@@ -2347,7 +2256,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
             }
             final ActivityDisplay display = getActivityDisplayOrCreateLocked(displayId);
             if (display != null) {
-                stack = display.getOrCreateStack(windowingMode, activityType, onTop);
+                stack = display.getOrCreateStack(r, options, candidateTask, activityType, onTop);
                 if (stack != null) {
                     return stack;
                 }
@@ -2365,10 +2274,14 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
             stack = r.getStack();
         }
         if (stack != null) {
-            if (stack.isCompatible(windowingMode, activityType)) {
-                return stack;
-            }
             display = stack.getDisplay();
+            if (display != null) {
+                final int windowingMode =
+                        display.resolveWindowingMode(r, options, candidateTask, activityType);
+                if (stack.isCompatible(windowingMode, activityType)) {
+                    return stack;
+                }
+            }
         }
 
         if (display == null
@@ -2379,7 +2292,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
             display = getDefaultDisplay();
         }
 
-        return display.getOrCreateStack(windowingMode, activityType, onTop);
+        return display.getOrCreateStack(r, options, candidateTask, activityType, onTop);
     }
 
     /**
@@ -2596,6 +2509,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
             final ActivityDisplay toDisplay = getActivityDisplay(toDisplayId);
 
             if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+                // Tell the display we are exiting split-screen mode.
+                toDisplay.onExitingSplitScreenMode();
                 // We are moving all tasks from the docked stack to the fullscreen stack,
                 // which is dismissing the docked stack, so resize all other stacks to
                 // fullscreen here already so we don't end up with resize trashing.
@@ -2625,35 +2540,34 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
             final ArrayList<TaskRecord> tasks = fromStack.getAllTasks();
 
             if (!tasks.isEmpty()) {
+                mTmpOptions.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
                 final int size = tasks.size();
-                final ActivityStack fullscreenStack = toDisplay.getOrCreateStack(
-                        WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, onTop);
-
-                if (onTop) {
-                    final int returnToType =
-                            toDisplay.getTopVisibleStackActivityType(WINDOWING_MODE_PINNED);
-                    for (int i = 0; i < size; i++) {
-                        final TaskRecord task = tasks.get(i);
+                for (int i = 0; i < size; ++i) {
+                    final TaskRecord task = tasks.get(i);
+                    final ActivityStack toStack = toDisplay.getOrCreateStack(
+                                null, mTmpOptions, task, task.getActivityType(), onTop);
+
+                    if (onTop) {
+                        final int returnToType =
+                                toDisplay.getTopVisibleStackActivityType(WINDOWING_MODE_PINNED);
                         final boolean isTopTask = i == (size - 1);
                         if (inPinnedWindowingMode) {
-                            // Update the return-to to reflect where the pinned stack task was moved
-                            // from so that we retain the stack that was previously visible if the
-                            // pinned stack is recreated. See moveActivityToPinnedStackLocked().
+                            // Update the return-to to reflect where the pinned stack task was
+                            // moved from so that we retain the stack that was previously
+                            // visible if the pinned stack is recreated.
+                            // See moveActivityToPinnedStackLocked().
                             task.setTaskToReturnTo(returnToType);
                         }
                         // Defer resume until all the tasks have been moved to the fullscreen stack
-                        task.reparent(fullscreenStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT,
+                        task.reparent(toStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT,
                                 isTopTask /* animate */, DEFER_RESUME,
                                 schedulePictureInPictureModeChange,
                                 "moveTasksToFullscreenStack - onTop");
-                    }
-                } else {
-                    for (int i = 0; i < size; i++) {
-                        final TaskRecord task = tasks.get(i);
+                    } else {
                         // Position the tasks in the fullscreen stack in order at the bottom of the
                         // stack. Also defer resume until all the tasks have been moved to the
                         // fullscreen stack.
-                        task.reparent(fullscreenStack, i /* position */,
+                        task.reparent(toStack, ON_TOP,
                                 REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE, DEFER_RESUME,
                                 schedulePictureInPictureModeChange,
                                 "moveTasksToFullscreenStack - NOT_onTop");
@@ -3080,23 +2994,16 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
                     + " task=" + task);
         }
 
-        // We don't allow moving a unresizeable task to the docked stack since the docked stack is
-        // used for split-screen mode and will cause things like the docked divider to show up. We
-        // instead leave the task in its current stack or move it to the fullscreen stack if it
-        // isn't currently in a stack.
+        // Leave the task in its current stack or a fullscreen stack if it isn't resizeable and the
+        // preferred stack is in multi-window mode.
         if (inMultiWindowMode && !task.isResizeable()) {
-            Slog.w(TAG, "Can not move unresizeable task=" + task + " to docked stack."
-                    + " Moving to stackId=" + stackId + " instead.");
-            // Temporarily disable resizeablility of the task as we don't want it to be resized if,
-            // for example, a docked stack is created which will lead to the stack we are moving
-            // from being resized and and its resizeable tasks being resized.
-            try {
-                task.mTemporarilyUnresizable = true;
-                stack = stack.getDisplay().createStack(
-                        WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), toTop);
-            } finally {
-                task.mTemporarilyUnresizable = false;
+            Slog.w(TAG, "Can not move unresizeable task=" + task + " to multi-window stack=" + stack
+                    + " Moving to a fullscreen stack instead.");
+            if (prevStack != null) {
+                return prevStack;
             }
+            stack = stack.getDisplay().createStack(
+                    WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), toTop);
         }
         return stack;
     }
@@ -4287,9 +4194,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
         final boolean isSecondaryDisplayPreferred =
                 (preferredDisplayId != DEFAULT_DISPLAY && preferredDisplayId != INVALID_DISPLAY);
         final boolean inSplitScreenMode = actualStack != null
-                && actualStack.inSplitScreenWindowingMode();
+                && actualStack.getDisplay().hasSplitScreenPrimaryStack();
         if (((!inSplitScreenMode && preferredWindowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
-                && !isSecondaryDisplayPreferred) || task.isActivityTypeHome()) {
+                && !isSecondaryDisplayPreferred) || !task.isActivityTypeStandardOrUndefined()) {
             return;
         }
 
index c451235..4080c27 100644 (file)
@@ -231,9 +231,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
     private boolean mSupportsPictureInPicture;  // Whether or not this task and its activities
             // support PiP. Based on the {@link ActivityInfo#FLAG_SUPPORTS_PICTURE_IN_PICTURE} flag
             // of the root activity.
-    boolean mTemporarilyUnresizable; // Separate flag from mResizeMode used to suppress resize
-                                     // changes on a temporary basis.
-
     /** Can't be put in lockTask mode. */
     final static int LOCK_TASK_AUTH_DONT_LOCK = 0;
     /** Can enter app pinning with user approval. Can never start over existing lockTask task. */
@@ -1457,7 +1454,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
 
     private boolean isResizeable(boolean checkSupportsPip) {
         return (mService.mForceResizableActivities || ActivityInfo.isResizeableMode(mResizeMode)
-                || (checkSupportsPip && mSupportsPictureInPicture)) && !mTemporarilyUnresizable;
+                || (checkSupportsPip && mSupportsPictureInPicture));
     }
 
     boolean isResizeable() {
index 679be1d..9e4a9e9 100644 (file)
@@ -57,7 +57,7 @@ public class ActivityRecordTests extends ActivityTestsBase {
             ComponentName.unflattenFromString("com.foo/.BarActivity2");
 
     private ActivityManagerService mService;
-    private ActivityStack mStack;
+    private TestActivityStack mStack;
     private TaskRecord mTask;
     private ActivityRecord mActivity;
 
@@ -76,13 +76,13 @@ public class ActivityRecordTests extends ActivityTestsBase {
     @Test
     public void testStackCleanupOnClearingTask() throws Exception {
         mActivity.setTask(null);
-        assertEquals(getActivityRemovedFromStackCount(), 1);
+        assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 1);
     }
 
     @Test
     public void testStackCleanupOnActivityRemoval() throws Exception {
         mTask.removeActivity(mActivity);
-        assertEquals(getActivityRemovedFromStackCount(),  1);
+        assertEquals(mStack.onActivityRemovedFromStackInvocationCount(),  1);
     }
 
     @Test
@@ -97,7 +97,7 @@ public class ActivityRecordTests extends ActivityTestsBase {
         final TaskRecord newTask =
                 createTask(mService.mStackSupervisor, testActivityComponent, mStack);
         mActivity.reparent(newTask, 0, null /*reason*/);
-        assertEquals(getActivityRemovedFromStackCount(), 0);
+        assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 0);
     }
 
     @Test
@@ -129,15 +129,6 @@ public class ActivityRecordTests extends ActivityTestsBase {
         assertEquals(expectedActivityBounds, mActivity.getBounds());
     }
 
-    private int getActivityRemovedFromStackCount() {
-        if (mStack instanceof ActivityStackReporter) {
-            return ((ActivityStackReporter) mStack).onActivityRemovedFromStackInvocationCount();
-        }
-
-        return -1;
-    }
-
-
     @Test
     public void testCanBeLaunchedOnDisplay() throws Exception {
         testSupportsLaunchingResizeable(false /*taskPresent*/, true /*taskResizeable*/,
index 4ee1f47..e17e51b 100644 (file)
 
 package com.android.server.am;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_DESTROYING;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
@@ -45,7 +51,6 @@ import org.junit.Test;
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 public class ActivityStackTests extends ActivityTestsBase {
-    private static final int TEST_STACK_ID = 100;
     private static final ComponentName testActivityComponent =
             ComponentName.unflattenFromString("com.foo/.BarActivity");
     private static final ComponentName testOverlayComponent =
@@ -127,4 +132,122 @@ public class ActivityStackTests extends ActivityTestsBase {
         assertEquals(mTask.getTopActivity(true /* includeOverlays */), taskOverlay);
         assertNotNull(result.r);
     }
+
+    @Test
+    public void testShouldBeVisible_Fullscreen() throws Exception {
+        final ActivityDisplay display = mService.mStackSupervisor.getDefaultDisplay();
+        final TestActivityStack homeStack = createStackForShouldBeVisibleTest(display,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+        final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(display,
+                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+
+        assertTrue(homeStack.shouldBeVisible(null /* starting */));
+        assertTrue(pinnedStack.shouldBeVisible(null /* starting */));
+
+        final TestActivityStack fullscreenStack = createStackForShouldBeVisibleTest(display,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        // Home stack shouldn't be visible behind an opaque fullscreen stack, but pinned stack
+        // should be visible since it is always on-top.
+        fullscreenStack.setIsTranslucent(false);
+        assertFalse(homeStack.shouldBeVisible(null /* starting */));
+        assertTrue(pinnedStack.shouldBeVisible(null /* starting */));
+        assertTrue(fullscreenStack.shouldBeVisible(null /* starting */));
+
+        // Home stack should be visible behind a translucent fullscreen stack.
+        fullscreenStack.setIsTranslucent(true);
+        assertTrue(homeStack.shouldBeVisible(null /* starting */));
+        assertTrue(pinnedStack.shouldBeVisible(null /* starting */));
+    }
+
+    @Test
+    public void testShouldBeVisible_SplitScreen() throws Exception {
+        final ActivityDisplay display = mService.mStackSupervisor.getDefaultDisplay();
+        final TestActivityStack homeStack = createStackForShouldBeVisibleTest(display,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+        final TestActivityStack splitScreenPrimary = createStackForShouldBeVisibleTest(display,
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        final TestActivityStack splitScreenSecondary = createStackForShouldBeVisibleTest(display,
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+
+        // Home stack shouldn't be visible if both halves of split-screen are opaque.
+        splitScreenPrimary.setIsTranslucent(false);
+        splitScreenSecondary.setIsTranslucent(false);
+        assertFalse(homeStack.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenSecondary.shouldBeVisible(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 */));
+
+        final TestActivityStack splitScreenSecondary2 = createStackForShouldBeVisibleTest(display,
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        // First split-screen secondary shouldn't be visible behind another opaque split-split
+        // secondary.
+        splitScreenSecondary2.setIsTranslucent(false);
+        assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
+
+        // First split-screen secondary should be visible behind another translucent split-split
+        // secondary.
+        splitScreenSecondary2.setIsTranslucent(true);
+        assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
+
+        final TestActivityStack assistantStack = createStackForShouldBeVisibleTest(display,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */);
+
+        // Split-screen stacks shouldn't be visible behind an opaque fullscreen stack.
+        assistantStack.setIsTranslucent(false);
+        assertTrue(assistantStack.shouldBeVisible(null /* starting */));
+        assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */));
+        assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
+        assertFalse(splitScreenSecondary2.shouldBeVisible(null /* starting */));
+
+        // Split-screen stacks should be visible behind a translucent fullscreen stack.
+        assistantStack.setIsTranslucent(true);
+        assertTrue(assistantStack.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
+    }
+
+    @Test
+    public void testShouldBeVisible_Finishing() throws Exception {
+        final ActivityDisplay display = mService.mStackSupervisor.getDefaultDisplay();
+        final TestActivityStack homeStack = createStackForShouldBeVisibleTest(display,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+        final TestActivityStack translucentStack = createStackForShouldBeVisibleTest(display,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        translucentStack.setIsTranslucent(true);
+
+        assertTrue(homeStack.shouldBeVisible(null /* starting */));
+        assertTrue(translucentStack.shouldBeVisible(null /* starting */));
+
+        final ActivityRecord topRunningHomeActivity = homeStack.topRunningActivityLocked();
+        topRunningHomeActivity.finishing = true;
+        final ActivityRecord topRunningTranslucentActivity =
+                translucentStack.topRunningActivityLocked();
+        topRunningTranslucentActivity.finishing = true;
+
+        // Home shouldn't be visible since its activity is marked as finishing and it isn't the top
+        // of the stack list.
+        assertFalse(homeStack.shouldBeVisible(null /* starting */));
+        // Home should be visible if we are starting an activity within it.
+        assertTrue(homeStack.shouldBeVisible(topRunningHomeActivity /* starting */));
+        // The translucent stack should be visible since it is the top of the stack list even though
+        // it has its activity marked as finishing.
+        assertTrue(translucentStack.shouldBeVisible(null /* starting */));
+    }
+
+    private <T extends ActivityStack> T createStackForShouldBeVisibleTest(
+            ActivityDisplay display, int windowingMode, int activityType, boolean onTop) {
+        final T stack = display.createStack(windowingMode, activityType, onTop);
+        // Create a task and activity in the stack so that it has a top running activity.
+        final TaskRecord task = createTask(mSupervisor, testActivityComponent, stack);
+        final ActivityRecord r = createActivity(mService, testActivityComponent, task, 0);
+        return stack;
+    }
 }
index d36f9d3..f5cdf21 100644 (file)
@@ -276,20 +276,21 @@ public class ActivityTestsBase {
         return service;
     }
 
-    protected interface ActivityStackReporter {
-        int onActivityRemovedFromStackInvocationCount();
-    }
-
     /**
      * Overrided of {@link ActivityStack} that tracks test metrics, such as the number of times a
      * method is called. Note that its functionality depends on the implementations of the
      * construction arguments.
      */
     protected static class TestActivityStack<T extends StackWindowController>
-            extends ActivityStack<T> implements ActivityStackReporter {
+            extends ActivityStack<T> {
         private int mOnActivityRemovedFromStackCount = 0;
         private T mContainerController;
 
+        static final int IS_TRANSLUCENT_UNSET = 0;
+        static final int IS_TRANSLUCENT_FALSE = 1;
+        static final int IS_TRANSLUCENT_TRUE = 2;
+        private int mIsTranslucent = IS_TRANSLUCENT_UNSET;
+
         TestActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor,
                 int windowingMode, int activityType, boolean onTop) {
             super(display, stackId, supervisor, windowingMode, activityType, onTop);
@@ -302,8 +303,7 @@ public class ActivityTestsBase {
         }
 
         // Returns the number of times {@link #onActivityRemovedFromStack} has been called
-        @Override
-        public int onActivityRemovedFromStackInvocationCount() {
+        int onActivityRemovedFromStackInvocationCount() {
             return mOnActivityRemovedFromStackCount;
         }
 
@@ -317,5 +317,22 @@ public class ActivityTestsBase {
         T getWindowContainerController() {
             return mContainerController;
         }
+
+        void setIsTranslucent(boolean isTranslucent) {
+            mIsTranslucent = isTranslucent ? IS_TRANSLUCENT_TRUE : IS_TRANSLUCENT_FALSE;
+        }
+
+        @Override
+        boolean isStackTranslucent(ActivityRecord starting, ActivityStack stackBehind) {
+            switch (mIsTranslucent) {
+                case IS_TRANSLUCENT_TRUE:
+                    return true;
+                case IS_TRANSLUCENT_FALSE:
+                    return false;
+                case IS_TRANSLUCENT_UNSET:
+                default:
+                    return super.isStackTranslucent(starting, stackBehind);
+            }
+        }
     }
 }