From 1fcbab6ae5c99acab70eacc015d194e2c6ddd4e2 Mon Sep 17 00:00:00 2001 From: Jorim Jaggi Date: Wed, 4 Nov 2015 16:39:50 +0100 Subject: [PATCH] Implement divider UX interactions - Update visuals to spec - Divider lifts when touching - Implement basic version of snap points and animations - Implement touch conflict behavior: If touched around 48x48dp area around the handle, the divider handles all these touches. If touched outside of the black background divider, touch goes directly to underlying window. If touch on the black background divider, touch is considered slippery and thus the window in which the touch trace moves gets the touches. Change-Id: I0307c191ae032672c4b73d439c23cf9833d3fce6 --- core/res/res/values/dimens.xml | 8 +- core/res/res/values/symbols.xml | 2 +- .../res/drawable/docked_divider_handle.xml | 22 ++ .../res/layout/docked_stack_divider.xml | 22 +- packages/SystemUI/res/values-land/dimens.xml | 3 + packages/SystemUI/res/values-land/styles.xml | 11 + packages/SystemUI/res/values/colors.xml | 3 + packages/SystemUI/res/values/dimens.xml | 9 +- packages/SystemUI/res/values/styles.xml | 10 + .../android/systemui/SystemUIApplication.java | 4 +- .../systemui/stackdivider/Divider.java | 68 ++++ .../stackdivider/DividerSnapAlgorithm.java | 130 ++++++++ .../systemui/stackdivider/DividerView.java | 311 ++++++++++++++++++ .../stackdivider/DividerWindowManager.java | 77 +++++ .../stackdivider/DockedStackDivider.java | 294 ----------------- .../stackdivider/WindowManagerProxy.java | 99 ++++++ .../wm/DockedStackDividerController.java | 28 +- .../com/android/server/wm/InputMonitor.java | 14 - .../java/com/android/server/wm/TaskStack.java | 6 +- 19 files changed, 785 insertions(+), 336 deletions(-) create mode 100644 packages/SystemUI/res/drawable/docked_divider_handle.xml create mode 100644 packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java create mode 100644 packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java create mode 100644 packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java create mode 100644 packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java delete mode 100644 packages/SystemUI/src/com/android/systemui/stackdivider/DockedStackDivider.java create mode 100644 packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index bfb0d103d7f2d..28756f5d03843 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -45,8 +45,12 @@ 5dp - - 24dp + + 48dp + + + 18dp 800dp diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 5c65366b41b86..cda7faa1f09bb 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1481,6 +1481,7 @@ + @@ -1724,7 +1725,6 @@ - diff --git a/packages/SystemUI/res/drawable/docked_divider_handle.xml b/packages/SystemUI/res/drawable/docked_divider_handle.xml new file mode 100644 index 0000000000000..ea32548b78cc7 --- /dev/null +++ b/packages/SystemUI/res/drawable/docked_divider_handle.xml @@ -0,0 +1,22 @@ + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/layout/docked_stack_divider.xml b/packages/SystemUI/res/layout/docked_stack_divider.xml index 2f840ca0a624d..5f42856b59290 100644 --- a/packages/SystemUI/res/layout/docked_stack_divider.xml +++ b/packages/SystemUI/res/layout/docked_stack_divider.xml @@ -14,8 +14,22 @@ limitations under the License. --> - + android:layout_width="match_parent"> + + + + + + diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml index 6ef1adac3e024..456391d612169 100644 --- a/packages/SystemUI/res/values-land/dimens.xml +++ b/packages/SystemUI/res/values-land/dimens.xml @@ -22,4 +22,7 @@ @dimen/standard_notification_panel_width @integer/standard_notification_panel_layout_gravity + + 2dp + 24dp diff --git a/packages/SystemUI/res/values-land/styles.xml b/packages/SystemUI/res/values-land/styles.xml index 89191984b9e8f..dd396d9216385 100644 --- a/packages/SystemUI/res/values-land/styles.xml +++ b/packages/SystemUI/res/values-land/styles.xml @@ -18,4 +18,15 @@ + + + + + diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index f40f0d9aecdb7..26a0577327e47 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -143,4 +143,7 @@ #1fffffff #ff009688 + + #ff000000 + #ffffff diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 7f2c8513aef1f..73f63a968c29d 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -578,7 +578,10 @@ -8dp - - 100dp + + 6dp + + 24dp + 2dp + diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 9e942f0b6904d..4462a03e0ad3a 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -304,4 +304,14 @@ @drawable/btn_borderless_rect + + + + diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index f8a80f0afd7be..949efc588dd9b 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -27,7 +27,7 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.util.Log; -import com.android.systemui.stackdivider.DockedStackDivider; +import com.android.systemui.stackdivider.Divider; import java.util.HashMap; import java.util.Map; @@ -53,7 +53,7 @@ public class SystemUIApplication extends Application { com.android.systemui.power.PowerUI.class, com.android.systemui.media.RingtonePlayer.class, com.android.systemui.keyboard.KeyboardUI.class, - DockedStackDivider.class + Divider.class }; /** diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java new file mode 100644 index 0000000000000..dd894ce418a32 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java @@ -0,0 +1,68 @@ +/* + * 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.systemui.stackdivider; + +import android.content.res.Configuration; +import android.view.LayoutInflater; + +import com.android.systemui.R; +import com.android.systemui.SystemUI; + +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; + +/** + * Controls the docked stack divider. + */ +public class Divider extends SystemUI { + private static final String TAG = "Divider"; + private int mDividerWindowWidth; + private DividerWindowManager mWindowManager; + + @Override + public void start() { + mWindowManager = new DividerWindowManager(mContext); + mDividerWindowWidth = mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.docked_stack_divider_thickness); + update(mContext.getResources().getConfiguration()); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + update(newConfig); + } + + private void addDivider(Configuration configuration) { + DividerView view = (DividerView) + LayoutInflater.from(mContext).inflate(R.layout.docked_stack_divider, null); + final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE; + final int width = landscape ? mDividerWindowWidth : MATCH_PARENT; + final int height = landscape ? MATCH_PARENT : mDividerWindowWidth; + mWindowManager.add(view, width, height); + view.setWindowManager(mWindowManager); + } + + private void removeDivider() { + mWindowManager.remove(); + } + + private void update(Configuration configuration) { + removeDivider(); + addDivider(configuration); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java new file mode 100644 index 0000000000000..5f983c5435e19 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java @@ -0,0 +1,130 @@ +/* + * 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.systemui.stackdivider; + +import android.content.Context; +import android.util.DisplayMetrics; +import android.view.DisplayInfo; + +import com.android.systemui.statusbar.FlingAnimationUtils; + +import java.util.ArrayList; + +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; + +/** + * 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. + */ +public class DividerSnapAlgorithm { + + private final Context mContext; + private final FlingAnimationUtils mFlingAnimationUtils; + private final int mDividerSize; + private final ArrayList mTargets; + + /** The first target which is still splitting the screen */ + private final SnapTarget mFirstSplitTarget; + + /** The last target which is still splitting the screen */ + private final SnapTarget mLastSplitTarget; + + private final SnapTarget mDismissStartTarget; + private final SnapTarget mDismissEndTarget; + + public DividerSnapAlgorithm(Context ctx, FlingAnimationUtils flingAnimationUtils, + int dividerSize, boolean isHorizontalDivision) { + mContext = ctx; + mFlingAnimationUtils = flingAnimationUtils; + mDividerSize = dividerSize; + mTargets = calculateTargets(isHorizontalDivision); + mFirstSplitTarget = mTargets.get(1); + mLastSplitTarget = mTargets.get(mTargets.size() - 2); + mDismissStartTarget = mTargets.get(0); + mDismissEndTarget = mTargets.get(mTargets.size() - 1); + } + + public SnapTarget calculateSnapTarget(int position, float velocity) { + if (Math.abs(velocity) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) { + return snap(position); + } + if (position < mFirstSplitTarget.position && velocity < 0) { + return mDismissStartTarget; + } + if (position > mLastSplitTarget.position && velocity > 0) { + return mDismissEndTarget; + } + if (velocity < 0) { + return mFirstSplitTarget; + } else { + return mLastSplitTarget; + } + } + + private SnapTarget snap(int position) { + int minIndex = -1; + int minDistance = Integer.MAX_VALUE; + int size = mTargets.size(); + for (int i = 0; i < size; i++) { + int distance = Math.abs(position - mTargets.get(i).position); + if (distance < minDistance) { + minIndex = i; + minDistance = distance; + } + } + return mTargets.get(minIndex); + } + + private ArrayList calculateTargets(boolean isHorizontalDivision) { + ArrayList targets = new ArrayList<>(); + DisplayMetrics info = mContext.getResources().getDisplayMetrics(); + int dividerMax = isHorizontalDivision + ? info.heightPixels + : info.widthPixels; + + // TODO: Better calculation + targets.add(new SnapTarget(-mDividerSize, SnapTarget.FLAG_DISMISS_START)); + targets.add(new SnapTarget((int) (0.35f * dividerMax) - mDividerSize / 2, + SnapTarget.FLAG_NONE)); + targets.add(new SnapTarget(dividerMax / 2 - mDividerSize / 2, SnapTarget.FLAG_NONE)); + targets.add(new SnapTarget((int) (0.65f * dividerMax) - mDividerSize / 2, + SnapTarget.FLAG_NONE)); + targets.add(new SnapTarget(dividerMax, SnapTarget.FLAG_DISMISS_END)); + return targets; + } + + /** + * Represents a snap target for the divider. + */ + public static class SnapTarget { + public static final int FLAG_NONE = 0; + + /** If the divider reaches this value, the left/top task should be dismissed. */ + public static final int FLAG_DISMISS_START = 1; + + /** If the divider reaches this value, the right/bottom task should be dismissed */ + public static final int FLAG_DISMISS_END = 2; + + public final int position; + public final int flag; + + public SnapTarget(int position, int flag) { + this.position = position; + this.flag = flag; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java new file mode 100644 index 0000000000000..14d8fefd8d7e3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java @@ -0,0 +1,311 @@ +/* + * 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.systemui.stackdivider; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.graphics.Region.Op; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.View.OnTouchListener; +import android.view.ViewTreeObserver.InternalInsetsInfo; +import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; +import android.view.WindowManager; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; +import android.view.animation.PathInterpolator; +import android.widget.FrameLayout; +import android.widget.ImageButton; + +import com.android.systemui.R; +import com.android.systemui.stackdivider.DividerSnapAlgorithm.SnapTarget; +import com.android.systemui.statusbar.FlingAnimationUtils; + +/** + * Docked stack divider. + */ +public class DividerView extends FrameLayout implements OnTouchListener, + OnComputeInternalInsetsListener { + + private static final String TAG = "DividerView"; + + private ImageButton mHandle; + private View mBackground; + private int mStartX; + private int mStartY; + private int mStartPosition; + private int mDockSide; + private final int[] mTempInt2 = new int[2]; + + private int mDividerInsets; + private int mDisplayWidth; + private int mDisplayHeight; + private int mDividerWindowWidth; + private int mDividerSize; + private int mTouchElevation; + + private final Rect mTmpRect = new Rect(); + private final Rect mLastResizeRect = new Rect(); + private final WindowManagerProxy mWindowManagerProxy = new WindowManagerProxy(); + private Interpolator mFastOutSlowInInterpolator; + private final Interpolator mTouchResponseInterpolator = + new PathInterpolator(0.3f, 0f, 0.1f, 1f); + private DividerWindowManager mWindowManager; + private VelocityTracker mVelocityTracker; + private FlingAnimationUtils mFlingAnimationUtils; + + public DividerView(Context context) { + super(context); + } + + public DividerView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mHandle = (ImageButton) findViewById(R.id.docked_divider_handle); + mBackground = findViewById(R.id.docked_divider_background); + mHandle.setOnTouchListener(this); + mDividerWindowWidth = getResources().getDimensionPixelSize( + com.android.internal.R.dimen.docked_stack_divider_thickness); + mDividerInsets = getResources().getDimensionPixelSize( + com.android.internal.R.dimen.docked_stack_divider_insets); + mDividerSize = mDividerWindowWidth - 2 * mDividerInsets; + mTouchElevation = getResources().getDimensionPixelSize( + R.dimen.docked_stack_divider_lift_elevation); + mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(), + android.R.interpolator.fast_out_slow_in); + mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.3f); + updateDisplayInfo(); + getViewTreeObserver().addOnComputeInternalInsetsListener(this); + } + + public void setWindowManager(DividerWindowManager windowManager) { + mWindowManager = windowManager; + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + convertToScreenCoordinates(event); + final int action = event.getAction() & MotionEvent.ACTION_MASK; + switch (action) { + case MotionEvent.ACTION_DOWN: + mVelocityTracker = VelocityTracker.obtain(); + mVelocityTracker.addMovement(event); + mStartX = (int) event.getX(); + mStartY = (int) event.getY(); + getLocationOnScreen(mTempInt2); + mDockSide = mWindowManagerProxy.getDockSide(); + if (isHorizontalDivision()) { + mStartPosition = mTempInt2[1] + mDividerInsets; + } else { + mStartPosition = mTempInt2[0] + mDividerInsets; + } + if (mDockSide != WindowManager.DOCKED_INVALID) { + mWindowManagerProxy.setResizing(true); + mWindowManager.setSlippery(false); + liftBackground(); + return true; + } else { + return false; + } + case MotionEvent.ACTION_MOVE: + mVelocityTracker.addMovement(event); + int x = (int) event.getX(); + int y = (int) event.getY(); + if (mDockSide != WindowManager.DOCKED_INVALID) { + resizeStack(calculatePosition(x, y)); + } + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mVelocityTracker.addMovement(event); + + x = (int) event.getRawX(); + y = (int) event.getRawY(); + + mVelocityTracker.computeCurrentVelocity(1000); + fling(x, y, mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); + + mWindowManager.setSlippery(true); + releaseBackground(); + break; + } + return true; + } + + private void convertToScreenCoordinates(MotionEvent event) { + event.setLocation(event.getRawX(), event.getRawY()); + } + + private void fling(int x, int y, float xVelocity, float yVelocity) { + int position = calculatePosition(x, y); + float velocity = isHorizontalDivision() ? yVelocity : xVelocity; + final SnapTarget snapTarget = new DividerSnapAlgorithm(getContext(), mFlingAnimationUtils, + mDividerSize, isHorizontalDivision()).calculateSnapTarget(position, velocity); + + ValueAnimator anim = ValueAnimator.ofInt(position, snapTarget.position); + anim.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + resizeStack((Integer) animation.getAnimatedValue()); + } + }); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + commitSnapFlags(snapTarget); + mWindowManagerProxy.setResizing(false); + mDockSide = WindowManager.DOCKED_INVALID; + } + }); + mFlingAnimationUtils.apply(anim, position, snapTarget.position, velocity); + anim.start(); + } + + private void commitSnapFlags(SnapTarget target) { + if (target.flag == SnapTarget.FLAG_NONE) { + return; + } + boolean dismissOrMaximize; + if (target.flag == SnapTarget.FLAG_DISMISS_START) { + dismissOrMaximize = mDockSide == WindowManager.DOCKED_LEFT + || mDockSide == WindowManager.DOCKED_TOP; + } else { + dismissOrMaximize = mDockSide == WindowManager.DOCKED_RIGHT + || mDockSide == WindowManager.DOCKED_BOTTOM; + } + if (dismissOrMaximize) { + mWindowManagerProxy.dismissDockedStack(); + } else { + mWindowManagerProxy.maximizeDockedStack(); + } + } + + private void liftBackground() { + if (isHorizontalDivision()) { + mBackground.animate().scaleY(1.5f); + } else { + mBackground.animate().scaleX(1.5f); + } + mBackground.animate() + .setInterpolator(mTouchResponseInterpolator) + .setDuration(150) + .translationZ(mTouchElevation); + + // Lift handle as well so it doesn't get behind the background, even though it doesn't + // cast shadow. + mHandle.animate() + .setInterpolator(mTouchResponseInterpolator) + .setDuration(150) + .translationZ(mTouchElevation); + } + + private void releaseBackground() { + mBackground.animate() + .setInterpolator(mFastOutSlowInInterpolator) + .setDuration(200) + .translationZ(0) + .scaleX(1f) + .scaleY(1f); + mHandle.animate() + .setInterpolator(mFastOutSlowInInterpolator) + .setDuration(200) + .translationZ(0); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + updateDisplayInfo(); + } + + private void updateDisplayInfo() { + DisplayMetrics info = mContext.getResources().getDisplayMetrics(); + mDisplayWidth = info.widthPixels; + mDisplayHeight = info.heightPixels; + } + + private int calculatePosition(int touchX, int touchY) { + return isHorizontalDivision() ? calculateYPosition(touchY) : calculateXPosition(touchX); + } + + private boolean isHorizontalDivision() { + return mDockSide == WindowManager.DOCKED_TOP + || mDockSide == WindowManager.DOCKED_BOTTOM; + } + + private int calculateXPosition(int touchX) { + return mStartPosition + touchX - mStartX; + } + + private int calculateYPosition(int touchY) { + return mStartPosition + touchY - mStartY; + } + + private void resizeStack(int position) { + mTmpRect.set(0, 0, mDisplayWidth, mDisplayHeight); + switch (mDockSide) { + case WindowManager.DOCKED_LEFT: + mTmpRect.right = position; + break; + case WindowManager.DOCKED_TOP: + mTmpRect.bottom = position; + break; + case WindowManager.DOCKED_RIGHT: + mTmpRect.left = position + mDividerWindowWidth - 2 * mDividerInsets; + break; + case WindowManager.DOCKED_BOTTOM: + mTmpRect.top = position + mDividerWindowWidth - 2 * mDividerInsets; + break; + } + if (mTmpRect.equals(mLastResizeRect)) { + return; + } + mLastResizeRect.set(mTmpRect); + mWindowManagerProxy.resizeDockedStack(mTmpRect); + } + + @Override + public void onComputeInternalInsets(InternalInsetsInfo inoutInfo) { + inoutInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + inoutInfo.touchableRegion.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(), + mHandle.getBottom()); + inoutInfo.touchableRegion.op(mBackground.getLeft(), mBackground.getTop(), + mBackground.getRight(), mBackground.getBottom(), Op.UNION); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java new file mode 100644 index 0000000000000..225187455f3f7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java @@ -0,0 +1,77 @@ +/* + * 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.systemui.stackdivider; + +import android.content.Context; +import android.graphics.PixelFormat; +import android.view.View; +import android.view.WindowManager; + +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; +import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY; +import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; +import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; +import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; + +/** + * Manages the window parameters of the docked stack divider. + */ +public class DividerWindowManager { + + private static final String WINDOW_TITLE = "DockedStackDivider"; + + private final WindowManager mWindowManager; + private WindowManager.LayoutParams mLp; + private View mView; + + public DividerWindowManager(Context ctx) { + mWindowManager = ctx.getSystemService(WindowManager.class); + } + + public void add(View view, int width, int height) { + mLp = new WindowManager.LayoutParams( + width, height, TYPE_DOCK_DIVIDER, + FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL + | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY, + PixelFormat.TRANSLUCENT); + mLp.setTitle(WINDOW_TITLE); + mWindowManager.addView(view, mLp); + mView = view; + } + + public void remove() { + if (mView != null) { + mWindowManager.removeView(mView); + } + mView = null; + } + + public void setSlippery(boolean slippery) { + boolean changed = false; + if (slippery && (mLp.flags & FLAG_SLIPPERY) == 0) { + mLp.flags |= FLAG_SLIPPERY; + changed = true; + } else if (!slippery && (mLp.flags & FLAG_SLIPPERY) != 0) { + mLp.flags &= ~FLAG_SLIPPERY; + changed = true; + } + if (changed) { + mWindowManager.updateViewLayout(mView, mLp); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DockedStackDivider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DockedStackDivider.java deleted file mode 100644 index 646ee475c798e..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DockedStackDivider.java +++ /dev/null @@ -1,294 +0,0 @@ -/* - * 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.systemui.stackdivider; - -import android.app.ActivityManagerNative; -import android.content.res.Configuration; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.os.RemoteException; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.WindowManager; -import android.view.WindowManagerGlobal; - -import com.android.internal.annotations.GuardedBy; -import com.android.systemui.R; -import com.android.systemui.SystemUI; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; -import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; -import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; -import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; -import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; -import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; - -/** - * Controls showing and hiding of a docked stack divider on the display. - */ -public class DockedStackDivider extends SystemUI implements View.OnTouchListener { - private static final String TAG = "DockedStackDivider"; - private int mDividerWidth; - private int mSideMargin; - private int mDisplayWidth; - private int mDisplayHeight; - private int mDisplayOrientation; - private View mView; - private final Rect mTmpRect = new Rect(); - private final Rect mTmpRectBackground = new Rect(); - private final Rect mLastResizeRect = new Rect(); - - @GuardedBy("mResizeRect") - private final Rect mResizeRect = new Rect(); - - private int mStartX; - private int mStartY; - private int mStartPosition; - private int mDockSide; - private WindowManager mWindowManager; - private final int[] mTempInt2 = new int[2]; - private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); - - private final Runnable mResizeRunnable = new Runnable() { - @Override - public void run() { - synchronized (mResizeRect) { - mTmpRectBackground.set(mResizeRect); - } - try { - ActivityManagerNative.getDefault().resizeStack(DOCKED_STACK_ID, - mTmpRectBackground, true); - } catch (RemoteException e) { - Log.w(TAG, "Failed to resize stack: " + e); - } - } - }; - - @Override - public void start() { - mWindowManager = mContext.getSystemService(WindowManager.class); - updateDisplayInfo(); - mDividerWidth = mContext.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.docked_stack_divider_thickness); - mSideMargin = mContext.getResources().getDimensionPixelSize( - R.dimen.docked_stack_divider_dismiss_distance); - update(mContext.getResources().getConfiguration()); - } - - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - updateDisplayInfo(); - update(newConfig); - } - - private void addDivider(Configuration configuration) { - View view = LayoutInflater.from(mContext).inflate(R.layout.docked_stack_divider, null); - view.setOnTouchListener(this); - final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE; - final int width = landscape ? mDividerWidth : MATCH_PARENT; - final int height = landscape ? MATCH_PARENT : mDividerWidth; - WindowManager.LayoutParams params = new WindowManager.LayoutParams( - width, height, TYPE_DOCK_DIVIDER, - FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL - | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH, - PixelFormat.TRANSLUCENT); - params.setTitle(TAG); - mWindowManager.addView(view, params); - mView = view; - } - - private void removeDivider() { - if (mView == null) return; - mView.setOnTouchListener(null); - mWindowManager.removeView(mView); - mView = null; - } - - - private void updateDisplayInfo() { - DisplayMetrics info = mContext.getResources().getDisplayMetrics(); - mDisplayWidth = info.widthPixels; - mDisplayHeight = info.heightPixels; - mDisplayOrientation = mContext.getResources().getConfiguration().orientation; - } - - private void update(Configuration configuration) { - removeDivider(); - addDivider(configuration); - } - - int getWidth() { - return mDividerWidth; - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - final int action = event.getAction() & MotionEvent.ACTION_MASK; - switch (action) { - case MotionEvent.ACTION_DOWN: - // We use raw values, because getX/Y() would give us results relative to the - // dock divider bounds. - mStartX = (int) event.getRawX(); - mStartY = (int) event.getRawY(); - mView.getLocationOnScreen(mTempInt2); - if (mDisplayOrientation == ORIENTATION_LANDSCAPE) { - mStartPosition = mTempInt2[0]; - } else { - mStartPosition = mTempInt2[1]; - } - mDockSide = getDockSide(); - if (mDockSide != WindowManager.DOCKED_INVALID) { - setResizing(true); - return true; - } else { - return false; - } - case MotionEvent.ACTION_MOVE: - int x = (int) event.getRawX(); - int y = (int) event.getRawY(); - if (mDockSide != WindowManager.DOCKED_INVALID) { - resizeStack(x, y); - } - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - x = (int) event.getRawX(); - y = (int) event.getRawY(); - // At most one of these will be executed, the other one will exit early. - maybeDismissTaskStack(x, y); - maybeMaximizeTaskStack(x, y); - mDockSide = WindowManager.DOCKED_INVALID; - setResizing(false); - break; - } - return true; - } - - private void setResizing(boolean resizing) { - try { - WindowManagerGlobal.getWindowManagerService().setDockedStackResizing(resizing); - } catch (RemoteException e) { - Log.w(TAG, "Error calling setDockedStackResizing: " + e); - } - } - - private int getDockSide() { - try { - return WindowManagerGlobal.getWindowManagerService().getDockedStackSide(); - } catch (RemoteException e) { - Log.w(TAG, "Failed to get dock side: " + e); - } - return WindowManager.DOCKED_INVALID; - } - - private void maybeMaximizeTaskStack(int x, int y) { - final int distance = distanceFromFullScreen(mDockSide, x, y); - if (distance == -1) { - Log.wtf(TAG, "maybeMaximizeTaskStack: Unknown dock side=" + mDockSide); - return; - } - if (distance <= mSideMargin) { - try { - ActivityManagerNative.getDefault().resizeStack( - DOCKED_STACK_ID, null, true); - } catch (RemoteException e) { - Log.w(TAG, "Failed to resize stack: " + e); - } - } - } - - private void maybeDismissTaskStack(int x, int y) { - final int distance = distanceFromDockSide(mDockSide, x, y); - if (distance == -1) { - Log.wtf(TAG, "maybeDismissTaskStack: Unknown dock side=" + mDockSide); - return; - } - if (distance <= mSideMargin) { - try { - ActivityManagerNative.getDefault().removeStack(DOCKED_STACK_ID); - } catch (RemoteException e) { - Log.w(TAG, "Failed to remove stack: " + e); - } - } - } - - private int distanceFromFullScreen(int dockSide, int x, int y) { - switch (dockSide) { - case WindowManager.DOCKED_LEFT: - return mDisplayWidth - x; - case WindowManager.DOCKED_TOP: - return mDisplayHeight - y; - case WindowManager.DOCKED_RIGHT: - return x; - case WindowManager.DOCKED_BOTTOM: - return y; - } - return -1; - } - - private int distanceFromDockSide(int dockSide, int x, int y) { - switch (dockSide) { - case WindowManager.DOCKED_LEFT: - return x; - case WindowManager.DOCKED_TOP: - return y; - case WindowManager.DOCKED_RIGHT: - return mDisplayWidth - x; - case WindowManager.DOCKED_BOTTOM: - return mDisplayHeight - y; - } - return -1; - } - - private void resizeStack(int x, int y) { - int deltaX = x - mStartX; - int deltaY = y - mStartY; - mTmpRect.set(0, 0, mDisplayWidth, mDisplayHeight); - switch (mDockSide) { - case WindowManager.DOCKED_LEFT: - mTmpRect.right = mStartPosition + deltaX; - break; - case WindowManager.DOCKED_TOP: - mTmpRect.bottom = mStartPosition + deltaY; - break; - case WindowManager.DOCKED_RIGHT: - mTmpRect.left = mStartPosition + deltaX + getWidth(); - break; - case WindowManager.DOCKED_BOTTOM: - mTmpRect.top = mStartPosition + deltaY + getWidth(); - break; - } - if (mTmpRect.equals(mLastResizeRect)) { - return; - } - mLastResizeRect.set(mTmpRect); - synchronized (mResizeRect) { - mResizeRect.set(mTmpRect); - } - mExecutor.execute(mResizeRunnable); - - } -} diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java new file mode 100644 index 0000000000000..94809bc664b1d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java @@ -0,0 +1,99 @@ +/* + * 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.systemui.stackdivider; + +import android.app.ActivityManagerNative; +import android.graphics.Rect; +import android.os.RemoteException; +import android.util.Log; +import android.view.WindowManagerGlobal; + +import com.android.internal.annotations.GuardedBy; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; +import static android.view.WindowManager.DOCKED_INVALID; + +/** + * Proxy to simplify calls into window manager/activity manager + */ +public class WindowManagerProxy { + + private static final String TAG = "WindowManagerProxy"; + + @GuardedBy("mResizeRect") + private final Rect mResizeRect = new Rect(); + private final Rect mTmpRect = new Rect(); + private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); + + private final Runnable mResizeRunnable = new Runnable() { + @Override + public void run() { + synchronized (mResizeRect) { + mTmpRect.set(mResizeRect); + } + try { + ActivityManagerNative.getDefault().resizeStack(DOCKED_STACK_ID, + mTmpRect, true); + } catch (RemoteException e) { + Log.w(TAG, "Failed to resize stack: " + e); + } + } + }; + + public void resizeDockedStack(Rect rect) { + synchronized (mResizeRect) { + mResizeRect.set(rect); + } + mExecutor.execute(mResizeRunnable); + } + + public void dismissDockedStack() { + try { + ActivityManagerNative.getDefault().removeStack(DOCKED_STACK_ID); + } catch (RemoteException e) { + Log.w(TAG, "Failed to remove stack: " + e); + } + } + + public void maximizeDockedStack() { + try { + ActivityManagerNative.getDefault().resizeStack(DOCKED_STACK_ID, null, true); + } catch (RemoteException e) { + Log.w(TAG, "Failed to resize stack: " + e); + } + } + + public void setResizing(boolean resizing) { + try { + WindowManagerGlobal.getWindowManagerService().setDockedStackResizing(resizing); + } catch (RemoteException e) { + Log.w(TAG, "Error calling setDockedStackResizing: " + e); + } + } + + public int getDockSide() { + try { + return WindowManagerGlobal.getWindowManagerService().getDockedStackSide(); + } catch (RemoteException e) { + Log.w(TAG, "Failed to get dock side: " + e); + } + return DOCKED_INVALID; + } +} diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java index 8d48dd8b4d746..6bdff1ec022a1 100644 --- a/services/core/java/com/android/server/wm/DockedStackDividerController.java +++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java @@ -32,23 +32,26 @@ public class DockedStackDividerController { private static final String TAG = "DockedStackDividerController"; private final DisplayContent mDisplayContent; - private final int mDividerWidth; + private final int mDividerWindowWidth; + private final int mDividerInsets; private boolean mResizing; private WindowState mWindow; private final Rect mTmpRect = new Rect(); DockedStackDividerController(Context context, DisplayContent displayContent) { mDisplayContent = displayContent; - mDividerWidth = context.getResources().getDimensionPixelSize( + mDividerWindowWidth = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.docked_stack_divider_thickness); + mDividerInsets = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.docked_stack_divider_insets); } boolean isResizing() { return mResizing; } - int getWidthAdjustment() { - return mDividerWidth; + int getContentWidth() { + return mDividerWindowWidth - 2 * mDividerInsets; } void setResizing(boolean resizing) { @@ -82,22 +85,21 @@ public class DockedStackDividerController { stack.getBounds(mTmpRect); switch (side) { case DOCKED_LEFT: - frame.set(mTmpRect.right, frame.top, mTmpRect.right + frame.width(), frame.bottom); + frame.set(mTmpRect.right - mDividerInsets, frame.top, + mTmpRect.right + frame.width() - mDividerInsets, frame.bottom); break; case DOCKED_TOP: - frame.set(frame.left, mTmpRect.bottom, mTmpRect.right, - mTmpRect.bottom + frame.height()); + frame.set(frame.left, mTmpRect.bottom - mDividerInsets, + mTmpRect.right, mTmpRect.bottom + frame.height() - mDividerInsets); break; case DOCKED_RIGHT: - frame.set(mTmpRect.left - frame.width(), frame.top, mTmpRect.left, frame.bottom); + frame.set(mTmpRect.left - frame.width() + mDividerInsets, frame.top, + mTmpRect.left + mDividerInsets, frame.bottom); break; case DOCKED_BOTTOM: - frame.set(frame.left, mTmpRect.top - frame.height(), frame.right, mTmpRect.top); + frame.set(frame.left, mTmpRect.top - frame.height() + mDividerInsets, + frame.right, mTmpRect.top + mDividerInsets); break; } } - - public boolean hasWindow() { - return mWindow != null; - } } diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index f9994020f3c50..3c3123ff0ba3b 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -193,20 +193,6 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { inputWindowHandle.frameTop = frame.top; inputWindowHandle.frameRight = frame.right; inputWindowHandle.frameBottom = frame.bottom; - if (child.mAttrs.type == TYPE_DOCK_DIVIDER) { - // We need to determine if the divider is horizontal or vertical and adjust its handle - // frame accordingly. - int adjustment = displayContent.mDividerControllerLocked.getWidthAdjustment(); - if (inputWindowHandle.frameRight - inputWindowHandle.frameLeft > - inputWindowHandle.frameTop - inputWindowHandle.frameBottom) { - // Horizontal divider. - inputWindowHandle.frameTop -= adjustment; - inputWindowHandle.frameBottom += adjustment; - } else { - inputWindowHandle.frameLeft -= adjustment; - inputWindowHandle.frameRight += adjustment; - } - } if (child.mGlobalScale != 1) { // If we are scaling the window, input coordinates need diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index affbf1064c8a7..7c02b43604bee 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -386,7 +386,7 @@ public class TaskStack implements DimLayer.DimLayerUser { final boolean dockedOnTopOrLeft = WindowManagerService.sDockedStackCreateMode == DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; getStackDockedModeBounds(mTmpRect, bounds, mStackId, mTmpRect2, - mDisplayContent.mDividerControllerLocked.getWidthAdjustment(), + mDisplayContent.mDividerControllerLocked.getContentWidth(), dockedOnTopOrLeft); } @@ -433,7 +433,7 @@ public class TaskStack implements DimLayer.DimLayerUser { final boolean dockedOnTopOrLeft = dockedSide == DOCKED_TOP || dockedSide == DOCKED_LEFT; getStackDockedModeBounds(mTmpRect, outBounds, mStackId, mTmpRect2, - mDisplayContent.mDividerControllerLocked.getWidthAdjustment(), dockedOnTopOrLeft); + mDisplayContent.mDividerControllerLocked.getContentWidth(), dockedOnTopOrLeft); } @@ -504,7 +504,7 @@ public class TaskStack implements DimLayer.DimLayerUser { final boolean dockedOnTopOrLeft = WindowManagerService.sDockedStackCreateMode == DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; getStackDockedModeBounds(bounds, bounds, FULLSCREEN_WORKSPACE_STACK_ID, dockedBounds, - mDisplayContent.mDividerControllerLocked.getWidthAdjustment(), dockedOnTopOrLeft); + mDisplayContent.mDividerControllerLocked.getContentWidth(), dockedOnTopOrLeft); } final int count = mService.mStackIdToStack.size();