From e65d6bb2072471e63b93aa14a288bc59ed86208f Mon Sep 17 00:00:00 2001 From: Jorim Jaggi Date: Thu, 31 Dec 2015 10:42:27 +0100 Subject: [PATCH] Snap docked stack after screen rotation - Move DividerSnapAlgorithm to com.android.internal, also move some utility stuff into DividerUtils which is used from both SystemUI and window manager - When the screen rotation changes, rotate the stacks like before but then also snap the docked stack to a valid snap position. Change-Id: Ifb0c65dfbdfca2343a76b12de982c0701fe0c3ab --- .../android/view/WindowManagerPolicy.java | 11 +++ .../policy}/DividerSnapAlgorithm.java | 32 +++++--- .../internal/policy/DockedDividerUtils.java | 74 ++++++++++++++++++ .../systemui/stackdivider/DividerView.java | 44 +++-------- .../phone/NavigationBarGestureHelper.java | 3 +- .../server/policy/PhoneWindowManager.java | 22 +++++- .../java/com/android/server/wm/TaskStack.java | 75 ++++++++++++++++--- .../server/wm/WindowManagerService.java | 11 +++ 8 files changed, 214 insertions(+), 58 deletions(-) rename {packages/SystemUI/src/com/android/systemui/stackdivider => core/java/com/android/internal/policy}/DividerSnapAlgorithm.java (91%) create mode 100644 core/java/com/android/internal/policy/DockedDividerUtils.java diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index ecec25852cbb7..a78b56ab194c0 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -1328,4 +1328,15 @@ public interface WindowManagerPolicy { * @param fadeoutDuration the duration of the exit animation, in milliseconds */ public void startKeyguardExitAnimation(long startTime, long fadeoutDuration); + + /** + * Calculates the stable insets without running a layout. + * + * @param displayRotation the current display rotation + * @param outInsets the insets to return + * @param displayWidth the current display width + * @param displayHeight the current display height + */ + public void getStableInsetsLw(int displayRotation, int displayWidth, int displayHeight, + Rect outInsets); } diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java similarity index 91% rename from packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java rename to core/java/com/android/internal/policy/DividerSnapAlgorithm.java index e43d531c7a5c5..e79f1b8a31336 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java +++ b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java @@ -14,18 +14,19 @@ * limitations under the License. */ -package com.android.systemui.stackdivider; +package com.android.internal.policy; import android.content.Context; +import android.content.res.Resources; import android.graphics.Rect; -import com.android.systemui.statusbar.FlingAnimationUtils; - import java.util.ArrayList; /** * Calculates the snap targets and the snap position given a position and a velocity. All positions * here are to be interpreted as the left/top edge of the divider rectangle. + * + * @hide */ public class DividerSnapAlgorithm { @@ -44,8 +45,7 @@ public class DividerSnapAlgorithm { */ private static final int SNAP_ONLY_1_1 = 2; - private final Context mContext; - private final FlingAnimationUtils mFlingAnimationUtils; + private final float mMinFlingVelocityPxPerSecond; private final int mDisplayWidth; private final int mDisplayHeight; private final int mDividerSize; @@ -63,18 +63,17 @@ public class DividerSnapAlgorithm { private final SnapTarget mDismissStartTarget; private final SnapTarget mDismissEndTarget; - public DividerSnapAlgorithm(Context ctx, FlingAnimationUtils flingAnimationUtils, + public DividerSnapAlgorithm(Resources res, float minFlingVelocityPxPerSecond, int displayWidth, int displayHeight, int dividerSize, boolean isHorizontalDivision, Rect insets) { - mContext = ctx; - mFlingAnimationUtils = flingAnimationUtils; + mMinFlingVelocityPxPerSecond = minFlingVelocityPxPerSecond; mDividerSize = dividerSize; mDisplayWidth = displayWidth; mDisplayHeight = displayHeight; mInsets.set(insets); - mSnapMode = ctx.getResources().getInteger( + mSnapMode = res.getInteger( com.android.internal.R.integer.config_dockedStackDividerSnapMode); - mFixedRatio = ctx.getResources().getFraction( + mFixedRatio = res.getFraction( com.android.internal.R.fraction.docked_stack_divider_fixed_ratio, 1, 1); calculateTargets(isHorizontalDivision); mFirstSplitTarget = mTargets.get(1); @@ -84,7 +83,7 @@ public class DividerSnapAlgorithm { } public SnapTarget calculateSnapTarget(int position, float velocity) { - if (Math.abs(velocity) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) { + if (Math.abs(velocity) < mMinFlingVelocityPxPerSecond) { return snap(position); } if (position < mFirstSplitTarget.position && velocity < 0) { @@ -100,6 +99,17 @@ public class DividerSnapAlgorithm { } } + public SnapTarget calculateNonDismissingSnapTarget(int position) { + SnapTarget target = snap(position); + if (target == mDismissStartTarget) { + return mFirstSplitTarget; + } else if (target == mDismissEndTarget) { + return mLastSplitTarget; + } else { + return target; + } + } + public float calculateDismissingFraction(int position) { if (position < mFirstSplitTarget.position) { return 1f - (float) position / mFirstSplitTarget.position; diff --git a/core/java/com/android/internal/policy/DockedDividerUtils.java b/core/java/com/android/internal/policy/DockedDividerUtils.java new file mode 100644 index 0000000000000..25a060e0a0a71 --- /dev/null +++ b/core/java/com/android/internal/policy/DockedDividerUtils.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2015 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.internal.policy; + +import android.graphics.Rect; +import android.view.WindowManager; + +/** + * Utility functions for docked stack divider used by both window manager and System UI. + * + * @hide + */ +public class DockedDividerUtils { + + public static void calculateBoundsForPosition(int position, int dockSide, Rect outRect, + int displayWidth, int displayHeight, int dividerSize) { + outRect.set(0, 0, displayWidth, displayHeight); + switch (dockSide) { + case WindowManager.DOCKED_LEFT: + outRect.right = position; + break; + case WindowManager.DOCKED_TOP: + outRect.bottom = position; + break; + case WindowManager.DOCKED_RIGHT: + outRect.left = position + dividerSize; + break; + case WindowManager.DOCKED_BOTTOM: + outRect.top = position + dividerSize; + break; + } + if (outRect.left > outRect.right) { + outRect.left = outRect.right; + } + if (outRect.top > outRect.bottom) { + outRect.top = outRect.bottom; + } + if (outRect.right < outRect.left) { + outRect.right = outRect.left; + } + if (outRect.bottom < outRect.top) { + outRect.bottom = outRect.top; + } + } + + public static int calculatePositionForBounds(Rect bounds, int dockSide, int dividerSize) { + switch (dockSide) { + case WindowManager.DOCKED_LEFT: + return bounds.right; + case WindowManager.DOCKED_TOP: + return bounds.bottom; + case WindowManager.DOCKED_RIGHT: + return bounds.left - dividerSize; + case WindowManager.DOCKED_BOTTOM: + return bounds.top - dividerSize; + default: + return 0; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java index 109cf4726101e..824d10a3aa19b 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java @@ -28,7 +28,6 @@ import android.graphics.Rect; import android.graphics.Region.Op; import android.hardware.display.DisplayManager; import android.util.AttributeSet; -import android.util.MathUtils; import android.view.Display; import android.view.DisplayInfo; import android.view.MotionEvent; @@ -39,7 +38,6 @@ import android.view.View.OnTouchListener; import android.view.ViewConfiguration; import android.view.ViewTreeObserver.InternalInsetsInfo; import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; -import android.view.Window; import android.view.WindowInsets; import android.view.WindowManager; import android.view.animation.AnimationUtils; @@ -48,8 +46,10 @@ import android.view.animation.PathInterpolator; import android.widget.FrameLayout; import android.widget.ImageButton; +import com.android.internal.policy.DividerSnapAlgorithm; +import com.android.internal.policy.DockedDividerUtils; import com.android.systemui.R; -import com.android.systemui.stackdivider.DividerSnapAlgorithm.SnapTarget; +import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget; import com.android.systemui.statusbar.FlingAnimationUtils; import static android.view.PointerIcon.STYLE_HORIZONTAL_DOUBLE_ARROW; @@ -167,7 +167,8 @@ public class DividerView extends FrameLayout implements OnTouchListener, public boolean startDragging(boolean animate) { mHandle.setTouching(true, animate); mDockSide = mWindowManagerProxy.getDockSide(); - mSnapAlgorithm = new DividerSnapAlgorithm(getContext(), mFlingAnimationUtils, mDisplayWidth, + mSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(), + mFlingAnimationUtils.getMinVelocityPxPerSecond(), mDisplayWidth, mDisplayHeight, mDividerSize, isHorizontalDivision(), mStableInsets); if (mDockSide != WindowManager.DOCKED_INVALID) { mWindowManagerProxy.setResizing(true); @@ -362,36 +363,6 @@ public class DividerView extends FrameLayout implements OnTouchListener, return mStartPosition + touchY - mStartY; } - public void calculateBoundsForPosition(int position, int dockSide, Rect outRect) { - outRect.set(0, 0, mDisplayWidth, mDisplayHeight); - switch (dockSide) { - case WindowManager.DOCKED_LEFT: - outRect.right = position; - break; - case WindowManager.DOCKED_TOP: - outRect.bottom = position; - break; - case WindowManager.DOCKED_RIGHT: - outRect.left = position + mDividerWindowWidth - 2 * mDividerInsets; - break; - case WindowManager.DOCKED_BOTTOM: - outRect.top = position + mDividerWindowWidth - 2 * mDividerInsets; - break; - } - if (outRect.left > outRect.right) { - outRect.left = outRect.right; - } - if (outRect.top > outRect.bottom) { - outRect.top = outRect.bottom; - } - if (outRect.right < outRect.left) { - outRect.right = outRect.left; - } - if (outRect.bottom < outRect.top) { - outRect.bottom = outRect.top; - } - } - private int invertDockSide(int dockSide) { switch (dockSide) { case WindowManager.DOCKED_LEFT: @@ -421,6 +392,11 @@ public class DividerView extends FrameLayout implements OnTouchListener, containingRect.right, containingRect.bottom); } + public void calculateBoundsForPosition(int position, int dockSide, Rect outRect) { + DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outRect, mDisplayWidth, + mDisplayHeight, mDividerSize); + } + public void resizeStack(int position, int taskPosition, SnapTarget taskSnapTarget) { calculateBoundsForPosition(position, mDockSide, mDockedRect); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java index cc85d0fb8a259..b1869e91dab9e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java @@ -29,8 +29,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.RecentsComponent; import com.android.systemui.stackdivider.Divider; -import com.android.systemui.stackdivider.DividerSnapAlgorithm.SnapTarget; -import com.android.systemui.stackdivider.DividerView; +import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget; import com.android.systemui.tuner.TunerService; import static android.view.WindowManager.*; diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index f13d964e1e1b9..cd0798ad836a0 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -3788,7 +3788,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // size. We need to do this directly, instead of relying on // it to bubble up from the nav bar, because this needs to // change atomically with screen rotations. - mNavigationBarOnBottom = (!mNavigationBarCanMove || displayWidth < displayHeight); + mNavigationBarOnBottom = isNavigationBarOnBottom(displayWidth, displayHeight); if (mNavigationBarOnBottom) { // It's a system nav bar or a portrait screen; nav bar goes on bottom. int top = displayHeight - overscanBottom @@ -3859,6 +3859,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { return false; } + private boolean isNavigationBarOnBottom(int displayWidth, int displayHeight) { + return !mNavigationBarCanMove || displayWidth < displayHeight; + } + /** {@inheritDoc} */ @Override public int getSystemDecorLayerLw() { @@ -5931,6 +5935,22 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + @Override + public void getStableInsetsLw(int displayRotation, int displayWidth, int displayHeight, + Rect outInsets) { + outInsets.setEmpty(); + if (mStatusBar != null) { + outInsets.top = mStatusBarHeight; + } + if (mNavigationBar != null) { + if (isNavigationBarOnBottom(displayWidth, displayHeight)) { + outInsets.bottom = mNavigationBarHeightForRotation[displayRotation]; + } else { + outInsets.right = mNavigationBarWidthForRotation[displayRotation]; + } + } + } + void sendCloseSystemWindows() { PhoneWindow.sendCloseSystemWindows(mContext, null); } diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index fc6ad70513b13..62a7f2503a4bd 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -18,6 +18,7 @@ package com.android.server.wm; import android.app.ActivityManager.StackId; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Rect; import android.os.Debug; import android.util.EventLog; @@ -26,6 +27,9 @@ import android.util.SparseArray; import android.view.DisplayInfo; import android.view.Surface; +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; @@ -245,19 +249,66 @@ public class TaskStack implements DimLayer.DimLayerUser { setBounds(null); } else { mTmpRect2.set(mBounds); - mDisplayContent.rotateBounds( - mRotation, mDisplayContent.getDisplayInfo().rotation, mTmpRect2); - if (setBounds(mTmpRect2)) { - // Post message to inform activity manager of the bounds change simulating - // a one-way call. We do this to prevent a deadlock between window manager - // lock and activity manager lock been held. - mService.mH.sendMessage(mService.mH.obtainMessage( - RESIZE_STACK, mStackId, 0 /*allowResizeInDockedMode*/, mBounds)); + int newRotation = mDisplayContent.getDisplayInfo().rotation; + if (mRotation == newRotation) { + setBounds(mTmpRect2); } + + // If the rotation changes, we'll handle it in updateBoundsAfterRotation } } } + /** + * Updates the bounds after rotating the screen. We can't handle it in + * {@link #updateDisplayInfo} because at that point the configuration might not be fully updated + * yet. + */ + void updateBoundsAfterRotation() { + final int newRotation = getDisplayInfo().rotation; + mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2); + if (mStackId == DOCKED_STACK_ID) { + snapDockedStackAfterRotation(mTmpRect2); + } + + // Post message to inform activity manager of the bounds change simulating + // a one-way call. We do this to prevent a deadlock between window manager + // lock and activity manager lock been held. + mService.mH.sendMessage(mService.mH.obtainMessage( + RESIZE_STACK, mStackId, 0 /*allowResizeInDockedMode*/, mTmpRect2)); + } + + /** + * Snaps the bounds after rotation to the closest snap target for the docked stack. + */ + private void snapDockedStackAfterRotation(Rect outBounds) { + + // Calculate the current position. + final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo(); + final int dividerSize = mService.getDefaultDisplayContentLocked() + .getDockedDividerController().getContentWidth(); + final int dockSide = getDockSide(outBounds); + final int dividerPosition = DockedDividerUtils.calculatePositionForBounds(outBounds, + dockSide, dividerSize); + final int displayWidth = mDisplayContent.getDisplayInfo().logicalWidth; + final int displayHeight = mDisplayContent.getDisplayInfo().logicalHeight; + + // Snap the position to a target. + final int rotation = displayInfo.rotation; + final int orientation = mService.mCurConfiguration.orientation; + mService.mPolicy.getStableInsetsLw(rotation, displayWidth, displayHeight, outBounds); + final DividerSnapAlgorithm algorithm = new DividerSnapAlgorithm( + mService.mContext.getResources(), + 0 /* minFlingVelocityPxPerSecond */, displayWidth, displayHeight, + dividerSize, orientation == Configuration.ORIENTATION_PORTRAIT, outBounds); + final SnapTarget target = algorithm.calculateNonDismissingSnapTarget(dividerPosition); + + // Recalculate the bounds based on the position of the target. + DockedDividerUtils.calculateBoundsForPosition(target.position, dockSide, + outBounds, displayInfo.logicalWidth, displayInfo.logicalHeight, + dividerSize); + } + boolean isAnimating() { for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) { final ArrayList activities = mTasks.get(taskNdx).mAppTokens; @@ -682,6 +733,10 @@ public class TaskStack implements DimLayer.DimLayerUser { * information which side of the screen was the dock anchored. */ int getDockSide() { + return getDockSide(mBounds); + } + + int getDockSide(Rect bounds) { if (mStackId != DOCKED_STACK_ID && !StackId.isResizeableByDockedStack(mStackId)) { return DOCKED_INVALID; } @@ -692,14 +747,14 @@ public class TaskStack implements DimLayer.DimLayerUser { final int orientation = mService.mCurConfiguration.orientation; if (orientation == Configuration.ORIENTATION_PORTRAIT) { // Portrait mode, docked either at the top or the bottom. - if (mBounds.top - mTmpRect.top < mTmpRect.bottom - mBounds.bottom) { + if (bounds.top - mTmpRect.top < mTmpRect.bottom - bounds.bottom) { return DOCKED_TOP; } else { return DOCKED_BOTTOM; } } else if (orientation == Configuration.ORIENTATION_LANDSCAPE) { // Landscape mode, docked either on the left or on the right. - if (mBounds.left - mTmpRect.left < mTmpRect.right - mBounds.right) { + if (bounds.left - mTmpRect.left < mTmpRect.right - bounds.right) { return DOCKED_LEFT; } else { return DOCKED_RIGHT; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index f858abe9b031e..64e810b010bbe 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -3523,6 +3523,7 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override public void setNewConfiguration(Configuration config) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "setNewConfiguration()")) { @@ -3536,10 +3537,20 @@ public class WindowManagerService extends IWindowManager.Stub mWaitingForConfig = false; mLastFinishedFreezeSource = "new-config"; } + if (orientationChanged) { + updateTaskStackBoundsAfterRotation(); + } mWindowPlacerLocked.performSurfacePlacement(); } } + private void updateTaskStackBoundsAfterRotation() { + for (int stackNdx = mStackIdToStack.size() - 1; stackNdx >= 0; stackNdx--) { + final TaskStack stack = mStackIdToStack.valueAt(stackNdx); + stack.updateBoundsAfterRotation(); + } + } + @Override public void setAppOrientation(IApplicationToken token, int requestedOrientation) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,