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();