| /* |
| * 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.wm; |
| |
| import static android.app.ActivityTaskManager.INVALID_STACK_ID; |
| 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.ROTATION_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.os.Build.VERSION_CODES.N; |
| 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.ActivityDisplayProto.CONFIGURATION_CONTAINER; |
| import static com.android.server.am.ActivityDisplayProto.FOCUSED_STACK_ID; |
| import static com.android.server.am.ActivityDisplayProto.ID; |
| import static com.android.server.am.ActivityDisplayProto.RESUMED_ACTIVITY; |
| import static com.android.server.am.ActivityDisplayProto.SINGLE_TASK_INSTANCE; |
| import static com.android.server.am.ActivityDisplayProto.STACKS; |
| import static com.android.server.wm.ActivityStack.ActivityState.RESUMED; |
| import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE; |
| import static com.android.server.wm.ActivityStackSupervisor.TAG_TASKS; |
| import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STACK; |
| import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STATES; |
| import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS; |
| import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_STACK; |
| import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; |
| import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; |
| import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT; |
| import static com.android.server.wm.RootActivityContainer.FindTaskResult; |
| import static com.android.server.wm.RootActivityContainer.TAG_STATES; |
| import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; |
| import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; |
| |
| import android.annotation.Nullable; |
| import android.app.ActivityManager; |
| import android.app.ActivityManagerInternal; |
| import android.app.ActivityOptions; |
| import android.app.WindowConfiguration; |
| import android.content.pm.ActivityInfo; |
| import android.content.res.Configuration; |
| import android.graphics.Point; |
| import android.os.IBinder; |
| import android.os.Message; |
| import android.os.UserHandle; |
| import android.provider.Settings; |
| 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.internal.util.function.pooled.PooledLambda; |
| import com.android.server.am.EventLogTags; |
| import com.android.server.protolog.common.ProtoLog; |
| |
| 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> { |
| private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityDisplay" : TAG_ATM; |
| 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 ActivityTaskManagerService mService; |
| private RootActivityContainer mRootActivityContainer; |
| // TODO: Remove once unification is complete. |
| DisplayContent mDisplayContent; |
| /** 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<ActivityTaskManagerInternal.SleepToken> mAllSleepTokens = new ArrayList<>(); |
| /** The token acquired by ActivityStackSupervisor to put stacks on the display to sleep */ |
| ActivityTaskManagerInternal.SleepToken mOffToken; |
| |
| private boolean mSleeping; |
| |
| /** |
| * The display is removed from the system and we are just waiting for all activities on it to be |
| * finished before removing this object. |
| */ |
| private boolean mRemoved; |
| |
| /** The display can only contain one task. */ |
| private boolean mSingleTaskInstance; |
| |
| /** |
| * Non-null if the last size compatibility mode activity is using non-native screen |
| * configuration. The activity is not able to put in multi-window mode, so it exists only one |
| * per display. |
| */ |
| private ActivityRecord mLastCompatModeActivity; |
| |
| /** |
| * A focusable stack that is purposely to be positioned at the top. Although the stack may not |
| * have the topmost index, it is used as a preferred candidate to prevent being unable to resume |
| * target stack properly when there are other focusable always-on-top stacks. |
| */ |
| private ActivityStack mPreferredTopFocusableStack; |
| |
| /** |
| * If this is the same as {@link #getFocusedStack} then the activity on the top of the focused |
| * stack has been resumed. If stacks are changing position this will hold the old stack until |
| * the new stack becomes resumed after which it will be set to current focused stack. |
| */ |
| private ActivityStack mLastFocusedStack; |
| |
| // 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(); |
| |
| // Used in updating override configurations |
| private final Configuration mTempConfig = new Configuration(); |
| |
| private final FindTaskResult mTmpFindTaskResult = new FindTaskResult(); |
| |
| ActivityDisplay(RootActivityContainer root, Display display) { |
| mRootActivityContainer = root; |
| mService = root.mService; |
| mDisplayId = display.getDisplayId(); |
| mDisplay = display; |
| mDisplayContent = mService.mWindowManager.mRoot.createDisplayContent(mDisplay, this); |
| mDisplayContent.reconfigureDisplayLocked(); |
| onRequestedOverrideConfigurationChanged( |
| mDisplayContent.getRequestedOverrideConfiguration()); |
| } |
| |
| void onDisplayChanged() { |
| // The window policy is responsible for stopping activities on the default display. |
| final int displayId = mDisplay.getDisplayId(); |
| if (displayId != DEFAULT_DISPLAY) { |
| final int displayState = mDisplay.getState(); |
| if (displayState == Display.STATE_OFF && mOffToken == null) { |
| mOffToken = mService.acquireSleepToken("Display-off", displayId); |
| } else if (displayState == Display.STATE_ON && mOffToken != null) { |
| mOffToken.release(); |
| mOffToken = null; |
| } |
| } |
| |
| mDisplay.getRealSize(mTmpDisplaySize); |
| setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y); |
| if (mDisplayContent != null) { |
| mDisplayContent.updateDisplayInfo(); |
| mService.mWindowManager.requestTraversal(); |
| } |
| } |
| |
| 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); |
| mService.updateSleepIfNeededLocked(); |
| } |
| |
| void removeChild(ActivityStack stack) { |
| if (DEBUG_STACK) Slog.v(TAG_STACK, "removeChild: detaching " + stack |
| + " from displayId=" + mDisplayId); |
| mStacks.remove(stack); |
| if (mPreferredTopFocusableStack == stack) { |
| mPreferredTopFocusableStack = null; |
| } |
| removeStackReferenceIfNeeded(stack); |
| releaseSelfIfNeeded(); |
| mService.updateSleepIfNeededLocked(); |
| onStackOrderChanged(stack); |
| } |
| |
| void positionChildAtTop(ActivityStack stack, boolean includingParents) { |
| positionChildAtTop(stack, includingParents, null /* updateLastFocusedStackReason */); |
| } |
| |
| void positionChildAtTop(ActivityStack stack, boolean includingParents, |
| String updateLastFocusedStackReason) { |
| positionChildAt(stack, mStacks.size(), includingParents, updateLastFocusedStackReason); |
| } |
| |
| void positionChildAtBottom(ActivityStack stack) { |
| positionChildAtBottom(stack, null /* updateLastFocusedStackReason */); |
| } |
| |
| void positionChildAtBottom(ActivityStack stack, String updateLastFocusedStackReason) { |
| positionChildAt(stack, 0, false /* includingParents */, updateLastFocusedStackReason); |
| } |
| |
| private void positionChildAt(ActivityStack stack, int position) { |
| positionChildAt(stack, position, false /* includingParents */, |
| null /* updateLastFocusedStackReason */); |
| } |
| |
| private void positionChildAt(ActivityStack stack, int position, boolean includingParents, |
| String updateLastFocusedStackReason) { |
| // TODO: Keep in sync with WindowContainer.positionChildAt(), once we change that to adjust |
| // the position internally, also update the logic here |
| final ActivityStack prevFocusedStack = updateLastFocusedStackReason != null |
| ? getFocusedStack() : null; |
| final boolean wasContained = mStacks.remove(stack); |
| if (mSingleTaskInstance && getChildCount() > 0) { |
| throw new IllegalStateException( |
| "positionChildAt: Can only have one child on display=" + this); |
| } |
| final int insertPosition = getTopInsertPosition(stack, position); |
| mStacks.add(insertPosition, stack); |
| |
| // The insert position may be adjusted to non-top when there is always-on-top stack. Since |
| // the original position is preferred to be top, the stack should have higher priority when |
| // we are looking for top focusable stack. The condition {@code wasContained} restricts the |
| // preferred stack is set only when moving an existing stack to top instead of adding a new |
| // stack that may be too early (e.g. in the middle of launching or reparenting). |
| if (wasContained && position >= mStacks.size() - 1 && stack.isFocusableAndVisible()) { |
| mPreferredTopFocusableStack = stack; |
| } else if (mPreferredTopFocusableStack == stack) { |
| mPreferredTopFocusableStack = null; |
| } |
| |
| if (updateLastFocusedStackReason != null) { |
| final ActivityStack currentFocusedStack = getFocusedStack(); |
| if (currentFocusedStack != prevFocusedStack) { |
| mLastFocusedStack = prevFocusedStack; |
| EventLogTags.writeAmFocusedStack(mRootActivityContainer.mCurrentUser, mDisplayId, |
| currentFocusedStack == null ? -1 : currentFocusedStack.getStackId(), |
| mLastFocusedStack == null ? -1 : mLastFocusedStack.getStackId(), |
| updateLastFocusedStackReason); |
| } |
| } |
| |
| // Since positionChildAt() is called during the creation process of pinned stacks, |
| // ActivityStack#getStack() can be null. |
| if (stack.getTaskStack() != null && mDisplayContent != null) { |
| mDisplayContent.positionStackAt(insertPosition, |
| stack.getTaskStack(), includingParents); |
| } |
| if (!wasContained) { |
| stack.setParent(this); |
| } |
| onStackOrderChanged(stack); |
| } |
| |
| private int getTopInsertPosition(ActivityStack stack, int candidatePosition) { |
| int position = mStacks.size(); |
| if (stack.inPinnedWindowingMode()) { |
| // Stack in pinned windowing mode is z-ordered on-top of all other stacks so okay to |
| // just return the candidate position. |
| return Math.min(position, candidatePosition); |
| } |
| while (position > 0) { |
| final ActivityStack targetStack = mStacks.get(position - 1); |
| if (!targetStack.isAlwaysOnTop()) { |
| // We reached a stack that isn't always-on-top. |
| break; |
| } |
| if (stack.isAlwaysOnTop() && !targetStack.inPinnedWindowingMode()) { |
| // Always on-top non-pinned windowing mode stacks can go anywhere below pinned stack. |
| break; |
| } |
| 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; |
| } |
| |
| 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) { |
| // First preference is the windowing mode in the activity options if set. |
| int windowingMode = (options != null) |
| ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED; |
| // Validate that our desired windowingMode will work under the current conditions. |
| // UNDEFINED windowing mode is a valid result and means that the new stack will inherit |
| // it's display's windowing mode. |
| windowingMode = validateWindowingMode(windowingMode, r, candidateTask, activityType); |
| return getOrCreateStack(windowingMode, activityType, onTop); |
| } |
| |
| @VisibleForTesting |
| 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 |
| * inherit it's parent's windowing mode. |
| * @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 (mSingleTaskInstance && getChildCount() > 0) { |
| // Create stack on default display instead since this display can only contain 1 stack. |
| // TODO: Kinda a hack, but better that having the decision at each call point. Hoping |
| // this goes away once ActivityView is no longer using virtual displays. |
| return mRootActivityContainer.getDefaultDisplay().createStack( |
| windowingMode, activityType, 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."); |
| } |
| } |
| |
| if (!isWindowingModeSupported(windowingMode, mService.mSupportsMultiWindow, |
| mService.mSupportsSplitScreenMultiWindow, |
| mService.mSupportsFreeformWindowManagement, mService.mSupportsPictureInPicture, |
| activityType)) { |
| throw new IllegalArgumentException("Can't create stack for unsupported windowingMode=" |
| + windowingMode); |
| } |
| |
| 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 && activityType != ACTIVITY_TYPE_STANDARD) { |
| throw new IllegalArgumentException("Stack with windowing mode cannot with non standard " |
| + "activity type."); |
| } |
| return (T) new ActivityStack(this, stackId, |
| mRootActivityContainer.mStackSupervisor, windowingMode, activityType, onTop); |
| } |
| |
| /** |
| * Get the preferred focusable stack in priority. If the preferred stack does not exist, find a |
| * focusable and visible stack from the top of stacks in this display. |
| */ |
| ActivityStack getFocusedStack() { |
| if (mPreferredTopFocusableStack != null) { |
| return mPreferredTopFocusableStack; |
| } |
| |
| for (int i = mStacks.size() - 1; i >= 0; --i) { |
| final ActivityStack stack = mStacks.get(i); |
| if (stack.isFocusableAndVisible()) { |
| return stack; |
| } |
| } |
| |
| return null; |
| } |
| |
| ActivityStack getNextFocusableStack(ActivityStack currentFocus, boolean ignoreCurrent) { |
| final int currentWindowingMode = currentFocus != null |
| ? currentFocus.getWindowingMode() : WINDOWING_MODE_UNDEFINED; |
| |
| ActivityStack candidate = null; |
| for (int i = mStacks.size() - 1; i >= 0; --i) { |
| final ActivityStack stack = mStacks.get(i); |
| if (ignoreCurrent && stack == currentFocus) { |
| continue; |
| } |
| if (!stack.isFocusableAndVisible()) { |
| continue; |
| } |
| |
| if (currentWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY |
| && candidate == null && stack.inSplitScreenPrimaryWindowingMode()) { |
| // If the currently focused stack is in split-screen secondary we save off the |
| // top primary split-screen stack as a candidate for focus because we might |
| // prefer focus to move to an other stack to avoid primary split-screen stack |
| // overlapping with a fullscreen stack when a fullscreen stack is higher in z |
| // than the next split-screen stack. Assistant stack, I am looking at you... |
| // We only move the focus to the primary-split screen stack if there isn't a |
| // better alternative. |
| candidate = stack; |
| continue; |
| } |
| if (candidate != null && stack.inSplitScreenSecondaryWindowingMode()) { |
| // Use the candidate stack since we are now at the secondary split-screen. |
| return candidate; |
| } |
| return stack; |
| } |
| return candidate; |
| } |
| |
| ActivityRecord getResumedActivity() { |
| final ActivityStack focusedStack = getFocusedStack(); |
| if (focusedStack == null) { |
| return null; |
| } |
| // TODO(b/111541062): Move this into ActivityStack#getResumedActivity() |
| // Check if the focused stack has the resumed activity |
| ActivityRecord resumedActivity = focusedStack.getResumedActivity(); |
| if (resumedActivity == null || resumedActivity.app == null) { |
| // If there is no registered resumed activity in the stack or it is not running - |
| // try to use previously resumed one. |
| resumedActivity = focusedStack.mPausingActivity; |
| if (resumedActivity == null || resumedActivity.app == null) { |
| // If previously resumed activity doesn't work either - find the topmost running |
| // activity that can be focused. |
| resumedActivity = focusedStack.topRunningActivityLocked(true /* focusableOnly */); |
| } |
| } |
| return resumedActivity; |
| } |
| |
| ActivityStack getLastFocusedStack() { |
| return mLastFocusedStack; |
| } |
| |
| boolean allResumedActivitiesComplete() { |
| for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { |
| final ActivityRecord r = mStacks.get(stackNdx).getResumedActivity(); |
| if (r != null && !r.isState(RESUMED)) { |
| return false; |
| } |
| } |
| final ActivityStack currentFocusedStack = getFocusedStack(); |
| if (DEBUG_STACK) { |
| Slog.d(TAG_STACK, "allResumedActivitiesComplete: mLastFocusedStack changing from=" |
| + mLastFocusedStack + " to=" + currentFocusedStack); |
| } |
| mLastFocusedStack = currentFocusedStack; |
| return true; |
| } |
| |
| /** |
| * Pause all activities in either all of the stacks or just the back stacks. This is done before |
| * resuming a new activity and to make sure that previously active activities are |
| * paused in stacks that are no longer visible or in pinned windowing mode. This does not |
| * pause activities in visible stacks, so if an activity is launched within the same stack/task, |
| * then we should explicitly pause that stack's top activity. |
| * @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving(). |
| * @param resuming The resuming activity. |
| * @return {@code true} if any activity was paused as a result of this call. |
| */ |
| boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming) { |
| boolean someActivityPaused = false; |
| for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { |
| final ActivityStack stack = mStacks.get(stackNdx); |
| final ActivityRecord resumedActivity = stack.getResumedActivity(); |
| if (resumedActivity != null |
| && (stack.getVisibility(resuming) != STACK_VISIBILITY_VISIBLE |
| || !stack.isFocusable())) { |
| if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack + |
| " mResumedActivity=" + resumedActivity); |
| someActivityPaused |= stack.startPausingLocked(userLeaving, false /* uiSleeping*/, |
| resuming); |
| } |
| } |
| return someActivityPaused; |
| } |
| |
| /** |
| * Find task for putting the Activity in. |
| */ |
| void findTaskLocked(final ActivityRecord r, final boolean isPreferredDisplay, |
| FindTaskResult result) { |
| mTmpFindTaskResult.clear(); |
| for (int stackNdx = getChildCount() - 1; stackNdx >= 0; --stackNdx) { |
| final ActivityStack stack = getChildAt(stackNdx); |
| if (!r.hasCompatibleActivityType(stack)) { |
| if (DEBUG_TASKS) { |
| Slog.d(TAG_TASKS, "Skipping stack: (mismatch activity/stack) " + stack); |
| } |
| continue; |
| } |
| |
| stack.findTaskLocked(r, mTmpFindTaskResult); |
| // It is possible to have tasks in multiple stacks with the same root affinity, so |
| // we should keep looking after finding an affinity match to see if there is a |
| // better match in another stack. Also, task affinity isn't a good enough reason |
| // to target a display which isn't the source of the intent, so skip any affinity |
| // matches not on the specified display. |
| if (mTmpFindTaskResult.mRecord != null) { |
| if (mTmpFindTaskResult.mIdealMatch) { |
| result.setTo(mTmpFindTaskResult); |
| return; |
| } else if (isPreferredDisplay) { |
| // Note: since the traversing through the stacks is top down, the floating |
| // tasks should always have lower priority than any affinity-matching tasks |
| // in the fullscreen stacks |
| result.setTo(mTmpFindTaskResult); |
| } |
| } |
| } |
| } |
| |
| /** |
| * 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; |
| } |
| |
| // Collect the stacks that are necessary to be removed instead of performing the removal |
| // by looping mStacks, so that we don't miss any stacks after the stack size changed or |
| // stacks reordered. |
| final ArrayList<ActivityStack> stacks = new ArrayList<>(); |
| 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; |
| } |
| stacks.add(stack); |
| } |
| } |
| |
| for (int i = stacks.size() - 1; i >= 0; --i) { |
| mRootActivityContainer.mStackSupervisor.removeStack(stacks.get(i)); |
| } |
| } |
| |
| void removeStacksWithActivityTypes(int... activityTypes) { |
| if (activityTypes == null || activityTypes.length == 0) { |
| return; |
| } |
| |
| // Collect the stacks that are necessary to be removed instead of performing the removal |
| // by looping mStacks, so that we don't miss any stacks after the stack size changed or |
| // stacks reordered. |
| final ArrayList<ActivityStack> stacks = new ArrayList<>(); |
| 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) { |
| stacks.add(stack); |
| } |
| } |
| } |
| |
| for (int i = stacks.size() - 1; i >= 0; --i) { |
| mRootActivityContainer.mStackSupervisor.removeStack(stacks.get(i)); |
| } |
| } |
| |
| 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() { |
| mService.deferWindowLayout(); |
| 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_UNDEFINED, false /* animate */, |
| false /* showRecents */, false /* enteringSplitScreenMode */, |
| true /* deferEnsuringVisibility */, false /* creating */); |
| } |
| } 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"); |
| } |
| mService.continueWindowLayout(); |
| } |
| } |
| |
| private void onSplitScreenModeActivated() { |
| mService.deferWindowLayout(); |
| 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 */, |
| false /* creating */); |
| } |
| } finally { |
| mService.continueWindowLayout(); |
| } |
| } |
| |
| /** |
| * 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; |
| } |
| |
| final int displayWindowingMode = getWindowingMode(); |
| if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY |
| || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { |
| return supportsSplitScreen |
| && WindowConfiguration.supportSplitScreenWindowingMode(activityType) |
| // Freeform windows and split-screen windows don't mix well, so prevent |
| // split windowing modes on freeform displays. |
| && displayWindowingMode != WINDOWING_MODE_FREEFORM; |
| } |
| |
| if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) { |
| return false; |
| } |
| |
| if (!supportsPip && windowingMode == WINDOWING_MODE_PINNED) { |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Resolves the windowing mode that an {@link ActivityRecord} would be in if started on this |
| * display with the provided parameters. |
| * |
| * @param r The ActivityRecord in question. |
| * @param options Options to start with. |
| * @param task The task within-which the activity would start. |
| * @param activityType The type of activity to start. |
| * @return The resolved (not UNDEFINED) windowing-mode that the activity would be in. |
| */ |
| 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(); |
| } |
| } |
| windowingMode = validateWindowingMode(windowingMode, r, task, activityType); |
| return windowingMode != WINDOWING_MODE_UNDEFINED |
| ? windowingMode : WINDOWING_MODE_FULLSCREEN; |
| } |
| |
| /** |
| * Check that the requested windowing-mode is appropriate for the specified task and/or activity |
| * on this display. |
| * |
| * @param windowingMode The windowing-mode to validate. |
| * @param r The {@link ActivityRecord} to check against. |
| * @param task The {@link TaskRecord} to check against. |
| * @param activityType An activity type. |
| * @return The provided windowingMode or the closest valid mode which is appropriate. |
| */ |
| int validateWindowingMode(int windowingMode, @Nullable ActivityRecord r, |
| @Nullable TaskRecord task, int activityType) { |
| // 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(); |
| } |
| } |
| |
| final boolean inSplitScreenMode = hasSplitScreenPrimaryStack(); |
| if (!inSplitScreenMode |
| && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) { |
| // Switch to the display's windowing mode if we are not in split-screen mode and we are |
| // trying to launch in split-screen secondary. |
| windowingMode = WINDOWING_MODE_UNDEFINED; |
| } else if (inSplitScreenMode && (windowingMode == WINDOWING_MODE_FULLSCREEN |
| || windowingMode == WINDOWING_MODE_UNDEFINED) |
| && supportsSplitScreen) { |
| windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; |
| } |
| |
| if (windowingMode != WINDOWING_MODE_UNDEFINED |
| && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen, |
| supportsFreeform, supportsPip, activityType)) { |
| return windowingMode; |
| } |
| return WINDOWING_MODE_UNDEFINED; |
| } |
| |
| /** |
| * Get the topmost stack on the display. It may be different from focused stack, because |
| * some stacks are not focusable (e.g. PiP). |
| */ |
| 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; |
| } |
| |
| ActivityRecord topRunningActivity() { |
| return topRunningActivity(false /* considerKeyguardState */); |
| } |
| |
| /** |
| * Returns the top running activity in the focused stack. In the case the focused stack has no |
| * such activity, the next focusable stack on this display is returned. |
| * |
| * @param considerKeyguardState Indicates whether the locked state should be considered. if |
| * {@code true} and the keyguard is locked, only activities that |
| * can be shown on top of the keyguard will be considered. |
| * @return The top running activity. {@code null} if none is available. |
| */ |
| ActivityRecord topRunningActivity(boolean considerKeyguardState) { |
| ActivityRecord topRunning = null; |
| final ActivityStack focusedStack = getFocusedStack(); |
| if (focusedStack != null) { |
| topRunning = focusedStack.topRunningActivityLocked(); |
| } |
| |
| // Look in other focusable stacks. |
| if (topRunning == null) { |
| for (int i = mStacks.size() - 1; i >= 0; --i) { |
| final ActivityStack stack = mStacks.get(i); |
| // Only consider focusable stacks other than the current focused one. |
| if (stack == focusedStack || !stack.isFocusable()) { |
| continue; |
| } |
| topRunning = stack.topRunningActivityLocked(); |
| if (topRunning != null) { |
| break; |
| } |
| } |
| } |
| |
| // This activity can be considered the top running activity if we are not considering |
| // the locked state, the keyguard isn't locked, or we can show when locked. |
| if (topRunning != null && considerKeyguardState |
| && mRootActivityContainer.mStackSupervisor.getKeyguardController().isKeyguardLocked() |
| && !topRunning.canShowWhenLocked()) { |
| return null; |
| } |
| |
| return topRunning; |
| } |
| |
| int getIndexOf(ActivityStack stack) { |
| return mStacks.indexOf(stack); |
| } |
| |
| boolean updateDisplayOverrideConfigurationLocked() { |
| Configuration values = new Configuration(); |
| mDisplayContent.computeScreenConfiguration(values); |
| |
| mService.mH.sendMessage(PooledLambda.obtainMessage( |
| ActivityManagerInternal::updateOomLevelsForDisplay, mService.mAmInternal, |
| mDisplayId)); |
| |
| Settings.System.clearConfiguration(values); |
| updateDisplayOverrideConfigurationLocked(values, null /* starting */, |
| false /* deferResume */, mService.mTmpUpdateConfigurationResult); |
| return mService.mTmpUpdateConfigurationResult.changes != 0; |
| } |
| |
| /** |
| * Updates override configuration specific for the selected display. If no config is provided, |
| * new one will be computed in WM based on current display info. |
| */ |
| boolean updateDisplayOverrideConfigurationLocked(Configuration values, |
| ActivityRecord starting, boolean deferResume, |
| ActivityTaskManagerService.UpdateConfigurationResult result) { |
| |
| int changes = 0; |
| boolean kept = true; |
| |
| mService.deferWindowLayout(); |
| try { |
| if (values != null) { |
| if (mDisplayId == DEFAULT_DISPLAY) { |
| // Override configuration of the default display duplicates global config, so |
| // we're calling global config update instead for default display. It will also |
| // apply the correct override config. |
| changes = mService.updateGlobalConfigurationLocked(values, |
| false /* initLocale */, false /* persistent */, |
| UserHandle.USER_NULL /* userId */, deferResume); |
| } else { |
| changes = performDisplayOverrideConfigUpdate(values, deferResume); |
| } |
| } |
| |
| kept = mService.ensureConfigAndVisibilityAfterUpdate(starting, changes); |
| } finally { |
| mService.continueWindowLayout(); |
| } |
| |
| if (result != null) { |
| result.changes = changes; |
| result.activityRelaunched = !kept; |
| } |
| return kept; |
| } |
| |
| int performDisplayOverrideConfigUpdate(Configuration values, boolean deferResume) { |
| mTempConfig.setTo(getRequestedOverrideConfiguration()); |
| final int changes = mTempConfig.updateFrom(values); |
| if (changes != 0) { |
| Slog.i(TAG, "Override config changes=" + Integer.toHexString(changes) + " " |
| + mTempConfig + " for displayId=" + mDisplayId); |
| onRequestedOverrideConfigurationChanged(mTempConfig); |
| |
| final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0; |
| if (isDensityChange && mDisplayId == DEFAULT_DISPLAY) { |
| mService.mAppWarnings.onDensityChanged(); |
| |
| // Post message to start process to avoid possible deadlock of calling into AMS with |
| // the ATMS lock held. |
| final Message msg = PooledLambda.obtainMessage( |
| ActivityManagerInternal::killAllBackgroundProcessesExcept, |
| mService.mAmInternal, N, |
| ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND); |
| mService.mH.sendMessage(msg); |
| } |
| } |
| return changes; |
| } |
| |
| @Override |
| public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) { |
| final int currRotation = |
| getRequestedOverrideConfiguration().windowConfiguration.getRotation(); |
| if (currRotation != ROTATION_UNDEFINED |
| && currRotation != overrideConfiguration.windowConfiguration.getRotation() |
| && mDisplayContent != null) { |
| mDisplayContent.applyRotationLocked(currRotation, |
| overrideConfiguration.windowConfiguration.getRotation()); |
| } |
| super.onRequestedOverrideConfigurationChanged(overrideConfiguration); |
| if (mDisplayContent != null) { |
| mService.mWindowManager.setNewDisplayOverrideConfiguration( |
| overrideConfiguration, mDisplayContent); |
| } |
| mService.addWindowLayoutReasons( |
| ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED); |
| } |
| |
| @Override |
| public void onConfigurationChanged(Configuration newParentConfig) { |
| // update resources before cascade so that docked/pinned stacks use the correct info |
| if (mDisplayContent != null) { |
| mDisplayContent.preOnConfigurationChanged(); |
| } |
| super.onConfigurationChanged(newParentConfig); |
| } |
| |
| 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; |
| } |
| |
| /** Checks whether the given activity is in size compatibility mode and notifies the change. */ |
| void handleActivitySizeCompatModeIfNeeded(ActivityRecord r) { |
| if (!r.isState(RESUMED) || r.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) { |
| // The callback is only interested in the foreground changes of fullscreen activity. |
| return; |
| } |
| if (!r.inSizeCompatMode()) { |
| if (mLastCompatModeActivity != null) { |
| mService.getTaskChangeNotificationController() |
| .notifySizeCompatModeActivityChanged(mDisplayId, null /* activityToken */); |
| } |
| mLastCompatModeActivity = null; |
| return; |
| } |
| if (mLastCompatModeActivity == r) { |
| return; |
| } |
| mLastCompatModeActivity = r; |
| mService.getTaskChangeNotificationController() |
| .notifySizeCompatModeActivityChanged(mDisplayId, r.appToken); |
| } |
| |
| ActivityStack getSplitScreenPrimaryStack() { |
| return mSplitScreenPrimaryStack; |
| } |
| |
| boolean hasSplitScreenPrimaryStack() { |
| return mSplitScreenPrimaryStack != null; |
| } |
| |
| ActivityStack getPinnedStack() { |
| return 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 mRootActivityContainer; |
| } |
| |
| boolean isPrivate() { |
| return (mDisplay.getFlags() & FLAG_PRIVATE) != 0; |
| } |
| |
| boolean isUidPresent(int uid) { |
| for (ActivityStack stack : mStacks) { |
| if (stack.isUidPresent(uid)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * @see #mRemoved |
| */ |
| boolean isRemoved() { |
| return mRemoved; |
| } |
| |
| void remove() { |
| final boolean destroyContentOnRemoval = shouldDestroyContentOnRemove(); |
| ActivityStack lastReparentedStack = null; |
| mPreferredTopFocusableStack = null; |
| |
| // Stacks could be reparented from the removed display to other display. While |
| // reparenting the last stack of the removed display, the remove display is ready to be |
| // released (no more ActivityStack). But, we cannot release it at that moment or the |
| // related WindowContainer will also be removed. So, we set display as removed after |
| // reparenting stack finished. |
| final ActivityDisplay toDisplay = mRootActivityContainer.getDefaultDisplay(); |
| mRootActivityContainer.mStackSupervisor.beginDeferResume(); |
| try { |
| int numStacks = mStacks.size(); |
| // Keep the order from bottom to top. |
| for (int stackNdx = 0; stackNdx < numStacks; stackNdx++) { |
| final ActivityStack stack = mStacks.get(stackNdx); |
| // Always finish non-standard type stacks. |
| if (destroyContentOnRemoval || !stack.isActivityTypeStandardOrUndefined()) { |
| stack.finishAllActivitiesImmediately(); |
| } else { |
| // If default display is in split-window mode, set windowing mode of the stack |
| // to split-screen secondary. Otherwise, set the windowing mode to undefined by |
| // default to let stack inherited the windowing mode from the new display. |
| final int windowingMode = toDisplay.hasSplitScreenPrimaryStack() |
| ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY |
| : WINDOWING_MODE_UNDEFINED; |
| stack.reparent(toDisplay, true /* onTop */, true /* displayRemoved */); |
| stack.setWindowingMode(windowingMode); |
| lastReparentedStack = stack; |
| } |
| // Stacks may be removed from this display. Ensure each stack will be processed and |
| // the loop will end. |
| stackNdx -= numStacks - mStacks.size(); |
| numStacks = mStacks.size(); |
| } |
| } finally { |
| mRootActivityContainer.mStackSupervisor.endDeferResume(); |
| } |
| mRemoved = true; |
| |
| // Only update focus/visibility for the last one because there may be many stacks are |
| // reparented and the intermediate states are unnecessary. |
| if (lastReparentedStack != null) { |
| lastReparentedStack.postReparent(); |
| } |
| releaseSelfIfNeeded(); |
| |
| if (!mAllSleepTokens.isEmpty()) { |
| mRootActivityContainer.mSleepTokens.removeAll(mAllSleepTokens); |
| mAllSleepTokens.clear(); |
| mService.updateSleepIfNeededLocked(); |
| } |
| } |
| |
| private void releaseSelfIfNeeded() { |
| if (!mRemoved || mDisplayContent == null) { |
| return; |
| } |
| |
| final ActivityStack stack = mStacks.size() == 1 ? mStacks.get(0) : null; |
| if (stack != null && stack.isActivityTypeHome() && stack.getAllTasks().isEmpty()) { |
| // Release this display if an empty home stack is the only thing left. |
| // Since it is the last stack, this display will be released along with the stack |
| // removal. |
| stack.remove(); |
| } else if (mStacks.isEmpty()) { |
| mDisplayContent.removeIfPossible(); |
| mDisplayContent = null; |
| mRootActivityContainer.removeChild(this); |
| mRootActivityContainer.mStackSupervisor |
| .getKeyguardController().onDisplayRemoved(mDisplayId); |
| } |
| } |
| |
| /** 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; |
| } |
| |
| /** |
| * Checks if system decorations should be shown on this display. |
| * |
| * @see Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS |
| */ |
| boolean supportsSystemDecorations() { |
| return mDisplayContent.supportsSystemDecorations(); |
| } |
| |
| @VisibleForTesting |
| boolean shouldDestroyContentOnRemove() { |
| return mDisplay.getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT; |
| } |
| |
| boolean shouldSleep() { |
| return (mStacks.isEmpty() || !mAllSleepTokens.isEmpty()) |
| && (mService.mRunningVoice == null); |
| } |
| |
| void setFocusedApp(ActivityRecord r, boolean moveFocusNow) { |
| if (mDisplayContent == null) { |
| return; |
| } |
| final ActivityRecord newFocus; |
| final IBinder token = r.appToken; |
| if (token == null) { |
| ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Clearing focused app, displayId=%d", |
| mDisplayId); |
| newFocus = null; |
| } else { |
| newFocus = mService.mWindowManager.mRoot.getActivityRecord(token); |
| if (newFocus == null) { |
| Slog.w(TAG_WM, "Attempted to set focus to non-existing app token: " + token |
| + ", displayId=" + mDisplayId); |
| } |
| ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, |
| "Set focused app to: %s moveFocusNow=%b displayId=%d", newFocus, |
| moveFocusNow, mDisplayId); |
| } |
| |
| final boolean changed = mDisplayContent.setFocusedApp(newFocus); |
| if (moveFocusNow && changed) { |
| mService.mWindowManager.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, |
| true /*updateInputWindows*/); |
| } |
| } |
| |
| /** |
| * @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)); |
| } |
| |
| void ensureActivitiesVisible(ActivityRecord starting, int configChanges, |
| boolean preserveWindows, boolean notifyClients) { |
| for (int stackNdx = getChildCount() - 1; stackNdx >= 0; --stackNdx) { |
| final ActivityStack stack = getChildAt(stackNdx); |
| stack.ensureActivitiesVisibleLocked(starting, configChanges, preserveWindows, |
| notifyClients); |
| } |
| } |
| |
| void moveHomeStackToFront(String reason) { |
| if (mHomeStack != null) { |
| mHomeStack.moveToFront(reason); |
| } |
| } |
| |
| /** |
| * Moves the focusable home activity to top. If there is no such activity, the home stack will |
| * still move to top. |
| */ |
| void moveHomeActivityToTop(String reason) { |
| final ActivityRecord top = getHomeActivity(); |
| if (top == null) { |
| moveHomeStackToFront(reason); |
| return; |
| } |
| top.moveFocusableActivityToTop(reason); |
| } |
| |
| @Nullable |
| ActivityStack getHomeStack() { |
| return mHomeStack; |
| } |
| |
| @Nullable |
| ActivityRecord getHomeActivity() { |
| return getHomeActivityForUser(mRootActivityContainer.mCurrentUser); |
| } |
| |
| @Nullable |
| ActivityRecord getHomeActivityForUser(int userId) { |
| if (mHomeStack == null) { |
| return null; |
| } |
| |
| final ArrayList<TaskRecord> tasks = mHomeStack.getAllTasks(); |
| for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { |
| final TaskRecord task = tasks.get(taskNdx); |
| if (!task.isActivityTypeHome()) { |
| continue; |
| } |
| |
| for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) { |
| final ActivityRecord r = task.getChildAt(activityNdx); |
| if (r.isActivityTypeHome() |
| && ((userId == UserHandle.USER_ALL) || (r.mUserId == userId))) { |
| return r; |
| } |
| } |
| } |
| return null; |
| } |
| |
| 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); |
| } |
| |
| /** |
| * Notifies of a stack order change |
| * @param stack The stack which triggered the order change |
| */ |
| private void onStackOrderChanged(ActivityStack stack) { |
| for (int i = mStackOrderChangedCallbacks.size() - 1; i >= 0; i--) { |
| mStackOrderChangedCallbacks.get(i).onStackOrderChanged(stack); |
| } |
| } |
| |
| /** |
| * See {@link DisplayContent#deferUpdateImeTarget()} |
| */ |
| public void deferUpdateImeTarget() { |
| if (mDisplayContent != null) { |
| mDisplayContent.deferUpdateImeTarget(); |
| } |
| } |
| |
| /** |
| * See {@link DisplayContent#deferUpdateImeTarget()} |
| */ |
| public void continueUpdateImeTarget() { |
| if (mDisplayContent != null) { |
| mDisplayContent.continueUpdateImeTarget(); |
| } |
| } |
| |
| void setDisplayToSingleTaskInstance() { |
| final int childCount = getChildCount(); |
| if (childCount > 1) { |
| throw new IllegalArgumentException("Display already has multiple stacks. display=" |
| + this); |
| } |
| if (childCount > 0) { |
| final ActivityStack stack = getChildAt(0); |
| if (stack.getChildCount() > 1) { |
| throw new IllegalArgumentException("Display stack already has multiple tasks." |
| + " display=" + this + " stack=" + stack); |
| } |
| } |
| |
| mSingleTaskInstance = true; |
| } |
| |
| /** Returns true if the display can only contain one task */ |
| boolean isSingleTaskInstance() { |
| return mSingleTaskInstance; |
| } |
| |
| @VisibleForTesting |
| void removeAllTasks() { |
| for (int i = getChildCount() - 1; i >= 0; --i) { |
| final ActivityStack stack = getChildAt(i); |
| final ArrayList<TaskRecord> tasks = stack.getAllTasks(); |
| for (int j = tasks.size() - 1; j >= 0; --j) { |
| stack.removeChild(tasks.get(j), "removeAllTasks"); |
| } |
| } |
| } |
| |
| public void dump(PrintWriter pw, String prefix) { |
| pw.println(prefix + "displayId=" + mDisplayId + " stacks=" + mStacks.size() |
| + (mSingleTaskInstance ? " mSingleTaskInstance" : "")); |
| 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); |
| } |
| if (mPreferredTopFocusableStack != null) { |
| pw.println(myPrefix + "mPreferredTopFocusableStack=" + mPreferredTopFocusableStack); |
| } |
| if (mLastFocusedStack != null) { |
| pw.println(myPrefix + "mLastFocusedStack=" + mLastFocusedStack); |
| } |
| } |
| |
| 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, |
| @WindowTraceLogLevel int logLevel) { |
| final long token = proto.start(fieldId); |
| super.writeToProto(proto, CONFIGURATION_CONTAINER, logLevel); |
| proto.write(ID, mDisplayId); |
| proto.write(SINGLE_TASK_INSTANCE, mSingleTaskInstance); |
| final ActivityStack focusedStack = getFocusedStack(); |
| if (focusedStack != null) { |
| proto.write(FOCUSED_STACK_ID, focusedStack.mStackId); |
| final ActivityRecord focusedActivity = focusedStack.getDisplay().getResumedActivity(); |
| if (focusedActivity != null) { |
| focusedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY); |
| } |
| } else { |
| proto.write(FOCUSED_STACK_ID, INVALID_STACK_ID); |
| } |
| for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { |
| final ActivityStack stack = mStacks.get(stackNdx); |
| stack.writeToProto(proto, STACKS, logLevel); |
| } |
| proto.end(token); |
| } |
| |
| /** |
| * Callback for when the order of the stacks in the display changes. |
| */ |
| interface OnStackOrderChangedListener { |
| void onStackOrderChanged(ActivityStack stack); |
| } |
| } |