| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License |
| */ |
| |
| package com.android.server.am; |
| |
| import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; |
| import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; |
| import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; |
| import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; |
| 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.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; |
| import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; |
| import static android.view.Display.DEFAULT_DISPLAY; |
| import static android.view.Display.FLAG_PRIVATE; |
| import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT; |
| import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK; |
| import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STACK; |
| import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; |
| import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; |
| import static com.android.server.am.ActivityDisplayProto.CONFIGURATION_CONTAINER; |
| import static com.android.server.am.ActivityDisplayProto.STACKS; |
| import static com.android.server.am.ActivityDisplayProto.ID; |
| |
| import android.annotation.Nullable; |
| import android.app.ActivityManagerInternal; |
| import android.app.ActivityOptions; |
| import android.app.WindowConfiguration; |
| import android.graphics.Point; |
| import android.util.IntArray; |
| import android.util.Slog; |
| import android.util.proto.ProtoOutputStream; |
| import android.view.Display; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.server.wm.ConfigurationContainer; |
| import com.android.server.wm.DisplayWindowController; |
| |
| import com.android.server.wm.WindowContainerListener; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| |
| /** |
| * Exactly one of these classes per Display in the system. Capable of holding zero or more |
| * attached {@link ActivityStack}s. |
| */ |
| class ActivityDisplay extends ConfigurationContainer<ActivityStack> |
| implements WindowContainerListener { |
| private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityDisplay" : TAG_AM; |
| private static final String TAG_STACK = TAG + POSTFIX_STACK; |
| |
| static final int POSITION_TOP = Integer.MAX_VALUE; |
| static final int POSITION_BOTTOM = Integer.MIN_VALUE; |
| |
| |
| /** |
| * Counter for next free stack ID to use for dynamic activity stacks. Unique across displays. |
| */ |
| private static int sNextFreeStackId = 0; |
| |
| private ActivityStackSupervisor mSupervisor; |
| /** Actual Display this object tracks. */ |
| int mDisplayId; |
| Display mDisplay; |
| |
| /** |
| * All of the stacks on this display. Order matters, topmost stack is in front of all other |
| * stacks, bottommost behind. Accessed directly by ActivityManager package classes. Any calls |
| * changing the list should also call {@link #onStackOrderChanged()}. |
| */ |
| private final ArrayList<ActivityStack> mStacks = new ArrayList<>(); |
| private ArrayList<OnStackOrderChangedListener> mStackOrderChangedCallbacks = new ArrayList<>(); |
| |
| /** Array of all UIDs that are present on the display. */ |
| private IntArray mDisplayAccessUIDs = new IntArray(); |
| |
| /** All tokens used to put activities on this stack to sleep (including mOffToken) */ |
| final ArrayList<ActivityManagerInternal.SleepToken> mAllSleepTokens = new ArrayList<>(); |
| /** The token acquired by ActivityStackSupervisor to put stacks on the display to sleep */ |
| ActivityManagerInternal.SleepToken mOffToken; |
| |
| private boolean mSleeping; |
| |
| // Cached reference to some special stacks we tend to get a lot so we don't need to loop |
| // through the list to find them. |
| private ActivityStack mHomeStack = null; |
| private ActivityStack mRecentsStack = null; |
| private ActivityStack mPinnedStack = null; |
| private ActivityStack mSplitScreenPrimaryStack = null; |
| |
| // Used in updating the display size |
| private Point mTmpDisplaySize = new Point(); |
| |
| private DisplayWindowController mWindowContainerController; |
| |
| @VisibleForTesting |
| ActivityDisplay(ActivityStackSupervisor supervisor, int displayId) { |
| this(supervisor, supervisor.mDisplayManager.getDisplay(displayId)); |
| } |
| |
| ActivityDisplay(ActivityStackSupervisor supervisor, Display display) { |
| mSupervisor = supervisor; |
| mDisplayId = display.getDisplayId(); |
| mDisplay = display; |
| mWindowContainerController = createWindowContainerController(); |
| updateBounds(); |
| } |
| |
| protected DisplayWindowController createWindowContainerController() { |
| return new DisplayWindowController(mDisplay, this); |
| } |
| |
| void updateBounds() { |
| mDisplay.getSize(mTmpDisplaySize); |
| setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y); |
| } |
| |
| void addChild(ActivityStack stack, int position) { |
| if (position == POSITION_BOTTOM) { |
| position = 0; |
| } else if (position == POSITION_TOP) { |
| position = mStacks.size(); |
| } |
| if (DEBUG_STACK) Slog.v(TAG_STACK, "addChild: attaching " + stack |
| + " to displayId=" + mDisplayId + " position=" + position); |
| addStackReferenceIfNeeded(stack); |
| positionChildAt(stack, position); |
| mSupervisor.mService.updateSleepIfNeededLocked(); |
| } |
| |
| void removeChild(ActivityStack stack) { |
| if (DEBUG_STACK) Slog.v(TAG_STACK, "removeChild: detaching " + stack |
| + " from displayId=" + mDisplayId); |
| mStacks.remove(stack); |
| removeStackReferenceIfNeeded(stack); |
| mSupervisor.mService.updateSleepIfNeededLocked(); |
| onStackOrderChanged(); |
| } |
| |
| void positionChildAtTop(ActivityStack stack) { |
| positionChildAt(stack, mStacks.size()); |
| } |
| |
| void positionChildAtBottom(ActivityStack stack) { |
| positionChildAt(stack, 0); |
| } |
| |
| private void positionChildAt(ActivityStack stack, int position) { |
| // TODO: Keep in sync with WindowContainer.positionChildAt(), once we change that to adjust |
| // the position internally, also update the logic here |
| mStacks.remove(stack); |
| final int insertPosition = getTopInsertPosition(stack, position); |
| mStacks.add(insertPosition, stack); |
| mWindowContainerController.positionChildAt(stack.getWindowContainerController(), |
| insertPosition); |
| onStackOrderChanged(); |
| } |
| |
| private int getTopInsertPosition(ActivityStack stack, int candidatePosition) { |
| int position = mStacks.size(); |
| if (position > 0) { |
| final ActivityStack topStack = mStacks.get(position - 1); |
| if (topStack.getWindowConfiguration().isAlwaysOnTop() && topStack != stack) { |
| // If the top stack is always on top, we move this stack just below it. |
| position--; |
| } |
| } |
| return Math.min(position, candidatePosition); |
| } |
| |
| <T extends ActivityStack> T getStack(int stackId) { |
| for (int i = mStacks.size() - 1; i >= 0; --i) { |
| final ActivityStack stack = mStacks.get(i); |
| if (stack.mStackId == stackId) { |
| return (T) stack; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * @return the topmost stack on the display that is compatible with the input windowing mode and |
| * activity type. {@code null} means no compatible stack on the display. |
| * @see ConfigurationContainer#isCompatible(int, int) |
| */ |
| <T extends ActivityStack> T getStack(int windowingMode, int activityType) { |
| if (activityType == ACTIVITY_TYPE_HOME) { |
| return (T) mHomeStack; |
| } else if (activityType == ACTIVITY_TYPE_RECENTS) { |
| return (T) mRecentsStack; |
| } |
| if (windowingMode == WINDOWING_MODE_PINNED) { |
| return (T) mPinnedStack; |
| } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { |
| return (T) mSplitScreenPrimaryStack; |
| } |
| |
| for (int i = mStacks.size() - 1; i >= 0; --i) { |
| final ActivityStack stack = mStacks.get(i); |
| if (stack.isCompatible(windowingMode, activityType)) { |
| return (T) stack; |
| } |
| } |
| return null; |
| } |
| |
| private boolean alwaysCreateStack(int windowingMode, int activityType) { |
| // Always create a stack for fullscreen, freeform, and split-screen-secondary windowing |
| // modes so that we can manage visual ordering and return types correctly. |
| return activityType == ACTIVITY_TYPE_STANDARD |
| && (windowingMode == WINDOWING_MODE_FULLSCREEN |
| || windowingMode == WINDOWING_MODE_FREEFORM |
| || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); |
| } |
| |
| /** |
| * Returns an existing stack compatible with the windowing mode and activity type or creates one |
| * if a compatible stack doesn't exist. |
| * @see #getStack(int, int) |
| * @see #createStack(int, int, boolean) |
| */ |
| <T extends ActivityStack> T getOrCreateStack(int windowingMode, int activityType, |
| boolean onTop) { |
| if (!alwaysCreateStack(windowingMode, activityType)) { |
| T stack = getStack(windowingMode, activityType); |
| if (stack != null) { |
| return stack; |
| } |
| } |
| return createStack(windowingMode, activityType, onTop); |
| } |
| |
| /** |
| * 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); |
| } |
| |
| private int getNextStackId() { |
| return sNextFreeStackId++; |
| } |
| |
| /** |
| * 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 |
| * be created in {@link WindowConfiguration#WINDOWING_MODE_FULLSCREEN}. |
| * @param activityType The activityType the stack should be created in. If |
| * {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} then the stack will |
| * be created in {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}. |
| * @param onTop If true the stack will be created at the top of the display, else at the bottom. |
| * @return The newly created stack. |
| */ |
| <T extends ActivityStack> T createStack(int windowingMode, int activityType, boolean onTop) { |
| |
| if (activityType == ACTIVITY_TYPE_UNDEFINED) { |
| // Can't have an undefined stack type yet...so re-map to standard. Anyone that wants |
| // anything else should be passing it in anyways... |
| activityType = ACTIVITY_TYPE_STANDARD; |
| } |
| |
| if (activityType != ACTIVITY_TYPE_STANDARD) { |
| // For now there can be only one stack of a particular non-standard activity type on a |
| // display. So, get that ignoring whatever windowing mode it is currently in. |
| T stack = getStack(WINDOWING_MODE_UNDEFINED, activityType); |
| if (stack != null) { |
| throw new IllegalArgumentException("Stack=" + stack + " of activityType=" |
| + activityType + " already on display=" + this + ". Can't have multiple."); |
| } |
| } |
| |
| final ActivityManagerService service = mSupervisor.mService; |
| if (!isWindowingModeSupported(windowingMode, service.mSupportsMultiWindow, |
| service.mSupportsSplitScreenMultiWindow, service.mSupportsFreeformWindowManagement, |
| service.mSupportsPictureInPicture, activityType)) { |
| throw new IllegalArgumentException("Can't create stack for unsupported windowingMode=" |
| + windowingMode); |
| } |
| |
| if (windowingMode == WINDOWING_MODE_UNDEFINED) { |
| // TODO: Should be okay to have stacks with with undefined windowing mode long term, but |
| // have to set them to something for now due to logic that depending on them. |
| windowingMode = getWindowingMode(); // Put in current display's windowing mode |
| if (windowingMode == WINDOWING_MODE_UNDEFINED) { |
| // Else fullscreen for now... |
| windowingMode = WINDOWING_MODE_FULLSCREEN; |
| } |
| } |
| |
| final int stackId = getNextStackId(); |
| return 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); |
| } |
| 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 |
| */ |
| void removeStacksInWindowingModes(int... windowingModes) { |
| if (windowingModes == null || windowingModes.length == 0) { |
| return; |
| } |
| |
| for (int j = windowingModes.length - 1 ; j >= 0; --j) { |
| final int windowingMode = windowingModes[j]; |
| for (int i = mStacks.size() - 1; i >= 0; --i) { |
| final ActivityStack stack = mStacks.get(i); |
| if (!stack.isActivityTypeStandardOrUndefined()) { |
| continue; |
| } |
| if (stack.getWindowingMode() != windowingMode) { |
| continue; |
| } |
| mSupervisor.removeStack(stack); |
| } |
| } |
| } |
| |
| void removeStacksWithActivityTypes(int... activityTypes) { |
| if (activityTypes == null || activityTypes.length == 0) { |
| return; |
| } |
| |
| for (int j = activityTypes.length - 1 ; j >= 0; --j) { |
| final int activityType = activityTypes[j]; |
| for (int i = mStacks.size() - 1; i >= 0; --i) { |
| final ActivityStack stack = mStacks.get(i); |
| if (stack.getActivityType() == activityType) { |
| mSupervisor.removeStack(stack); |
| } |
| } |
| } |
| } |
| |
| void onStackWindowingModeChanged(ActivityStack stack) { |
| removeStackReferenceIfNeeded(stack); |
| addStackReferenceIfNeeded(stack); |
| } |
| |
| private void addStackReferenceIfNeeded(ActivityStack stack) { |
| final int activityType = stack.getActivityType(); |
| final int windowingMode = stack.getWindowingMode(); |
| |
| if (activityType == ACTIVITY_TYPE_HOME) { |
| if (mHomeStack != null && mHomeStack != stack) { |
| throw new IllegalArgumentException("addStackReferenceIfNeeded: home stack=" |
| + mHomeStack + " already exist on display=" + this + " stack=" + stack); |
| } |
| mHomeStack = stack; |
| } else if (activityType == ACTIVITY_TYPE_RECENTS) { |
| if (mRecentsStack != null && mRecentsStack != stack) { |
| throw new IllegalArgumentException("addStackReferenceIfNeeded: recents stack=" |
| + mRecentsStack + " already exist on display=" + this + " stack=" + stack); |
| } |
| mRecentsStack = stack; |
| } |
| if (windowingMode == WINDOWING_MODE_PINNED) { |
| if (mPinnedStack != null && mPinnedStack != stack) { |
| throw new IllegalArgumentException("addStackReferenceIfNeeded: pinned stack=" |
| + mPinnedStack + " already exist on display=" + this |
| + " stack=" + stack); |
| } |
| mPinnedStack = stack; |
| } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { |
| if (mSplitScreenPrimaryStack != null && mSplitScreenPrimaryStack != stack) { |
| throw new IllegalArgumentException("addStackReferenceIfNeeded:" |
| + " split-screen-primary" + " stack=" + mSplitScreenPrimaryStack |
| + " already exist on display=" + this + " stack=" + stack); |
| } |
| mSplitScreenPrimaryStack = stack; |
| onSplitScreenModeActivated(); |
| } |
| } |
| |
| private void removeStackReferenceIfNeeded(ActivityStack stack) { |
| if (stack == mHomeStack) { |
| mHomeStack = null; |
| } else if (stack == mRecentsStack) { |
| mRecentsStack = null; |
| } else if (stack == mPinnedStack) { |
| mPinnedStack = null; |
| } else if (stack == mSplitScreenPrimaryStack) { |
| mSplitScreenPrimaryStack = null; |
| // Inform the reset of the system that split-screen mode was dismissed so things like |
| // resizing all the other stacks can take place. |
| onSplitScreenModeDismissed(); |
| } |
| } |
| |
| private void onSplitScreenModeDismissed() { |
| mSupervisor.mWindowManager.deferSurfaceLayout(); |
| try { |
| // Adjust the windowing mode of any stack in secondary split-screen to fullscreen. |
| for (int i = mStacks.size() - 1; i >= 0; --i) { |
| final ActivityStack otherStack = mStacks.get(i); |
| if (!otherStack.inSplitScreenSecondaryWindowingMode()) { |
| continue; |
| } |
| otherStack.setWindowingMode(WINDOWING_MODE_FULLSCREEN, false /* animate */, |
| false /* showRecents */, false /* enteringSplitScreenMode */, |
| true /* deferEnsuringVisibility */); |
| } |
| } finally { |
| final ActivityStack topFullscreenStack = |
| getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN); |
| if (topFullscreenStack != null && mHomeStack != null && !isTopStack(mHomeStack)) { |
| // Whenever split-screen is dismissed we want the home stack directly behind the |
| // current top fullscreen stack so it shows up when the top stack is finished. |
| // TODO: Would be better to use ActivityDisplay.positionChildAt() for this, however |
| // ActivityDisplay doesn't have a direct controller to WM side yet. We can switch |
| // once we have that. |
| mHomeStack.moveToFront("onSplitScreenModeDismissed"); |
| topFullscreenStack.moveToFront("onSplitScreenModeDismissed"); |
| } |
| mSupervisor.mWindowManager.continueSurfaceLayout(); |
| } |
| } |
| |
| private void onSplitScreenModeActivated() { |
| mSupervisor.mWindowManager.deferSurfaceLayout(); |
| try { |
| // Adjust the windowing mode of any affected by split-screen to split-screen secondary. |
| for (int i = mStacks.size() - 1; i >= 0; --i) { |
| final ActivityStack otherStack = mStacks.get(i); |
| if (otherStack == mSplitScreenPrimaryStack |
| || !otherStack.affectedBySplitScreenResize()) { |
| continue; |
| } |
| otherStack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, |
| false /* animate */, false /* showRecents */, |
| true /* enteringSplitScreenMode */, true /* deferEnsuringVisibility */); |
| } |
| } finally { |
| mSupervisor.mWindowManager.continueSurfaceLayout(); |
| } |
| } |
| |
| /** |
| * 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(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; |
| } |
| // Try to use the display's windowing mode otherwise fallback to fullscreen. |
| windowingMode = getWindowingMode(); |
| return windowingMode != WINDOWING_MODE_UNDEFINED |
| ? windowingMode : WINDOWING_MODE_FULLSCREEN; |
| } |
| |
| /** |
| * Get the topmost stack on the display. It may be different from focused stack, because |
| * focus may be on another display. |
| */ |
| ActivityStack getTopStack() { |
| return mStacks.isEmpty() ? null : mStacks.get(mStacks.size() - 1); |
| } |
| |
| boolean isTopStack(ActivityStack stack) { |
| return stack == getTopStack(); |
| } |
| |
| boolean isTopNotPinnedStack(ActivityStack stack) { |
| for (int i = mStacks.size() - 1; i >= 0; --i) { |
| final ActivityStack current = mStacks.get(i); |
| if (!current.inPinnedWindowingMode()) { |
| return current == stack; |
| } |
| } |
| return false; |
| } |
| |
| ActivityStack getTopStackInWindowingMode(int windowingMode) { |
| for (int i = mStacks.size() - 1; i >= 0; --i) { |
| final ActivityStack current = mStacks.get(i); |
| if (windowingMode == current.getWindowingMode()) { |
| return current; |
| } |
| } |
| return null; |
| } |
| |
| int getIndexOf(ActivityStack stack) { |
| return mStacks.indexOf(stack); |
| } |
| |
| void onLockTaskPackagesUpdated() { |
| for (int i = mStacks.size() - 1; i >= 0; --i) { |
| mStacks.get(i).onLockTaskPackagesUpdated(); |
| } |
| } |
| |
| /** 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; |
| } |
| |
| boolean hasSplitScreenPrimaryStack() { |
| return mSplitScreenPrimaryStack != null; |
| } |
| |
| PinnedActivityStack getPinnedStack() { |
| return (PinnedActivityStack) mPinnedStack; |
| } |
| |
| boolean hasPinnedStack() { |
| return mPinnedStack != null; |
| } |
| |
| @Override |
| public String toString() { |
| return "ActivityDisplay={" + mDisplayId + " numStacks=" + mStacks.size() + "}"; |
| } |
| |
| @Override |
| protected int getChildCount() { |
| return mStacks.size(); |
| } |
| |
| @Override |
| protected ActivityStack getChildAt(int index) { |
| return mStacks.get(index); |
| } |
| |
| @Override |
| protected ConfigurationContainer getParent() { |
| return mSupervisor; |
| } |
| |
| boolean isPrivate() { |
| return (mDisplay.getFlags() & FLAG_PRIVATE) != 0; |
| } |
| |
| boolean isUidPresent(int uid) { |
| for (ActivityStack stack : mStacks) { |
| if (stack.isUidPresent(uid)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void remove() { |
| final boolean destroyContentOnRemoval = shouldDestroyContentOnRemove(); |
| while (getChildCount() > 0) { |
| final ActivityStack stack = getChildAt(0); |
| if (destroyContentOnRemoval) { |
| // Override the stack configuration to make it equal to the current applied one, so |
| // that we don't accidentally report configuration change to activities that are |
| // going to be finished. |
| stack.onOverrideConfigurationChanged(stack.getConfiguration()); |
| mSupervisor.moveStackToDisplayLocked(stack.mStackId, DEFAULT_DISPLAY, |
| false /* onTop */); |
| stack.finishAllActivitiesLocked(true /* immediately */); |
| } else { |
| // Moving all tasks to fullscreen stack, because it's guaranteed to be |
| // a valid launch stack for all activities. This way the task history from |
| // external display will be preserved on primary after move. |
| mSupervisor.moveTasksToFullscreenStackLocked(stack, true /* onTop */); |
| } |
| } |
| |
| mWindowContainerController.removeContainer(); |
| mWindowContainerController = null; |
| } |
| |
| /** Update and get all UIDs that are present on the display and have access to it. */ |
| IntArray getPresentUIDs() { |
| mDisplayAccessUIDs.clear(); |
| for (ActivityStack stack : mStacks) { |
| stack.getPresentUIDs(mDisplayAccessUIDs); |
| } |
| return mDisplayAccessUIDs; |
| } |
| |
| private boolean shouldDestroyContentOnRemove() { |
| return mDisplay.getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT; |
| } |
| |
| boolean shouldSleep() { |
| return (mStacks.isEmpty() || !mAllSleepTokens.isEmpty()) |
| && (mSupervisor.mService.mRunningVoice == null); |
| } |
| |
| /** |
| * @return the stack currently above the {@param stack}. Can be null if the {@param stack} is |
| * already top-most. |
| */ |
| ActivityStack getStackAbove(ActivityStack stack) { |
| final int stackIndex = mStacks.indexOf(stack) + 1; |
| return (stackIndex < mStacks.size()) ? mStacks.get(stackIndex) : null; |
| } |
| |
| /** |
| * Adjusts the {@param stack} behind the last visible stack in the display if necessary. |
| * Generally used in conjunction with {@link #moveStackBehindStack}. |
| */ |
| void moveStackBehindBottomMostVisibleStack(ActivityStack stack) { |
| if (stack.shouldBeVisible(null)) { |
| // Skip if the stack is already visible |
| return; |
| } |
| |
| // Move the stack to the bottom to not affect the following visibility checks |
| positionChildAtBottom(stack); |
| |
| // Find the next position where the stack should be placed |
| final int numStacks = mStacks.size(); |
| for (int stackNdx = 0; stackNdx < numStacks; stackNdx++) { |
| final ActivityStack s = mStacks.get(stackNdx); |
| if (s == stack) { |
| continue; |
| } |
| final int winMode = s.getWindowingMode(); |
| final boolean isValidWindowingMode = winMode == WINDOWING_MODE_FULLSCREEN || |
| winMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; |
| if (s.shouldBeVisible(null) && isValidWindowingMode) { |
| // Move the provided stack to behind this stack |
| positionChildAt(stack, Math.max(0, stackNdx - 1)); |
| break; |
| } |
| } |
| } |
| |
| /** |
| * Moves the {@param stack} behind the given {@param behindStack} if possible. If |
| * {@param behindStack} is not currently in the display, then then the stack is moved to the |
| * back. Generally used in conjunction with {@link #moveStackBehindBottomMostVisibleStack}. |
| */ |
| void moveStackBehindStack(ActivityStack stack, ActivityStack behindStack) { |
| if (behindStack == null || behindStack == stack) { |
| return; |
| } |
| |
| // Note that positionChildAt will first remove the given stack before inserting into the |
| // list, so we need to adjust the insertion index to account for the removed index |
| // TODO: Remove this logic when WindowContainer.positionChildAt() is updated to adjust the |
| // position internally |
| final int stackIndex = mStacks.indexOf(stack); |
| final int behindStackIndex = mStacks.indexOf(behindStack); |
| final int insertIndex = stackIndex <= behindStackIndex |
| ? behindStackIndex - 1 : behindStackIndex; |
| positionChildAt(stack, Math.max(0, insertIndex)); |
| } |
| |
| boolean isSleeping() { |
| return mSleeping; |
| } |
| |
| void setIsSleeping(boolean asleep) { |
| mSleeping = asleep; |
| } |
| |
| /** |
| * Adds a listener to be notified whenever the stack order in the display changes. Currently |
| * only used by the {@link RecentsAnimation} to determine whether to interrupt and cancel the |
| * current animation when the system state changes. |
| */ |
| void registerStackOrderChangedListener(OnStackOrderChangedListener listener) { |
| if (!mStackOrderChangedCallbacks.contains(listener)) { |
| mStackOrderChangedCallbacks.add(listener); |
| } |
| } |
| |
| /** |
| * Removes a previously registered stack order change listener. |
| */ |
| void unregisterStackOrderChangedListener(OnStackOrderChangedListener listener) { |
| mStackOrderChangedCallbacks.remove(listener); |
| } |
| |
| private void onStackOrderChanged() { |
| for (int i = mStackOrderChangedCallbacks.size() - 1; i >= 0; i--) { |
| mStackOrderChangedCallbacks.get(i).onStackOrderChanged(); |
| } |
| } |
| |
| /** |
| * See {@link DisplayWindowController#deferUpdateImeTarget()} |
| */ |
| public void deferUpdateImeTarget() { |
| mWindowContainerController.deferUpdateImeTarget(); |
| } |
| |
| /** |
| * See {@link DisplayWindowController#deferUpdateImeTarget()} |
| */ |
| public void continueUpdateImeTarget() { |
| mWindowContainerController.continueUpdateImeTarget(); |
| } |
| |
| public void dump(PrintWriter pw, String prefix) { |
| pw.println(prefix + "displayId=" + mDisplayId + " stacks=" + mStacks.size()); |
| final String myPrefix = prefix + " "; |
| if (mHomeStack != null) { |
| pw.println(myPrefix + "mHomeStack=" + mHomeStack); |
| } |
| if (mRecentsStack != null) { |
| pw.println(myPrefix + "mRecentsStack=" + mRecentsStack); |
| } |
| if (mPinnedStack != null) { |
| pw.println(myPrefix + "mPinnedStack=" + mPinnedStack); |
| } |
| if (mSplitScreenPrimaryStack != null) { |
| pw.println(myPrefix + "mSplitScreenPrimaryStack=" + mSplitScreenPrimaryStack); |
| } |
| } |
| |
| public void dumpStacks(PrintWriter pw) { |
| for (int i = mStacks.size() - 1; i >= 0; --i) { |
| pw.print(mStacks.get(i).mStackId); |
| if (i > 0) { |
| pw.print(","); |
| } |
| } |
| } |
| |
| public void writeToProto(ProtoOutputStream proto, long fieldId) { |
| final long token = proto.start(fieldId); |
| super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */); |
| proto.write(ID, mDisplayId); |
| for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { |
| final ActivityStack stack = mStacks.get(stackNdx); |
| stack.writeToProto(proto, STACKS); |
| } |
| proto.end(token); |
| } |
| |
| /** |
| * Callback for when the order of the stacks in the display changes. |
| */ |
| interface OnStackOrderChangedListener { |
| void onStackOrderChanged(); |
| } |
| } |