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