| /* |
| * Copyright (C) 2013 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.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT; |
| import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; |
| import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; |
| 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.PINNED_WINDOWING_MODE_ELEVATION_IN_DIP; |
| import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; |
| import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; |
| import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; |
| import static android.content.res.Configuration.ORIENTATION_PORTRAIT; |
| import static android.view.Display.DEFAULT_DISPLAY; |
| import static android.view.WindowManager.DOCKED_BOTTOM; |
| import static android.view.WindowManager.DOCKED_INVALID; |
| import static android.view.WindowManager.DOCKED_LEFT; |
| import static android.view.WindowManager.DOCKED_RIGHT; |
| import static android.view.WindowManager.DOCKED_TOP; |
| |
| import static com.android.server.wm.BoundsAnimationController.FADE_IN; |
| import static com.android.server.wm.BoundsAnimationController.NO_PIP_MODE_CHANGED_CALLBACKS; |
| import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_END; |
| import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_START; |
| import static com.android.server.wm.BoundsAnimationController.SchedulePipModeChangedState; |
| import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER; |
| import static com.android.server.wm.StackProto.ADJUSTED_BOUNDS; |
| import static com.android.server.wm.StackProto.ADJUSTED_FOR_IME; |
| import static com.android.server.wm.StackProto.ADJUST_DIVIDER_AMOUNT; |
| import static com.android.server.wm.StackProto.ADJUST_IME_AMOUNT; |
| import static com.android.server.wm.StackProto.ANIMATING_BOUNDS; |
| import static com.android.server.wm.StackProto.ANIMATION_BACKGROUND_SURFACE_IS_DIMMING; |
| import static com.android.server.wm.StackProto.BOUNDS; |
| import static com.android.server.wm.StackProto.DEFER_REMOVAL; |
| import static com.android.server.wm.StackProto.FILLS_PARENT; |
| import static com.android.server.wm.StackProto.ID; |
| import static com.android.server.wm.StackProto.MINIMIZE_AMOUNT; |
| import static com.android.server.wm.StackProto.TASKS; |
| import static com.android.server.wm.StackProto.WINDOW_CONTAINER; |
| import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK; |
| import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT; |
| import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; |
| |
| import android.annotation.CallSuper; |
| import android.app.RemoteAction; |
| import android.content.res.Configuration; |
| import android.graphics.Point; |
| import android.graphics.Rect; |
| import android.graphics.Region; |
| import android.os.RemoteException; |
| import android.util.DisplayMetrics; |
| import android.util.EventLog; |
| import android.util.Slog; |
| import android.util.proto.ProtoOutputStream; |
| import android.view.DisplayCutout; |
| import android.view.DisplayInfo; |
| import android.view.SurfaceControl; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.policy.DividerSnapAlgorithm; |
| import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget; |
| import com.android.internal.policy.DockedDividerUtils; |
| import com.android.server.EventLogTags; |
| |
| import java.io.PrintWriter; |
| import java.util.List; |
| |
| public class TaskStack extends WindowContainer<Task> implements |
| BoundsAnimationTarget, ConfigurationContainerListener { |
| /** Minimum size of an adjusted stack bounds relative to original stack bounds. Used to |
| * restrict IME adjustment so that a min portion of top stack remains visible.*/ |
| private static final float ADJUSTED_STACK_FRACTION_MIN = 0.3f; |
| |
| /** Dimming amount for non-focused stack when stacks are IME-adjusted. */ |
| private static final float IME_ADJUST_DIM_AMOUNT = 0.25f; |
| |
| /** Unique identifier */ |
| final int mStackId; |
| |
| /** The display this stack sits under. */ |
| // TODO: Track parent marks like this in WindowContainer. |
| private DisplayContent mDisplayContent; |
| |
| /** For comparison with DisplayContent bounds. */ |
| private Rect mTmpRect = new Rect(); |
| private Rect mTmpRect2 = new Rect(); |
| private Rect mTmpRect3 = new Rect(); |
| |
| /** For Pinned stack controlling. */ |
| private Rect mTmpFromBounds = new Rect(); |
| private Rect mTmpToBounds = new Rect(); |
| |
| /** Stack bounds adjusted to screen content area (taking into account IM windows, etc.) */ |
| private final Rect mAdjustedBounds = new Rect(); |
| |
| /** |
| * Fully adjusted IME bounds. These are different from {@link #mAdjustedBounds} because they |
| * represent the state when the animation has ended. |
| */ |
| private final Rect mFullyAdjustedImeBounds = new Rect(); |
| |
| private SurfaceControl mAnimationBackgroundSurface; |
| private boolean mAnimationBackgroundSurfaceIsShown = false; |
| |
| /** The particular window with an Animation with non-zero background color. */ |
| private WindowStateAnimator mAnimationBackgroundAnimator; |
| |
| /** Application tokens that are exiting, but still on screen for animations. */ |
| final AppTokenList mExitingAppTokens = new AppTokenList(); |
| final AppTokenList mTmpAppTokens = new AppTokenList(); |
| |
| /** Detach this stack from its display when animation completes. */ |
| // TODO: maybe tie this to WindowContainer#removeChild some how... |
| boolean mDeferRemoval; |
| |
| private final Rect mTmpAdjustedBounds = new Rect(); |
| private boolean mAdjustedForIme; |
| private boolean mImeGoingAway; |
| private WindowState mImeWin; |
| private float mMinimizeAmount; |
| private float mAdjustImeAmount; |
| private float mAdjustDividerAmount; |
| private final int mDockedStackMinimizeThickness; |
| |
| // If this is true, we are in the bounds animating mode. The task will be down or upscaled to |
| // perfectly fit the region it would have been cropped to. We may also avoid certain logic we |
| // would otherwise apply while resizing, while resizing in the bounds animating mode. |
| private boolean mBoundsAnimating = false; |
| // Set when an animation has been requested but has not yet started from the UI thread. This is |
| // cleared when the animation actually starts. |
| private boolean mBoundsAnimatingRequested = false; |
| private boolean mBoundsAnimatingToFullscreen = false; |
| private boolean mCancelCurrentBoundsAnimation = false; |
| private Rect mBoundsAnimationTarget = new Rect(); |
| private Rect mBoundsAnimationSourceHintBounds = new Rect(); |
| private @BoundsAnimationController.AnimationType int mAnimationType; |
| |
| Rect mPreAnimationBounds = new Rect(); |
| |
| private Dimmer mDimmer = new Dimmer(this); |
| |
| // TODO: remove after unification. |
| ActivityStack mActivityStack; |
| |
| /** |
| * For {@link #prepareSurfaces}. |
| */ |
| final Rect mTmpDimBoundsRect = new Rect(); |
| private final Point mLastSurfaceSize = new Point(); |
| |
| private final AnimatingAppWindowTokenRegistry mAnimatingAppWindowTokenRegistry = |
| new AnimatingAppWindowTokenRegistry(); |
| |
| TaskStack(WindowManagerService service, int stackId, ActivityStack activityStack) { |
| super(service); |
| mStackId = stackId; |
| mActivityStack = activityStack; |
| activityStack.registerConfigurationChangeListener(this); |
| mDockedStackMinimizeThickness = service.mContext.getResources().getDimensionPixelSize( |
| com.android.internal.R.dimen.docked_stack_minimize_thickness); |
| EventLog.writeEvent(EventLogTags.WM_STACK_CREATED, stackId); |
| } |
| |
| DisplayContent getDisplayContent() { |
| return mDisplayContent; |
| } |
| |
| Task findHomeTask() { |
| if (!isActivityTypeHome() || mChildren.isEmpty()) { |
| return null; |
| } |
| return mChildren.get(mChildren.size() - 1); |
| } |
| |
| void prepareFreezingTaskBounds() { |
| for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; --taskNdx) { |
| final Task task = mChildren.get(taskNdx); |
| task.prepareFreezingBounds(); |
| } |
| } |
| |
| /** |
| * Overrides the adjusted bounds, i.e. sets temporary layout bounds which are different from |
| * the normal task bounds. |
| * |
| * @param bounds The adjusted bounds. |
| */ |
| private void setAdjustedBounds(Rect bounds) { |
| if (mAdjustedBounds.equals(bounds) && !isAnimatingForIme()) { |
| return; |
| } |
| |
| mAdjustedBounds.set(bounds); |
| final boolean adjusted = !mAdjustedBounds.isEmpty(); |
| Rect insetBounds = null; |
| if (adjusted && isAdjustedForMinimizedDockedStack()) { |
| insetBounds = getRawBounds(); |
| } else if (adjusted && mAdjustedForIme) { |
| if (mImeGoingAway) { |
| insetBounds = getRawBounds(); |
| } else { |
| insetBounds = mFullyAdjustedImeBounds; |
| } |
| } |
| alignTasksToAdjustedBounds(adjusted ? mAdjustedBounds : getRawBounds(), insetBounds); |
| mDisplayContent.setLayoutNeeded(); |
| |
| updateSurfaceBounds(); |
| } |
| |
| private void alignTasksToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds) { |
| if (matchParentBounds()) { |
| return; |
| } |
| |
| final boolean alignBottom = mAdjustedForIme && getDockSide() == DOCKED_TOP; |
| |
| // Update bounds of containing tasks. |
| for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; --taskNdx) { |
| final Task task = mChildren.get(taskNdx); |
| task.alignToAdjustedBounds(adjustedBounds, tempInsetBounds, alignBottom); |
| } |
| } |
| |
| private void updateAnimationBackgroundBounds() { |
| if (mAnimationBackgroundSurface == null) { |
| return; |
| } |
| getRawBounds(mTmpRect); |
| final Rect stackBounds = getBounds(); |
| getPendingTransaction() |
| .setWindowCrop(mAnimationBackgroundSurface, mTmpRect.width(), mTmpRect.height()) |
| .setPosition(mAnimationBackgroundSurface, mTmpRect.left - stackBounds.left, |
| mTmpRect.top - stackBounds.top); |
| scheduleAnimation(); |
| } |
| |
| private void hideAnimationSurface() { |
| if (mAnimationBackgroundSurface == null) { |
| return; |
| } |
| getPendingTransaction().hide(mAnimationBackgroundSurface); |
| mAnimationBackgroundSurfaceIsShown = false; |
| scheduleAnimation(); |
| } |
| |
| private void showAnimationSurface(float alpha) { |
| if (mAnimationBackgroundSurface == null) { |
| return; |
| } |
| getPendingTransaction().setLayer(mAnimationBackgroundSurface, Integer.MIN_VALUE) |
| .setAlpha(mAnimationBackgroundSurface, alpha) |
| .show(mAnimationBackgroundSurface); |
| mAnimationBackgroundSurfaceIsShown = true; |
| scheduleAnimation(); |
| } |
| |
| @Override |
| public int setBounds(Rect bounds) { |
| return setBounds(getRequestedOverrideBounds(), bounds); |
| } |
| |
| private int setBounds(Rect existing, Rect bounds) { |
| if (equivalentBounds(existing, bounds)) { |
| return BOUNDS_CHANGE_NONE; |
| } |
| |
| final int result = super.setBounds(bounds); |
| |
| if (getParent() != null) { |
| updateAnimationBackgroundBounds(); |
| } |
| |
| updateAdjustedBounds(); |
| |
| updateSurfaceBounds(); |
| return result; |
| } |
| |
| /** Bounds of the stack without adjusting for other factors in the system like visibility |
| * of docked stack. |
| * Most callers should be using {@link ConfigurationContainer#getRequestedOverrideBounds} a |
| * it takes into consideration other system factors. */ |
| void getRawBounds(Rect out) { |
| out.set(getRawBounds()); |
| } |
| |
| Rect getRawBounds() { |
| return super.getBounds(); |
| } |
| |
| @Override |
| public void getBounds(Rect bounds) { |
| bounds.set(getBounds()); |
| } |
| |
| @Override |
| public Rect getBounds() { |
| // If we're currently adjusting for IME or minimized docked stack, we use the adjusted |
| // bounds; otherwise, no need to adjust the output bounds if fullscreen or the docked |
| // stack is visible since it is already what we want to represent to the rest of the |
| // system. |
| if (!mAdjustedBounds.isEmpty()) { |
| return mAdjustedBounds; |
| } else { |
| return super.getBounds(); |
| } |
| } |
| |
| /** |
| * Sets the bounds animation target bounds ahead of an animation. This can't currently be done |
| * in onAnimationStart() since that is started on the UiThread. |
| */ |
| private void setAnimationFinalBounds(Rect sourceHintBounds, Rect destBounds, |
| boolean toFullscreen) { |
| mBoundsAnimatingRequested = true; |
| mBoundsAnimatingToFullscreen = toFullscreen; |
| if (destBounds != null) { |
| mBoundsAnimationTarget.set(destBounds); |
| } else { |
| mBoundsAnimationTarget.setEmpty(); |
| } |
| if (sourceHintBounds != null) { |
| mBoundsAnimationSourceHintBounds.set(sourceHintBounds); |
| } else if (!mBoundsAnimating) { |
| // If the bounds are already animating, we don't want to reset the source hint. This is |
| // because the source hint is sent when starting the animation from the client that |
| // requested to enter pip. Other requests can adjust the pip bounds during an animation, |
| // but could accidentally reset the source hint bounds. |
| mBoundsAnimationSourceHintBounds.setEmpty(); |
| } |
| |
| mPreAnimationBounds.set(getRawBounds()); |
| } |
| |
| /** |
| * @return the final bounds for the bounds animation. |
| */ |
| void getFinalAnimationBounds(Rect outBounds) { |
| outBounds.set(mBoundsAnimationTarget); |
| } |
| |
| /** |
| * @return the final source bounds for the bounds animation. |
| */ |
| void getFinalAnimationSourceHintBounds(Rect outBounds) { |
| outBounds.set(mBoundsAnimationSourceHintBounds); |
| } |
| |
| /** |
| * @return the final animation bounds if the task stack is currently being animated, or the |
| * current stack bounds otherwise. |
| */ |
| void getAnimationOrCurrentBounds(Rect outBounds) { |
| if ((mBoundsAnimatingRequested || mBoundsAnimating) && !mBoundsAnimationTarget.isEmpty()) { |
| getFinalAnimationBounds(outBounds); |
| return; |
| } |
| getBounds(outBounds); |
| } |
| |
| /** Bounds of the stack with other system factors taken into consideration. */ |
| public void getDimBounds(Rect out) { |
| getBounds(out); |
| } |
| |
| /** |
| * Updates the passed-in {@code inOutBounds} based on the current state of the |
| * pinned controller. This gets run *after* the override configuration is updated, so it's |
| * safe to rely on the controller's state in here (though eventually this dependence should |
| * be removed). |
| * |
| * This does NOT modify this TaskStack's configuration. However, it does, for the time-being, |
| * update pinned controller state. |
| * |
| * @param inOutBounds the bounds to update (both input and output). |
| * @return true if bounds were updated to some non-empty value. |
| */ |
| boolean calculatePinnedBoundsForConfigChange(Rect inOutBounds) { |
| boolean animating = false; |
| if ((mBoundsAnimatingRequested || mBoundsAnimating) && !mBoundsAnimationTarget.isEmpty()) { |
| animating = true; |
| getFinalAnimationBounds(mTmpRect2); |
| } else { |
| mTmpRect2.set(inOutBounds); |
| } |
| boolean updated = mDisplayContent.mPinnedStackControllerLocked.onTaskStackBoundsChanged( |
| mTmpRect2, mTmpRect3); |
| if (updated) { |
| inOutBounds.set(mTmpRect3); |
| |
| // The final boundary is updated while there is an existing boundary animation. Let's |
| // cancel this animation to prevent the obsolete animation overwritten updated bounds. |
| if (animating && !inOutBounds.equals(mBoundsAnimationTarget)) { |
| final DisplayContent displayContent = getDisplayContent(); |
| displayContent.mBoundsAnimationController.getHandler().post(() -> |
| displayContent.mBoundsAnimationController.cancel(this)); |
| } |
| // Once we've set the bounds based on the rotation of the old bounds in the new |
| // orientation, clear the animation target bounds since they are obsolete, and |
| // cancel any currently running animations |
| mBoundsAnimationTarget.setEmpty(); |
| mBoundsAnimationSourceHintBounds.setEmpty(); |
| mCancelCurrentBoundsAnimation = true; |
| } |
| return updated; |
| } |
| |
| /** |
| * Updates the passed-in {@code inOutBounds} based on the current state of the |
| * docked controller. This gets run *after* the override configuration is updated, so it's |
| * safe to rely on the controller's state in here (though eventually this dependence should |
| * be removed). |
| * |
| * This does NOT modify this TaskStack's configuration. However, it does, for the time-being, |
| * update docked controller state. |
| * |
| * @param parentConfig the parent configuration for reference. |
| * @param inOutBounds the bounds to update (both input and output). |
| */ |
| void calculateDockedBoundsForConfigChange(Configuration parentConfig, Rect inOutBounds) { |
| final boolean primary = |
| getRequestedOverrideWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; |
| repositionSplitScreenStackAfterRotation(parentConfig, primary, inOutBounds); |
| final DisplayCutout cutout = mDisplayContent.getDisplayInfo().displayCutout; |
| snapDockedStackAfterRotation(parentConfig, cutout, inOutBounds); |
| if (primary) { |
| final int newDockSide = getDockSide(parentConfig, inOutBounds); |
| // Update the dock create mode and clear the dock create bounds, these |
| // might change after a rotation and the original values will be invalid. |
| mWmService.setDockedStackCreateStateLocked( |
| (newDockSide == DOCKED_LEFT || newDockSide == DOCKED_TOP) |
| ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT |
| : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, |
| null); |
| mDisplayContent.getDockedDividerController().notifyDockSideChanged(newDockSide); |
| } |
| } |
| |
| /** |
| * Some primary split screen sides are not allowed by the policy. This method queries the policy |
| * and moves the primary stack around if needed. |
| * |
| * @param parentConfig the configuration of the stack's parent. |
| * @param primary true if adjusting the primary docked stack, false for secondary. |
| * @param inOutBounds the bounds of the stack to adjust. |
| */ |
| void repositionSplitScreenStackAfterRotation(Configuration parentConfig, boolean primary, |
| Rect inOutBounds) { |
| final int dockSide = getDockSide(mDisplayContent, parentConfig, inOutBounds); |
| final int otherDockSide = DockedDividerUtils.invertDockSide(dockSide); |
| final int primaryDockSide = primary ? dockSide : otherDockSide; |
| if (mDisplayContent.getDockedDividerController() |
| .canPrimaryStackDockTo(primaryDockSide, |
| parentConfig.windowConfiguration.getBounds(), |
| parentConfig.windowConfiguration.getRotation())) { |
| return; |
| } |
| final Rect parentBounds = parentConfig.windowConfiguration.getBounds(); |
| switch (otherDockSide) { |
| case DOCKED_LEFT: |
| int movement = inOutBounds.left; |
| inOutBounds.left -= movement; |
| inOutBounds.right -= movement; |
| break; |
| case DOCKED_RIGHT: |
| movement = parentBounds.right - inOutBounds.right; |
| inOutBounds.left += movement; |
| inOutBounds.right += movement; |
| break; |
| case DOCKED_TOP: |
| movement = inOutBounds.top; |
| inOutBounds.top -= movement; |
| inOutBounds.bottom -= movement; |
| break; |
| case DOCKED_BOTTOM: |
| movement = parentBounds.bottom - inOutBounds.bottom; |
| inOutBounds.top += movement; |
| inOutBounds.bottom += movement; |
| break; |
| } |
| } |
| |
| /** |
| * Snaps the bounds after rotation to the closest snap target for the docked stack. |
| */ |
| void snapDockedStackAfterRotation(Configuration parentConfig, DisplayCutout displayCutout, |
| Rect outBounds) { |
| |
| // Calculate the current position. |
| final int dividerSize = mDisplayContent.getDockedDividerController().getContentWidth(); |
| final int dockSide = getDockSide(parentConfig, outBounds); |
| final int dividerPosition = DockedDividerUtils.calculatePositionForBounds(outBounds, |
| dockSide, dividerSize); |
| final int displayWidth = parentConfig.windowConfiguration.getBounds().width(); |
| final int displayHeight = parentConfig.windowConfiguration.getBounds().height(); |
| |
| // Snap the position to a target. |
| final int rotation = parentConfig.windowConfiguration.getRotation(); |
| final int orientation = parentConfig.orientation; |
| mDisplayContent.getDisplayPolicy().getStableInsetsLw(rotation, displayWidth, displayHeight, |
| displayCutout, outBounds); |
| final DividerSnapAlgorithm algorithm = new DividerSnapAlgorithm( |
| mWmService.mContext.getResources(), displayWidth, displayHeight, |
| dividerSize, orientation == Configuration.ORIENTATION_PORTRAIT, outBounds, |
| getDockSide(), isMinimizedDockAndHomeStackResizable()); |
| final SnapTarget target = algorithm.calculateNonDismissingSnapTarget(dividerPosition); |
| |
| // Recalculate the bounds based on the position of the target. |
| DockedDividerUtils.calculateBoundsForPosition(target.position, dockSide, |
| outBounds, displayWidth, displayHeight, |
| dividerSize); |
| } |
| |
| // TODO: Checkout the call points of this method and the ones below to see how they can fit in WC. |
| void addTask(Task task, int position) { |
| addTask(task, position, task.showForAllUsers(), true /* moveParents */); |
| } |
| |
| /** |
| * Put a Task in this stack. Used for adding only. |
| * When task is added to top of the stack, the entire branch of the hierarchy (including stack |
| * and display) will be brought to top. |
| * @param task The task to add. |
| * @param position Target position to add the task to. |
| * @param showForAllUsers Whether to show the task regardless of the current user. |
| */ |
| void addTask(Task task, int position, boolean showForAllUsers, boolean moveParents) { |
| final TaskStack currentStack = task.mStack; |
| // TODO: We pass stack to task's constructor, but we still need to call this method. |
| // This doesn't make sense, mStack will already be set equal to "this" at this point. |
| if (currentStack != null && currentStack.mStackId != mStackId) { |
| throw new IllegalStateException("Trying to add taskId=" + task.mTaskId |
| + " to stackId=" + mStackId |
| + ", but it is already attached to stackId=" + task.mStack.mStackId); |
| } |
| |
| // Add child task. |
| task.mStack = this; |
| addChild(task, null); |
| |
| // Move child to a proper position, as some restriction for position might apply. |
| positionChildAt(position, task, moveParents /* includingParents */, showForAllUsers); |
| } |
| |
| void positionChildAt(Task child, int position) { |
| if (DEBUG_STACK) { |
| Slog.i(TAG_WM, "positionChildAt: positioning task=" + child + " at " + position); |
| } |
| if (child == null) { |
| if (DEBUG_STACK) { |
| Slog.i(TAG_WM, "positionChildAt: could not find task=" + this); |
| } |
| return; |
| } |
| child.positionAt(position); |
| getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); |
| } |
| |
| void positionChildAtTop(Task child, boolean includingParents) { |
| if (child == null) { |
| // TODO: Fix the call-points that cause this to happen. |
| return; |
| } |
| |
| positionChildAt(POSITION_TOP, child, includingParents); |
| |
| final DisplayContent displayContent = getDisplayContent(); |
| if (displayContent.mAppTransition.isTransitionSet()) { |
| child.setSendingToBottom(false); |
| } |
| displayContent.layoutAndAssignWindowLayersIfNeeded(); |
| } |
| |
| void positionChildAtBottom(Task child, boolean includingParents) { |
| if (child == null) { |
| // TODO: Fix the call-points that cause this to happen. |
| return; |
| } |
| |
| positionChildAt(POSITION_BOTTOM, child, includingParents); |
| |
| if (getDisplayContent().mAppTransition.isTransitionSet()) { |
| child.setSendingToBottom(true); |
| } |
| getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); |
| } |
| |
| @Override |
| void positionChildAt(int position, Task child, boolean includingParents) { |
| positionChildAt(position, child, includingParents, child.showForAllUsers()); |
| } |
| |
| /** |
| * Overridden version of {@link TaskStack#positionChildAt(int, Task, boolean)}. Used in |
| * {@link TaskStack#addTask(Task, int, boolean showForAllUsers, boolean)}, as it can receive |
| * showForAllUsers param from {@link AppWindowToken} instead of {@link Task#showForAllUsers()}. |
| */ |
| private void positionChildAt(int position, Task child, boolean includingParents, |
| boolean showForAllUsers) { |
| final int targetPosition = findPositionForTask(child, position, showForAllUsers, |
| false /* addingNew */); |
| super.positionChildAt(targetPosition, child, includingParents); |
| |
| // Log positioning. |
| if (DEBUG_TASK_MOVEMENT) |
| Slog.d(TAG_WM, "positionTask: task=" + this + " position=" + position); |
| |
| final int toTop = targetPosition == mChildren.size() - 1 ? 1 : 0; |
| EventLog.writeEvent(EventLogTags.WM_TASK_MOVED, child.mTaskId, toTop, targetPosition); |
| } |
| |
| void reparent(int displayId, Rect outStackBounds, boolean onTop) { |
| final DisplayContent targetDc = mWmService.mRoot.getDisplayContent(displayId); |
| if (targetDc == null) { |
| throw new IllegalArgumentException("Trying to move stackId=" + mStackId |
| + " to unknown displayId=" + displayId); |
| } |
| |
| targetDc.moveStackToDisplay(this, onTop); |
| if (matchParentBounds()) { |
| outStackBounds.setEmpty(); |
| } else { |
| getRawBounds(outStackBounds); |
| } |
| } |
| |
| // TODO: We should really have users as a window container in the hierarchy so that we don't |
| // have to do complicated things like we are doing in this method. |
| private int findPositionForTask(Task task, int targetPosition, boolean showForAllUsers, |
| boolean addingNew) { |
| final boolean canShowTask = |
| showForAllUsers || mWmService.isCurrentProfileLocked(task.mUserId); |
| |
| final int stackSize = mChildren.size(); |
| int minPosition = 0; |
| int maxPosition = addingNew ? stackSize : stackSize - 1; |
| |
| if (canShowTask) { |
| minPosition = computeMinPosition(minPosition, stackSize); |
| } else { |
| maxPosition = computeMaxPosition(maxPosition); |
| } |
| |
| // preserve POSITION_BOTTOM/POSITION_TOP positions if they are still valid. |
| if (targetPosition == POSITION_BOTTOM && minPosition == 0) { |
| return POSITION_BOTTOM; |
| } else if (targetPosition == POSITION_TOP |
| && maxPosition == (addingNew ? stackSize : stackSize - 1)) { |
| return POSITION_TOP; |
| } |
| // Reset position based on minimum/maximum possible positions. |
| return Math.min(Math.max(targetPosition, minPosition), maxPosition); |
| } |
| |
| /** Calculate the minimum possible position for a task that can be shown to the user. |
| * The minimum position will be above all other tasks that can't be shown. |
| * @param minPosition The minimum position the caller is suggesting. |
| * We will start adjusting up from here. |
| * @param size The size of the current task list. |
| */ |
| private int computeMinPosition(int minPosition, int size) { |
| while (minPosition < size) { |
| final Task tmpTask = mChildren.get(minPosition); |
| final boolean canShowTmpTask = |
| tmpTask.showForAllUsers() |
| || mWmService.isCurrentProfileLocked(tmpTask.mUserId); |
| if (canShowTmpTask) { |
| break; |
| } |
| minPosition++; |
| } |
| return minPosition; |
| } |
| |
| /** Calculate the maximum possible position for a task that can't be shown to the user. |
| * The maximum position will be below all other tasks that can be shown. |
| * @param maxPosition The maximum position the caller is suggesting. |
| * We will start adjusting down from here. |
| */ |
| private int computeMaxPosition(int maxPosition) { |
| while (maxPosition > 0) { |
| final Task tmpTask = mChildren.get(maxPosition); |
| final boolean canShowTmpTask = |
| tmpTask.showForAllUsers() |
| || mWmService.isCurrentProfileLocked(tmpTask.mUserId); |
| if (!canShowTmpTask) { |
| break; |
| } |
| maxPosition--; |
| } |
| return maxPosition; |
| } |
| |
| /** |
| * Delete a Task from this stack. If it is the last Task in the stack, move this stack to the |
| * back. |
| * @param task The Task to delete. |
| */ |
| @Override |
| void removeChild(Task task) { |
| if (DEBUG_TASK_MOVEMENT) Slog.d(TAG_WM, "removeChild: task=" + task); |
| |
| super.removeChild(task); |
| task.mStack = null; |
| |
| if (mDisplayContent != null) { |
| if (mChildren.isEmpty()) { |
| getParent().positionChildAt(POSITION_BOTTOM, this, false /* includingParents */); |
| } |
| mDisplayContent.setLayoutNeeded(); |
| } |
| for (int appNdx = mExitingAppTokens.size() - 1; appNdx >= 0; --appNdx) { |
| final AppWindowToken wtoken = mExitingAppTokens.get(appNdx); |
| if (wtoken.getTask() == task) { |
| wtoken.mIsExiting = false; |
| mExitingAppTokens.remove(appNdx); |
| } |
| } |
| } |
| |
| @Override |
| public void onConfigurationChanged(Configuration newParentConfig) { |
| final int prevWindowingMode = getWindowingMode(); |
| super.onConfigurationChanged(newParentConfig); |
| |
| // Only need to update surface size here since the super method will handle updating |
| // surface position. |
| updateSurfaceSize(getPendingTransaction()); |
| final int windowingMode = getWindowingMode(); |
| final boolean isAlwaysOnTop = isAlwaysOnTop(); |
| |
| if (mDisplayContent == null) { |
| return; |
| } |
| |
| if (prevWindowingMode != windowingMode) { |
| mDisplayContent.onStackWindowingModeChanged(this); |
| |
| if (inSplitScreenSecondaryWindowingMode()) { |
| // When the stack is resized due to entering split screen secondary, offset the |
| // windows to compensate for the new stack position. |
| forAllWindows(w -> { |
| w.mWinAnimator.setOffsetPositionForStackResize(true); |
| }, true); |
| } |
| } |
| } |
| |
| private void updateSurfaceBounds() { |
| updateSurfaceSize(getPendingTransaction()); |
| updateSurfacePosition(); |
| scheduleAnimation(); |
| } |
| |
| /** |
| * Calculate an amount by which to expand the stack bounds in each direction. |
| * Used to make room for shadows in the pinned windowing mode. |
| */ |
| int getStackOutset() { |
| DisplayContent displayContent = getDisplayContent(); |
| if (inPinnedWindowingMode() && displayContent != null) { |
| final DisplayMetrics displayMetrics = displayContent.getDisplayMetrics(); |
| |
| // We multiply by two to match the client logic for converting view elevation |
| // to insets, as in {@link WindowManager.LayoutParams#setSurfaceInsets} |
| return (int)Math.ceil(mWmService.dipToPixel(PINNED_WINDOWING_MODE_ELEVATION_IN_DIP, |
| displayMetrics) * 2); |
| } |
| return 0; |
| } |
| |
| @Override |
| void getRelativeDisplayedPosition(Point outPos) { |
| super.getRelativeDisplayedPosition(outPos); |
| final int outset = getStackOutset(); |
| outPos.x -= outset; |
| outPos.y -= outset; |
| } |
| |
| private void updateSurfaceSize(SurfaceControl.Transaction transaction) { |
| if (mSurfaceControl == null) { |
| return; |
| } |
| |
| final Rect stackBounds = getDisplayedBounds(); |
| int width = stackBounds.width(); |
| int height = stackBounds.height(); |
| |
| final int outset = getStackOutset(); |
| width += 2*outset; |
| height += 2*outset; |
| |
| if (width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) { |
| return; |
| } |
| if (getWindowConfiguration().tasksAreFloating()) { |
| // Don't crop freeform windows to the stack. |
| transaction.setWindowCrop(mSurfaceControl, -1, -1); |
| } else { |
| transaction.setWindowCrop(mSurfaceControl, width, height); |
| } |
| mLastSurfaceSize.set(width, height); |
| } |
| |
| @VisibleForTesting |
| Point getLastSurfaceSize() { |
| return mLastSurfaceSize; |
| } |
| |
| @Override |
| void onDisplayChanged(DisplayContent dc) { |
| if (mDisplayContent != null && mDisplayContent != dc) { |
| throw new IllegalStateException("onDisplayChanged: Already attached"); |
| } |
| |
| final boolean movedToNewDisplay = mDisplayContent == null; |
| mDisplayContent = dc; |
| |
| updateSurfaceBounds(); |
| if (mAnimationBackgroundSurface == null) { |
| mAnimationBackgroundSurface = makeChildSurface(null).setColorLayer() |
| .setName("animation background stackId=" + mStackId) |
| .build(); |
| } |
| |
| super.onDisplayChanged(dc); |
| } |
| |
| /** |
| * Determines the stack and task bounds of the other stack when in docked mode. The current task |
| * bounds is passed in but depending on the stack, the task and stack must match. Only in |
| * minimized mode with resizable launcher, the other stack ignores calculating the stack bounds |
| * and uses the task bounds passed in as the stack and task bounds, otherwise the stack bounds |
| * is calculated and is also used for its task bounds. |
| * If any of the out bounds are empty, it represents default bounds |
| * |
| * @param currentTempTaskBounds the current task bounds of the other stack |
| * @param outStackBounds the calculated stack bounds of the other stack |
| * @param outTempTaskBounds the calculated task bounds of the other stack |
| */ |
| void getStackDockedModeBoundsLocked(Configuration parentConfig, Rect dockedBounds, |
| Rect currentTempTaskBounds, Rect outStackBounds, Rect outTempTaskBounds) { |
| outTempTaskBounds.setEmpty(); |
| |
| if (dockedBounds == null || dockedBounds.isEmpty()) { |
| // Calculate the primary docked bounds. |
| final boolean dockedOnTopOrLeft = mWmService.mDockedStackCreateMode |
| == SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; |
| getStackDockedModeBounds(parentConfig, |
| true /* primary */, outStackBounds, dockedBounds, |
| mDisplayContent.mDividerControllerLocked.getContentWidth(), dockedOnTopOrLeft); |
| return; |
| } |
| final int dockedSide = getDockSide(parentConfig, dockedBounds); |
| |
| // When the home stack is resizable, should always have the same stack and task bounds |
| if (isActivityTypeHome()) { |
| final Task homeTask = findHomeTask(); |
| if (homeTask != null && homeTask.isResizeable()) { |
| // Calculate the home stack bounds when in docked mode and the home stack is |
| // resizeable. |
| getDisplayContent().mDividerControllerLocked |
| .getHomeStackBoundsInDockedMode(parentConfig, |
| dockedSide, outStackBounds); |
| } else { |
| // Home stack isn't resizeable, so don't specify stack bounds. |
| outStackBounds.setEmpty(); |
| } |
| |
| outTempTaskBounds.set(outStackBounds); |
| return; |
| } |
| |
| // When minimized state, the stack bounds for all non-home and docked stack bounds should |
| // match the passed task bounds |
| if (isMinimizedDockAndHomeStackResizable() && currentTempTaskBounds != null) { |
| outStackBounds.set(currentTempTaskBounds); |
| return; |
| } |
| |
| if (dockedSide == DOCKED_INVALID) { |
| // Not sure how you got here...Only thing we can do is return current bounds. |
| Slog.e(TAG_WM, "Failed to get valid docked side for docked stack"); |
| outStackBounds.set(getRawBounds()); |
| return; |
| } |
| |
| final boolean dockedOnTopOrLeft = dockedSide == DOCKED_TOP || dockedSide == DOCKED_LEFT; |
| getStackDockedModeBounds(parentConfig, |
| false /* primary */, outStackBounds, dockedBounds, |
| mDisplayContent.mDividerControllerLocked.getContentWidth(), dockedOnTopOrLeft); |
| } |
| |
| /** |
| * Outputs the bounds a stack should be given the presence of a docked stack on the display. |
| * @param parentConfig The parent configuration. |
| * @param primary {@code true} if getting the primary stack bounds. |
| * @param outBounds Output bounds that should be used for the stack. |
| * @param dockedBounds Bounds of the docked stack. |
| * @param dockDividerWidth We need to know the width of the divider make to the output bounds |
| * close to the side of the dock. |
| * @param dockOnTopOrLeft If the docked stack is on the top or left side of the screen. |
| */ |
| private void getStackDockedModeBounds(Configuration parentConfig, boolean primary, |
| Rect outBounds, Rect dockedBounds, int dockDividerWidth, |
| boolean dockOnTopOrLeft) { |
| final Rect displayRect = parentConfig.windowConfiguration.getBounds(); |
| final boolean splitHorizontally = displayRect.width() > displayRect.height(); |
| |
| outBounds.set(displayRect); |
| if (primary) { |
| if (mWmService.mDockedStackCreateBounds != null) { |
| outBounds.set(mWmService.mDockedStackCreateBounds); |
| return; |
| } |
| |
| // The initial bounds of the docked stack when it is created about half the screen space |
| // and its bounds can be adjusted after that. The bounds of all other stacks are |
| // adjusted to occupy whatever screen space the docked stack isn't occupying. |
| final DisplayCutout displayCutout = mDisplayContent.getDisplayInfo().displayCutout; |
| mDisplayContent.getDisplayPolicy().getStableInsetsLw( |
| parentConfig.windowConfiguration.getRotation(), |
| displayRect.width(), displayRect.height(), displayCutout, mTmpRect2); |
| final int position = new DividerSnapAlgorithm(mWmService.mContext.getResources(), |
| displayRect.width(), |
| displayRect.height(), |
| dockDividerWidth, |
| parentConfig.orientation == ORIENTATION_PORTRAIT, |
| mTmpRect2).getMiddleTarget().position; |
| |
| if (dockOnTopOrLeft) { |
| if (splitHorizontally) { |
| outBounds.right = position; |
| } else { |
| outBounds.bottom = position; |
| } |
| } else { |
| if (splitHorizontally) { |
| outBounds.left = position + dockDividerWidth; |
| } else { |
| outBounds.top = position + dockDividerWidth; |
| } |
| } |
| return; |
| } |
| |
| // Other stacks occupy whatever space is left by the docked stack. |
| if (!dockOnTopOrLeft) { |
| if (splitHorizontally) { |
| outBounds.right = dockedBounds.left - dockDividerWidth; |
| } else { |
| outBounds.bottom = dockedBounds.top - dockDividerWidth; |
| } |
| } else { |
| if (splitHorizontally) { |
| outBounds.left = dockedBounds.right + dockDividerWidth; |
| } else { |
| outBounds.top = dockedBounds.bottom + dockDividerWidth; |
| } |
| } |
| DockedDividerUtils.sanitizeStackBounds(outBounds, !dockOnTopOrLeft); |
| } |
| |
| void resetDockedStackToMiddle() { |
| if (inSplitScreenPrimaryWindowingMode()) { |
| throw new IllegalStateException("Not a docked stack=" + this); |
| } |
| |
| mWmService.mDockedStackCreateBounds = null; |
| |
| final Rect bounds = new Rect(); |
| final Rect tempBounds = new Rect(); |
| TaskStack dockedStack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility(); |
| Rect dockedBounds = |
| (dockedStack == null || dockedStack == this) ? null : dockedStack.getRawBounds(); |
| getStackDockedModeBoundsLocked(mDisplayContent.getConfiguration(), dockedBounds, |
| null /* currentTempTaskBounds */, bounds, tempBounds); |
| mActivityStack.requestResize(bounds); |
| } |
| |
| @Override |
| void removeIfPossible() { |
| if (isSelfOrChildAnimating()) { |
| mDeferRemoval = true; |
| return; |
| } |
| removeImmediately(); |
| } |
| |
| @Override |
| void removeImmediately() { |
| if (mActivityStack != null) { |
| mActivityStack.unregisterConfigurationChangeListener(this); |
| } |
| super.removeImmediately(); |
| } |
| |
| @Override |
| void onParentChanged() { |
| super.onParentChanged(); |
| |
| if (getParent() != null || mDisplayContent == null) { |
| return; |
| } |
| |
| EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId); |
| |
| if (mAnimationBackgroundSurface != null) { |
| mAnimationBackgroundSurface.remove(); |
| mAnimationBackgroundSurface = null; |
| } |
| |
| mDisplayContent = null; |
| mWmService.mWindowPlacerLocked.requestTraversal(); |
| } |
| |
| void resetAnimationBackgroundAnimator() { |
| mAnimationBackgroundAnimator = null; |
| hideAnimationSurface(); |
| } |
| |
| void setAnimationBackground(WindowStateAnimator winAnimator, int color) { |
| if (mAnimationBackgroundAnimator == null) { |
| mAnimationBackgroundAnimator = winAnimator; |
| showAnimationSurface(((color >> 24) & 0xff) / 255f); |
| } |
| } |
| |
| // TODO: Should each user have there own stacks? |
| @Override |
| void switchUser() { |
| super.switchUser(); |
| int top = mChildren.size(); |
| for (int taskNdx = 0; taskNdx < top; ++taskNdx) { |
| Task task = mChildren.get(taskNdx); |
| if (mWmService.isCurrentProfileLocked(task.mUserId) || task.showForAllUsers()) { |
| mChildren.remove(taskNdx); |
| mChildren.add(task); |
| --top; |
| } |
| } |
| } |
| |
| /** |
| * Adjusts the stack bounds if the IME is visible. |
| * |
| * @param imeWin The IME window. |
| * @param keepLastAmount Use {@code true} to keep the last adjusted amount from |
| * {@link DockedStackDividerController} for adjusting the stack bounds, |
| * Use {@code false} to reset adjusted amount as 0. |
| * @see #updateAdjustForIme(float, float, boolean) |
| */ |
| void setAdjustedForIme(WindowState imeWin, boolean keepLastAmount) { |
| mImeWin = imeWin; |
| mImeGoingAway = false; |
| if (!mAdjustedForIme || keepLastAmount) { |
| mAdjustedForIme = true; |
| DockedStackDividerController controller = getDisplayContent().mDividerControllerLocked; |
| final float adjustImeAmount = keepLastAmount ? controller.mLastAnimationProgress : 0f; |
| final float adjustDividerAmount = keepLastAmount ? controller.mLastDividerProgress : 0f; |
| updateAdjustForIme(adjustImeAmount, adjustDividerAmount, true /* force */); |
| } |
| } |
| |
| boolean isAdjustedForIme() { |
| return mAdjustedForIme; |
| } |
| |
| boolean isAnimatingForIme() { |
| return mImeWin != null && mImeWin.isAnimatingLw(); |
| } |
| |
| /** |
| * Update the stack's bounds (crop or position) according to the IME window's |
| * current position. When IME window is animated, the bottom stack is animated |
| * together to track the IME window's current position, and the top stack is |
| * cropped as necessary. |
| * |
| * @return true if a traversal should be performed after the adjustment. |
| */ |
| boolean updateAdjustForIme(float adjustAmount, float adjustDividerAmount, boolean force) { |
| if (adjustAmount != mAdjustImeAmount |
| || adjustDividerAmount != mAdjustDividerAmount || force) { |
| mAdjustImeAmount = adjustAmount; |
| mAdjustDividerAmount = adjustDividerAmount; |
| updateAdjustedBounds(); |
| return isVisible(); |
| } else { |
| return false; |
| } |
| } |
| |
| /** |
| * Resets the adjustment after it got adjusted for the IME. |
| * @param adjustBoundsNow if true, reset and update the bounds immediately and forget about |
| * animations; otherwise, set flag and animates the window away together |
| * with IME window. |
| */ |
| void resetAdjustedForIme(boolean adjustBoundsNow) { |
| if (adjustBoundsNow) { |
| mImeWin = null; |
| mImeGoingAway = false; |
| mAdjustImeAmount = 0f; |
| mAdjustDividerAmount = 0f; |
| if (!mAdjustedForIme) { |
| return; |
| } |
| mAdjustedForIme = false; |
| updateAdjustedBounds(); |
| mWmService.setResizeDimLayer(false, getWindowingMode(), 1.0f); |
| } else { |
| mImeGoingAway |= mAdjustedForIme; |
| } |
| } |
| |
| /** |
| * Sets the amount how much we currently minimize our stack. |
| * |
| * @param minimizeAmount The amount, between 0 and 1. |
| * @return Whether the amount has changed and a layout is needed. |
| */ |
| boolean setAdjustedForMinimizedDock(float minimizeAmount) { |
| if (minimizeAmount != mMinimizeAmount) { |
| mMinimizeAmount = minimizeAmount; |
| updateAdjustedBounds(); |
| return isVisible(); |
| } else { |
| return false; |
| } |
| } |
| |
| boolean shouldIgnoreInput() { |
| return isAdjustedForMinimizedDockedStack() || |
| (inSplitScreenPrimaryWindowingMode() && isMinimizedDockAndHomeStackResizable()); |
| } |
| |
| /** |
| * Puts all visible tasks that are adjusted for IME into resizing mode and adds the windows |
| * to the list of to be drawn windows the service is waiting for. |
| */ |
| void beginImeAdjustAnimation() { |
| for (int j = mChildren.size() - 1; j >= 0; j--) { |
| final Task task = mChildren.get(j); |
| if (task.hasContentToDisplay()) { |
| task.setDragResizing(true, DRAG_RESIZE_MODE_DOCKED_DIVIDER); |
| task.setWaitingForDrawnIfResizingChanged(); |
| } |
| } |
| } |
| |
| /** |
| * Resets the resizing state of all windows. |
| */ |
| void endImeAdjustAnimation() { |
| for (int j = mChildren.size() - 1; j >= 0; j--) { |
| mChildren.get(j).setDragResizing(false, DRAG_RESIZE_MODE_DOCKED_DIVIDER); |
| } |
| } |
| |
| int getMinTopStackBottom(final Rect displayContentRect, int originalStackBottom) { |
| return displayContentRect.top + (int) |
| ((originalStackBottom - displayContentRect.top) * ADJUSTED_STACK_FRACTION_MIN); |
| } |
| |
| private boolean adjustForIME(final WindowState imeWin) { |
| // To prevent task stack resize animation may flicking when playing app transition |
| // animation & IME window enter animation in parallel, we need to make sure app |
| // transition is done and then adjust task size for IME, skip the new adjusted frame when |
| // app transition is still running. |
| if (getDisplayContent().mAppTransition.isRunning()) { |
| return false; |
| } |
| |
| final int dockedSide = getDockSide(); |
| final boolean dockedTopOrBottom = dockedSide == DOCKED_TOP || dockedSide == DOCKED_BOTTOM; |
| if (imeWin == null || !dockedTopOrBottom) { |
| return false; |
| } |
| |
| final Rect displayStableRect = mTmpRect; |
| final Rect contentBounds = mTmpRect2; |
| |
| // Calculate the content bounds excluding the area occupied by IME |
| getDisplayContent().getStableRect(displayStableRect); |
| contentBounds.set(displayStableRect); |
| int imeTop = Math.max(imeWin.getFrameLw().top, contentBounds.top); |
| |
| imeTop += imeWin.getGivenContentInsetsLw().top; |
| if (contentBounds.bottom > imeTop) { |
| contentBounds.bottom = imeTop; |
| } |
| |
| final int yOffset = displayStableRect.bottom - contentBounds.bottom; |
| |
| final int dividerWidth = |
| getDisplayContent().mDividerControllerLocked.getContentWidth(); |
| final int dividerWidthInactive = |
| getDisplayContent().mDividerControllerLocked.getContentWidthInactive(); |
| |
| if (dockedSide == DOCKED_TOP) { |
| // If this stack is docked on top, we make it smaller so the bottom stack is not |
| // occluded by IME. We shift its bottom up by the height of the IME, but |
| // leaves at least 30% of the top stack visible. |
| final int minTopStackBottom = |
| getMinTopStackBottom(displayStableRect, getRawBounds().bottom); |
| final int bottom = Math.max( |
| getRawBounds().bottom - yOffset + dividerWidth - dividerWidthInactive, |
| minTopStackBottom); |
| mTmpAdjustedBounds.set(getRawBounds()); |
| mTmpAdjustedBounds.bottom = (int) (mAdjustImeAmount * bottom + (1 - mAdjustImeAmount) |
| * getRawBounds().bottom); |
| mFullyAdjustedImeBounds.set(getRawBounds()); |
| } else { |
| // When the stack is on bottom and has no focus, it's only adjusted for divider width. |
| final int dividerWidthDelta = dividerWidthInactive - dividerWidth; |
| |
| // When the stack is on bottom and has focus, it needs to be moved up so as to |
| // not occluded by IME, and at the same time adjusted for divider width. |
| // We try to move it up by the height of the IME window, but only to the extent |
| // that leaves at least 30% of the top stack visible. |
| // 'top' is where the top of bottom stack will move to in this case. |
| final int topBeforeImeAdjust = |
| getRawBounds().top - dividerWidth + dividerWidthInactive; |
| final int minTopStackBottom = |
| getMinTopStackBottom(displayStableRect, |
| getRawBounds().top - dividerWidth); |
| final int top = Math.max( |
| getRawBounds().top - yOffset, minTopStackBottom + dividerWidthInactive); |
| |
| mTmpAdjustedBounds.set(getRawBounds()); |
| // Account for the adjustment for IME and divider width separately. |
| // (top - topBeforeImeAdjust) is the amount of movement due to IME only, |
| // and dividerWidthDelta is due to divider width change only. |
| mTmpAdjustedBounds.top = getRawBounds().top + |
| (int) (mAdjustImeAmount * (top - topBeforeImeAdjust) + |
| mAdjustDividerAmount * dividerWidthDelta); |
| mFullyAdjustedImeBounds.set(getRawBounds()); |
| mFullyAdjustedImeBounds.top = top; |
| mFullyAdjustedImeBounds.bottom = top + getRawBounds().height(); |
| } |
| return true; |
| } |
| |
| private boolean adjustForMinimizedDockedStack(float minimizeAmount) { |
| final int dockSide = getDockSide(); |
| if (dockSide == DOCKED_INVALID && !mTmpAdjustedBounds.isEmpty()) { |
| return false; |
| } |
| |
| if (dockSide == DOCKED_TOP) { |
| mWmService.getStableInsetsLocked(DEFAULT_DISPLAY, mTmpRect); |
| int topInset = mTmpRect.top; |
| mTmpAdjustedBounds.set(getRawBounds()); |
| mTmpAdjustedBounds.bottom = (int) (minimizeAmount * topInset + (1 - minimizeAmount) |
| * getRawBounds().bottom); |
| } else if (dockSide == DOCKED_LEFT) { |
| mTmpAdjustedBounds.set(getRawBounds()); |
| final int width = getRawBounds().width(); |
| mTmpAdjustedBounds.right = |
| (int) (minimizeAmount * mDockedStackMinimizeThickness |
| + (1 - minimizeAmount) * getRawBounds().right); |
| mTmpAdjustedBounds.left = mTmpAdjustedBounds.right - width; |
| } else if (dockSide == DOCKED_RIGHT) { |
| mTmpAdjustedBounds.set(getRawBounds()); |
| mTmpAdjustedBounds.left = (int) (minimizeAmount * |
| (getRawBounds().right - mDockedStackMinimizeThickness) |
| + (1 - minimizeAmount) * getRawBounds().left); |
| } |
| return true; |
| } |
| |
| private boolean isMinimizedDockAndHomeStackResizable() { |
| return mDisplayContent.mDividerControllerLocked.isMinimizedDock() |
| && mDisplayContent.mDividerControllerLocked.isHomeStackResizable(); |
| } |
| |
| /** |
| * @return the distance in pixels how much the stack gets minimized from it's original size |
| */ |
| int getMinimizeDistance() { |
| final int dockSide = getDockSide(); |
| if (dockSide == DOCKED_INVALID) { |
| return 0; |
| } |
| |
| if (dockSide == DOCKED_TOP) { |
| mWmService.getStableInsetsLocked(DEFAULT_DISPLAY, mTmpRect); |
| int topInset = mTmpRect.top; |
| return getRawBounds().bottom - topInset; |
| } else if (dockSide == DOCKED_LEFT || dockSide == DOCKED_RIGHT) { |
| return getRawBounds().width() - mDockedStackMinimizeThickness; |
| } else { |
| return 0; |
| } |
| } |
| |
| /** |
| * Updates the adjustment depending on it's current state. |
| */ |
| private void updateAdjustedBounds() { |
| boolean adjust = false; |
| if (mMinimizeAmount != 0f) { |
| adjust = adjustForMinimizedDockedStack(mMinimizeAmount); |
| } else if (mAdjustedForIme) { |
| adjust = adjustForIME(mImeWin); |
| } |
| if (!adjust) { |
| mTmpAdjustedBounds.setEmpty(); |
| } |
| setAdjustedBounds(mTmpAdjustedBounds); |
| |
| final boolean isImeTarget = (mWmService.getImeFocusStackLocked() == this); |
| if (mAdjustedForIme && adjust && !isImeTarget) { |
| final float alpha = Math.max(mAdjustImeAmount, mAdjustDividerAmount) |
| * IME_ADJUST_DIM_AMOUNT; |
| mWmService.setResizeDimLayer(true, getWindowingMode(), alpha); |
| } |
| } |
| |
| void applyAdjustForImeIfNeeded(Task task) { |
| if (mMinimizeAmount != 0f || !mAdjustedForIme || mAdjustedBounds.isEmpty()) { |
| return; |
| } |
| |
| final Rect insetBounds = mImeGoingAway ? getRawBounds() : mFullyAdjustedImeBounds; |
| task.alignToAdjustedBounds(mAdjustedBounds, insetBounds, getDockSide() == DOCKED_TOP); |
| mDisplayContent.setLayoutNeeded(); |
| } |
| |
| |
| boolean isAdjustedForMinimizedDockedStack() { |
| return mMinimizeAmount != 0f; |
| } |
| |
| /** |
| * @return {@code true} if we have a {@link Task} that is animating (currently only used for the |
| * recents animation); {@code false} otherwise. |
| */ |
| boolean isTaskAnimating() { |
| for (int j = mChildren.size() - 1; j >= 0; j--) { |
| final Task task = mChildren.get(j); |
| if (task.isTaskAnimating()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @CallSuper |
| @Override |
| public void writeToProto(ProtoOutputStream proto, long fieldId, |
| @WindowTraceLogLevel int logLevel) { |
| if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible()) { |
| return; |
| } |
| |
| final long token = proto.start(fieldId); |
| super.writeToProto(proto, WINDOW_CONTAINER, logLevel); |
| proto.write(ID, mStackId); |
| for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) { |
| mChildren.get(taskNdx).writeToProto(proto, TASKS, logLevel); |
| } |
| proto.write(FILLS_PARENT, matchParentBounds()); |
| getRawBounds().writeToProto(proto, BOUNDS); |
| proto.write(ANIMATION_BACKGROUND_SURFACE_IS_DIMMING, mAnimationBackgroundSurfaceIsShown); |
| proto.write(DEFER_REMOVAL, mDeferRemoval); |
| proto.write(MINIMIZE_AMOUNT, mMinimizeAmount); |
| proto.write(ADJUSTED_FOR_IME, mAdjustedForIme); |
| proto.write(ADJUST_IME_AMOUNT, mAdjustImeAmount); |
| proto.write(ADJUST_DIVIDER_AMOUNT, mAdjustDividerAmount); |
| mAdjustedBounds.writeToProto(proto, ADJUSTED_BOUNDS); |
| proto.write(ANIMATING_BOUNDS, mBoundsAnimating); |
| proto.end(token); |
| } |
| |
| @Override |
| void dump(PrintWriter pw, String prefix, boolean dumpAll) { |
| pw.println(prefix + "mStackId=" + mStackId); |
| pw.println(prefix + "mDeferRemoval=" + mDeferRemoval); |
| pw.println(prefix + "mBounds=" + getRawBounds().toShortString()); |
| if (mMinimizeAmount != 0f) { |
| pw.println(prefix + "mMinimizeAmount=" + mMinimizeAmount); |
| } |
| if (mAdjustedForIme) { |
| pw.println(prefix + "mAdjustedForIme=true"); |
| pw.println(prefix + "mAdjustImeAmount=" + mAdjustImeAmount); |
| pw.println(prefix + "mAdjustDividerAmount=" + mAdjustDividerAmount); |
| } |
| if (!mAdjustedBounds.isEmpty()) { |
| pw.println(prefix + "mAdjustedBounds=" + mAdjustedBounds.toShortString()); |
| } |
| for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) { |
| mChildren.get(taskNdx).dump(pw, prefix + " ", dumpAll); |
| } |
| if (mAnimationBackgroundSurfaceIsShown) { |
| pw.println(prefix + "mWindowAnimationBackgroundSurface is shown"); |
| } |
| if (!mExitingAppTokens.isEmpty()) { |
| pw.println(); |
| pw.println(" Exiting application tokens:"); |
| for (int i = mExitingAppTokens.size() - 1; i >= 0; i--) { |
| WindowToken token = mExitingAppTokens.get(i); |
| pw.print(" Exiting App #"); pw.print(i); |
| pw.print(' '); pw.print(token); |
| pw.println(':'); |
| token.dump(pw, " ", dumpAll); |
| } |
| } |
| mAnimatingAppWindowTokenRegistry.dump(pw, "AnimatingApps:", prefix); |
| } |
| |
| @Override |
| boolean fillsParent() { |
| return matchParentBounds(); |
| } |
| |
| @Override |
| public String toString() { |
| return "{stackId=" + mStackId + " tasks=" + mChildren + "}"; |
| } |
| |
| String getName() { |
| return toShortString(); |
| } |
| |
| public String toShortString() { |
| return "Stack=" + mStackId; |
| } |
| |
| /** |
| * For docked workspace (or workspace that's side-by-side to the docked), provides |
| * information which side of the screen was the dock anchored. |
| */ |
| int getDockSide() { |
| return getDockSide(mDisplayContent.getConfiguration(), getRawBounds()); |
| } |
| |
| int getDockSideForDisplay(DisplayContent dc) { |
| return getDockSide(dc, dc.getConfiguration(), getRawBounds()); |
| } |
| |
| int getDockSide(Configuration parentConfig, Rect bounds) { |
| if (mDisplayContent == null) { |
| return DOCKED_INVALID; |
| } |
| return getDockSide(mDisplayContent, parentConfig, bounds); |
| } |
| |
| private int getDockSide(DisplayContent dc, Configuration parentConfig, Rect bounds) { |
| return dc.getDockedDividerController().getDockSide(bounds, |
| parentConfig.windowConfiguration.getBounds(), |
| parentConfig.orientation, parentConfig.windowConfiguration.getRotation()); |
| } |
| |
| boolean hasTaskForUser(int userId) { |
| for (int i = mChildren.size() - 1; i >= 0; i--) { |
| final Task task = mChildren.get(i); |
| if (task.mUserId == userId) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void findTaskForResizePoint(int x, int y, int delta, |
| DisplayContent.TaskForResizePointSearchResult results) { |
| if (!getWindowConfiguration().canResizeTask()) { |
| results.searchDone = true; |
| return; |
| } |
| |
| for (int i = mChildren.size() - 1; i >= 0; --i) { |
| final Task task = mChildren.get(i); |
| if (task.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { |
| results.searchDone = true; |
| return; |
| } |
| |
| // We need to use the task's dim bounds (which is derived from the visible bounds of |
| // its apps windows) for any touch-related tests. Can't use the task's original |
| // bounds because it might be adjusted to fit the content frame. One example is when |
| // the task is put to top-left quadrant, the actual visible area would not start at |
| // (0,0) after it's adjusted for the status bar. |
| task.getDimBounds(mTmpRect); |
| mTmpRect.inset(-delta, -delta); |
| if (mTmpRect.contains(x, y)) { |
| mTmpRect.inset(delta, delta); |
| |
| results.searchDone = true; |
| |
| if (!mTmpRect.contains(x, y)) { |
| results.taskForResize = task; |
| return; |
| } |
| // User touched inside the task. No need to look further, |
| // focus transfer will be handled in ACTION_UP. |
| return; |
| } |
| } |
| } |
| |
| void setTouchExcludeRegion(Task focusedTask, int delta, Region touchExcludeRegion, |
| Rect contentRect, Rect postExclude) { |
| for (int i = mChildren.size() - 1; i >= 0; --i) { |
| final Task task = mChildren.get(i); |
| AppWindowToken token = task.getTopVisibleAppToken(); |
| if (token == null || !token.hasContentToDisplay()) { |
| continue; |
| } |
| |
| /** |
| * Exclusion region is the region that TapDetector doesn't care about. |
| * Here we want to remove all non-focused tasks from the exclusion region. |
| * We also remove the outside touch area for resizing for all freeform |
| * tasks (including the focused). |
| * |
| * We save the focused task region once we find it, and add it back at the end. |
| * |
| * If the task is home stack and it is resizable in the minimized state, we want to |
| * exclude the docked stack from touch so we need the entire screen area and not just a |
| * small portion which the home stack currently is resized to. |
| */ |
| |
| if (task.isActivityTypeHome() && isMinimizedDockAndHomeStackResizable()) { |
| mDisplayContent.getBounds(mTmpRect); |
| } else { |
| task.getDimBounds(mTmpRect); |
| } |
| |
| if (task == focusedTask) { |
| // Add the focused task rect back into the exclude region once we are done |
| // processing stacks. |
| postExclude.set(mTmpRect); |
| } |
| |
| final boolean isFreeformed = task.inFreeformWindowingMode(); |
| if (task != focusedTask || isFreeformed) { |
| if (isFreeformed) { |
| // If the task is freeformed, enlarge the area to account for outside |
| // touch area for resize. |
| mTmpRect.inset(-delta, -delta); |
| // Intersect with display content rect. If we have system decor (status bar/ |
| // navigation bar), we want to exclude that from the tap detection. |
| // Otherwise, if the app is partially placed under some system button (eg. |
| // Recents, Home), pressing that button would cause a full series of |
| // unwanted transfer focus/resume/pause, before we could go home. |
| mTmpRect.intersect(contentRect); |
| } |
| touchExcludeRegion.op(mTmpRect, Region.Op.DIFFERENCE); |
| } |
| } |
| } |
| |
| public boolean setPinnedStackSize(Rect stackBounds, Rect tempTaskBounds) { |
| // Hold the lock since this is called from the BoundsAnimator running on the UiThread |
| synchronized (mWmService.mGlobalLock) { |
| if (mCancelCurrentBoundsAnimation) { |
| return false; |
| } |
| } |
| |
| try { |
| mWmService.mActivityTaskManager.resizePinnedStack(stackBounds, tempTaskBounds); |
| } catch (RemoteException e) { |
| // I don't believe you. |
| } |
| return true; |
| } |
| |
| void onAllWindowsDrawn() { |
| if (!mBoundsAnimating && !mBoundsAnimatingRequested) { |
| return; |
| } |
| |
| getDisplayContent().mBoundsAnimationController.onAllWindowsDrawn(); |
| } |
| |
| @Override // AnimatesBounds |
| public boolean onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate, |
| @BoundsAnimationController.AnimationType int animationType) { |
| // Hold the lock since this is called from the BoundsAnimator running on the UiThread |
| synchronized (mWmService.mGlobalLock) { |
| if (!isAttached()) { |
| // Don't run the animation if the stack is already detached |
| return false; |
| } |
| |
| mBoundsAnimatingRequested = false; |
| mBoundsAnimating = true; |
| mAnimationType = animationType; |
| |
| // If we are changing UI mode, as in the PiP to fullscreen |
| // transition, then we need to wait for the window to draw. |
| if (schedulePipModeChangedCallback) { |
| forAllWindows((w) -> { w.mWinAnimator.resetDrawState(); }, |
| false /* traverseTopToBottom */); |
| } |
| } |
| |
| if (inPinnedWindowingMode()) { |
| try { |
| mWmService.mActivityTaskManager.notifyPinnedStackAnimationStarted(); |
| } catch (RemoteException e) { |
| // I don't believe you... |
| } |
| |
| if ((schedulePipModeChangedCallback || animationType == FADE_IN) |
| && mActivityStack != null) { |
| // We need to schedule the PiP mode change before the animation up. It is possible |
| // in this case for the animation down to not have been completed, so always |
| // force-schedule and update to the client to ensure that it is notified that it |
| // is no longer in picture-in-picture mode |
| mActivityStack.updatePictureInPictureModeForPinnedStackAnimation(null, |
| forceUpdate); |
| } |
| } |
| return true; |
| } |
| |
| @Override // AnimatesBounds |
| public void onAnimationEnd(boolean schedulePipModeChangedCallback, Rect finalStackSize, |
| boolean moveToFullscreen) { |
| if (mAnimationType == BoundsAnimationController.FADE_IN) { |
| setPinnedStackAlpha(1f); |
| try { |
| mWmService.mActivityTaskManager.notifyPinnedStackAnimationEnded(); |
| } catch (RemoteException e) { |
| // I don't believe you... |
| } |
| return; |
| } |
| |
| onBoundAnimationEnd(schedulePipModeChangedCallback, finalStackSize, moveToFullscreen); |
| } |
| |
| private void onBoundAnimationEnd(boolean schedulePipModeChangedCallback, Rect finalStackSize, |
| boolean moveToFullscreen) { |
| if (inPinnedWindowingMode()) { |
| // Update to the final bounds if requested. This is done here instead of in the bounds |
| // animator to allow us to coordinate this after we notify the PiP mode changed |
| |
| if (schedulePipModeChangedCallback) { |
| // We need to schedule the PiP mode change after the animation down, so use the |
| // final bounds |
| mActivityStack.updatePictureInPictureModeForPinnedStackAnimation( |
| mBoundsAnimationTarget, false /* forceUpdate */); |
| } |
| |
| if (finalStackSize != null && !mCancelCurrentBoundsAnimation) { |
| setPinnedStackSize(finalStackSize, null); |
| } else { |
| // We have been canceled, so the final stack size is null, still run the |
| // animation-end logic |
| onPipAnimationEndResize(); |
| } |
| |
| try { |
| mWmService.mActivityTaskManager.notifyPinnedStackAnimationEnded(); |
| if (moveToFullscreen) { |
| mWmService.mActivityTaskManager.moveTasksToFullscreenStack(mStackId, |
| true /* onTop */); |
| } |
| } catch (RemoteException e) { |
| // I don't believe you... |
| } |
| } else { |
| // No PiP animation, just run the normal animation-end logic |
| onPipAnimationEndResize(); |
| } |
| } |
| |
| /** |
| * @return the current stack bounds transformed to the given {@param aspectRatio}. If |
| * the default bounds is {@code null}, then the {@param aspectRatio} is applied to the |
| * default bounds. |
| */ |
| Rect getPictureInPictureBounds(float aspectRatio, Rect stackBounds) { |
| if (!mWmService.mSupportsPictureInPicture) { |
| return null; |
| } |
| |
| final DisplayContent displayContent = getDisplayContent(); |
| if (displayContent == null) { |
| return null; |
| } |
| |
| if (!inPinnedWindowingMode()) { |
| return null; |
| } |
| |
| final PinnedStackController pinnedStackController = |
| displayContent.getPinnedStackController(); |
| if (stackBounds == null) { |
| // Calculate the aspect ratio bounds from the default bounds |
| stackBounds = pinnedStackController.getDefaultOrLastSavedBounds(); |
| } |
| |
| if (pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)) { |
| return pinnedStackController.transformBoundsToAspectRatio(stackBounds, aspectRatio, |
| true /* useCurrentMinEdgeSize */); |
| } else { |
| return stackBounds; |
| } |
| } |
| |
| /** |
| * Animates the pinned stack. |
| */ |
| void animateResizePinnedStack(Rect toBounds, Rect sourceHintBounds, |
| int animationDuration, boolean fromFullscreen) { |
| if (!inPinnedWindowingMode()) { |
| return; |
| } |
| // Get the from-bounds |
| final Rect fromBounds = new Rect(); |
| getBounds(fromBounds); |
| |
| // Get non-null fullscreen to-bounds for animating if the bounds are null |
| @SchedulePipModeChangedState int schedulePipModeChangedState = |
| NO_PIP_MODE_CHANGED_CALLBACKS; |
| final boolean toFullscreen = toBounds == null; |
| if (toFullscreen) { |
| if (fromFullscreen) { |
| throw new IllegalArgumentException("Should not defer scheduling PiP mode" |
| + " change on animation to fullscreen."); |
| } |
| schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_START; |
| |
| mWmService.getStackBounds( |
| WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mTmpToBounds); |
| if (!mTmpToBounds.isEmpty()) { |
| // If there is a fullscreen bounds, use that |
| toBounds = new Rect(mTmpToBounds); |
| } else { |
| // Otherwise, use the display bounds |
| toBounds = new Rect(); |
| getDisplayContent().getBounds(toBounds); |
| } |
| } else if (fromFullscreen) { |
| schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END; |
| } |
| |
| setAnimationFinalBounds(sourceHintBounds, toBounds, toFullscreen); |
| |
| final Rect finalToBounds = toBounds; |
| final @SchedulePipModeChangedState int finalSchedulePipModeChangedState = |
| schedulePipModeChangedState; |
| final DisplayContent displayContent = getDisplayContent(); |
| @BoundsAnimationController.AnimationType int intendedAnimationType = |
| displayContent.mBoundsAnimationController.getAnimationType(); |
| if (intendedAnimationType == FADE_IN) { |
| if (fromFullscreen) { |
| setPinnedStackAlpha(0f); |
| } |
| if (toBounds.width() == fromBounds.width() |
| && toBounds.height() == fromBounds.height()) { |
| intendedAnimationType = BoundsAnimationController.BOUNDS; |
| } |
| } |
| |
| final @BoundsAnimationController.AnimationType int animationType = intendedAnimationType; |
| mCancelCurrentBoundsAnimation = false; |
| displayContent.mBoundsAnimationController.getHandler().post(() -> { |
| displayContent.mBoundsAnimationController.animateBounds(this, fromBounds, |
| finalToBounds, animationDuration, finalSchedulePipModeChangedState, |
| fromFullscreen, toFullscreen, animationType); |
| }); |
| } |
| |
| /** |
| * Sets the current picture-in-picture aspect ratio. |
| */ |
| void setPictureInPictureAspectRatio(float aspectRatio) { |
| if (!mWmService.mSupportsPictureInPicture) { |
| return; |
| } |
| |
| if (!inPinnedWindowingMode()) { |
| return; |
| } |
| |
| final PinnedStackController pinnedStackController = |
| getDisplayContent().getPinnedStackController(); |
| |
| if (Float.compare(aspectRatio, pinnedStackController.getAspectRatio()) == 0) { |
| return; |
| } |
| getAnimationOrCurrentBounds(mTmpFromBounds); |
| mTmpToBounds.set(mTmpFromBounds); |
| getPictureInPictureBounds(aspectRatio, mTmpToBounds); |
| if (!mTmpToBounds.equals(mTmpFromBounds)) { |
| animateResizePinnedStack(mTmpToBounds, null /* sourceHintBounds */, |
| -1 /* duration */, false /* fromFullscreen */); |
| } |
| pinnedStackController.setAspectRatio( |
| pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio) |
| ? aspectRatio : -1f); |
| } |
| |
| /** |
| * Sets the current picture-in-picture actions. |
| */ |
| void setPictureInPictureActions(List<RemoteAction> actions) { |
| if (!mWmService.mSupportsPictureInPicture) { |
| return; |
| } |
| |
| if (!inPinnedWindowingMode()) { |
| return; |
| } |
| |
| getDisplayContent().getPinnedStackController().setActions(actions); |
| } |
| |
| @Override |
| public boolean isAttached() { |
| synchronized (mWmService.mGlobalLock) { |
| return mDisplayContent != null; |
| } |
| } |
| |
| /** |
| * Called immediately prior to resizing the tasks at the end of the pinned stack animation. |
| */ |
| public void onPipAnimationEndResize() { |
| synchronized (mWmService.mGlobalLock) { |
| mBoundsAnimating = false; |
| for (int i = 0; i < mChildren.size(); i++) { |
| final Task t = mChildren.get(i); |
| t.clearPreserveNonFloatingState(); |
| } |
| mWmService.requestTraversal(); |
| } |
| } |
| |
| @Override |
| public boolean shouldDeferStartOnMoveToFullscreen() { |
| synchronized (mWmService.mGlobalLock) { |
| if (!isAttached()) { |
| // Unnecessary to pause the animation because the stack is detached. |
| return false; |
| } |
| |
| // Workaround for the recents animation -- normally we need to wait for the new activity |
| // to show before starting the PiP animation, but because we start and show the home |
| // activity early for the recents animation prior to the PiP animation starting, there |
| // is no subsequent all-drawn signal. In this case, we can skip the pause when the home |
| // stack is already visible and drawn. |
| final TaskStack homeStack = mDisplayContent.getHomeStack(); |
| if (homeStack == null) { |
| return true; |
| } |
| final Task homeTask = homeStack.getTopChild(); |
| if (homeTask == null) { |
| return true; |
| } |
| final AppWindowToken homeApp = homeTask.getTopVisibleAppToken(); |
| if (!homeTask.isVisible() || homeApp == null) { |
| return true; |
| } |
| return !homeApp.allDrawn; |
| } |
| } |
| |
| /** |
| * @return True if we are currently animating the pinned stack from fullscreen to non-fullscreen |
| * bounds and we have a deferred PiP mode changed callback set with the animation. |
| */ |
| public boolean deferScheduleMultiWindowModeChanged() { |
| if (inPinnedWindowingMode()) { |
| return (mBoundsAnimatingRequested || mBoundsAnimating); |
| } |
| return false; |
| } |
| |
| public boolean isForceScaled() { |
| return mBoundsAnimating; |
| } |
| |
| public boolean isAnimatingBounds() { |
| return mBoundsAnimating; |
| } |
| |
| public boolean lastAnimatingBoundsWasToFullscreen() { |
| return mBoundsAnimatingToFullscreen; |
| } |
| |
| public boolean isAnimatingBoundsToFullscreen() { |
| return isAnimatingBounds() && lastAnimatingBoundsWasToFullscreen(); |
| } |
| |
| public boolean pinnedStackResizeDisallowed() { |
| if (mBoundsAnimating && mCancelCurrentBoundsAnimation) { |
| return true; |
| } |
| return false; |
| } |
| |
| /** Returns true if a removal action is still being deferred. */ |
| boolean checkCompleteDeferredRemoval() { |
| if (isSelfOrChildAnimating()) { |
| return true; |
| } |
| if (mDeferRemoval) { |
| removeImmediately(); |
| } |
| |
| return super.checkCompleteDeferredRemoval(); |
| } |
| |
| @Override |
| int getOrientation() { |
| return (canSpecifyOrientation()) ? super.getOrientation() : SCREEN_ORIENTATION_UNSET; |
| } |
| |
| private boolean canSpecifyOrientation() { |
| final int windowingMode = getWindowingMode(); |
| final int activityType = getActivityType(); |
| return windowingMode == WINDOWING_MODE_FULLSCREEN |
| || activityType == ACTIVITY_TYPE_HOME |
| || activityType == ACTIVITY_TYPE_RECENTS |
| || activityType == ACTIVITY_TYPE_ASSISTANT; |
| } |
| |
| @Override |
| Dimmer getDimmer() { |
| return mDimmer; |
| } |
| |
| @Override |
| void prepareSurfaces() { |
| mDimmer.resetDimStates(); |
| super.prepareSurfaces(); |
| getDimBounds(mTmpDimBoundsRect); |
| |
| // Bounds need to be relative, as the dim layer is a child. |
| mTmpDimBoundsRect.offsetTo(0, 0); |
| if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) { |
| scheduleAnimation(); |
| } |
| } |
| |
| @Override |
| public boolean setPinnedStackAlpha(float alpha) { |
| // Hold the lock since this is called from the BoundsAnimator running on the UiThread |
| synchronized (mWmService.mGlobalLock) { |
| if (mCancelCurrentBoundsAnimation) { |
| return false; |
| } |
| getPendingTransaction().setAlpha(getSurfaceControl(), alpha); |
| scheduleAnimation(); |
| } |
| |
| return true; |
| } |
| |
| public DisplayInfo getDisplayInfo() { |
| return mDisplayContent.getDisplayInfo(); |
| } |
| |
| void dim(float alpha) { |
| mDimmer.dimAbove(getPendingTransaction(), alpha); |
| scheduleAnimation(); |
| } |
| |
| void stopDimming() { |
| mDimmer.stopDim(getPendingTransaction()); |
| scheduleAnimation(); |
| } |
| |
| AnimatingAppWindowTokenRegistry getAnimatingAppWindowTokenRegistry() { |
| return mAnimatingAppWindowTokenRegistry; |
| } |
| } |