| /* |
| * 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_RECENTS; |
| import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; |
| import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; |
| 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.proto.ActivityDisplayProto.CONFIGURATION_CONTAINER; |
| import static com.android.server.am.proto.ActivityDisplayProto.STACKS; |
| import static com.android.server.am.proto.ActivityDisplayProto.ID; |
| |
| import android.app.ActivityManagerInternal; |
| import android.app.WindowConfiguration; |
| 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 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 { |
| 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; |
| |
| 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 */ |
| final ArrayList<ActivityStack> mStacks = 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; |
| |
| ActivityDisplay(ActivityStackSupervisor supervisor, int displayId) { |
| mSupervisor = supervisor; |
| mDisplayId = displayId; |
| final Display display = supervisor.mDisplayManager.getDisplay(displayId); |
| if (display == null) { |
| throw new IllegalStateException("Display does not exist displayId=" + displayId); |
| } |
| mDisplay = display; |
| } |
| |
| 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); |
| 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); |
| mSupervisor.mService.updateSleepIfNeededLocked(); |
| } |
| |
| void positionChildAtTop(ActivityStack stack) { |
| positionChildAt(stack, mStacks.size()); |
| } |
| |
| void positionChildAtBottom(ActivityStack stack) { |
| positionChildAt(stack, 0); |
| } |
| |
| private void positionChildAt(ActivityStack stack, int position) { |
| mStacks.remove(stack); |
| mStacks.add(getTopInsertPosition(stack, position), stack); |
| } |
| |
| 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) { |
| for (int i = mStacks.size() - 1; i >= 0; --i) { |
| final ActivityStack stack = mStacks.get(i); |
| // TODO: Should undefined windowing and activity type be compatible with standard type? |
| if (stack.isCompatible(windowingMode, activityType)) { |
| return (T) stack; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * @see #getStack(int, int) |
| * @see #createStack(int, int, boolean) |
| */ |
| <T extends ActivityStack> T getOrCreateStack(int windowingMode, int activityType, |
| boolean onTop) { |
| T stack = getStack(windowingMode, activityType); |
| if (stack != null) { |
| return stack; |
| } |
| return createStack(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 |
| * 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 (!mSupervisor.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 boolean inSplitScreenMode = hasSplitScreenStack(); |
| 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 |
| && WindowConfiguration.supportSplitScreenWindowingMode( |
| windowingMode, activityType)) { |
| windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; |
| } |
| |
| final int stackId = mSupervisor.getNextStackId(); |
| |
| final T stack = createStackUnchecked(windowingMode, activityType, stackId, 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. |
| // 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 |
| */ |
| 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.removeStackLocked(stack.mStackId); |
| } |
| } |
| } |
| |
| 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.removeStackLocked(stack.mStackId); |
| } |
| } |
| } |
| } |
| |
| /** 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) { |
| final ActivityStack stack = mStacks.get(i); |
| if (stack.getWindowingMode() == excludeWindowingMode) { |
| continue; |
| } |
| if (stack.shouldBeVisible(null /* starting */)) { |
| return stack.getActivityType(); |
| } |
| } |
| return ACTIVITY_TYPE_UNDEFINED; |
| } |
| |
| ActivityStack getSplitScreenStack() { |
| return getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED); |
| } |
| |
| boolean hasSplitScreenStack() { |
| return getSplitScreenStack() != null; |
| } |
| |
| PinnedActivityStack getPinnedStack() { |
| return getStack(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED); |
| } |
| |
| boolean hasPinnedStack() { |
| return getPinnedStack() != null; |
| } |
| |
| @Override |
| public String toString() { |
| return "ActivityDisplay={" + mDisplayId + " numStacks=" + mStacks.size() + "}"; |
| } |
| |
| @Override |
| protected int getChildCount() { |
| return mStacks.size(); |
| } |
| |
| @Override |
| protected ConfigurationContainer 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; |
| } |
| |
| /** 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; |
| } |
| |
| boolean shouldDestroyContentOnRemove() { |
| return mDisplay.getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT; |
| } |
| |
| boolean shouldSleep() { |
| return (mStacks.isEmpty() || !mAllSleepTokens.isEmpty()) |
| && (mSupervisor.mService.mRunningVoice == null); |
| } |
| |
| boolean isSleeping() { |
| return mSleeping; |
| } |
| |
| void setIsSleeping(boolean asleep) { |
| mSleeping = asleep; |
| } |
| |
| public void writeToProto(ProtoOutputStream proto, long fieldId) { |
| final long token = proto.start(fieldId); |
| super.writeToProto(proto, CONFIGURATION_CONTAINER); |
| 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); |
| } |
| } |