DividerView.java revision 2fbe033f9d634505ff9e90f232c0339f616d7b86
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.stackdivider;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ValueAnimator;
22import android.animation.ValueAnimator.AnimatorUpdateListener;
23import android.annotation.Nullable;
24import android.content.Context;
25import android.content.res.Configuration;
26import android.graphics.Rect;
27import android.graphics.Region.Op;
28import android.hardware.display.DisplayManager;
29import android.util.AttributeSet;
30import android.view.Display;
31import android.view.DisplayInfo;
32import android.view.MotionEvent;
33import android.view.VelocityTracker;
34import android.view.View;
35import android.view.View.OnTouchListener;
36import android.view.ViewTreeObserver.InternalInsetsInfo;
37import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
38import android.view.WindowManager;
39import android.view.animation.AnimationUtils;
40import android.view.animation.Interpolator;
41import android.view.animation.PathInterpolator;
42import android.widget.FrameLayout;
43import android.widget.ImageButton;
44
45import com.android.systemui.R;
46import com.android.systemui.stackdivider.DividerSnapAlgorithm.SnapTarget;
47import com.android.systemui.statusbar.FlingAnimationUtils;
48
49import static android.view.PointerIcon.STYLE_HORIZONTAL_DOUBLE_ARROW;
50import static android.view.PointerIcon.STYLE_VERTICAL_DOUBLE_ARROW;
51
52/**
53 * Docked stack divider.
54 */
55public class DividerView extends FrameLayout implements OnTouchListener,
56        OnComputeInternalInsetsListener {
57
58    private static final String TAG = "DividerView";
59
60    private ImageButton mHandle;
61    private View mBackground;
62    private int mStartX;
63    private int mStartY;
64    private int mStartPosition;
65    private int mDockSide;
66    private final int[] mTempInt2 = new int[2];
67
68    private int mDividerInsets;
69    private int mDisplayWidth;
70    private int mDisplayHeight;
71    private int mDividerWindowWidth;
72    private int mDividerSize;
73    private int mTouchElevation;
74
75    private final Rect mTmpRect = new Rect();
76    private final Rect mLastResizeRect = new Rect();
77    private final WindowManagerProxy mWindowManagerProxy = new WindowManagerProxy();
78    private Interpolator mFastOutSlowInInterpolator;
79    private final Interpolator mTouchResponseInterpolator =
80            new PathInterpolator(0.3f, 0f, 0.1f, 1f);
81    private DividerWindowManager mWindowManager;
82    private VelocityTracker mVelocityTracker;
83    private FlingAnimationUtils mFlingAnimationUtils;
84
85    public DividerView(Context context) {
86        super(context);
87    }
88
89    public DividerView(Context context, @Nullable AttributeSet attrs) {
90        super(context, attrs);
91    }
92
93    public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
94        super(context, attrs, defStyleAttr);
95    }
96
97    public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
98            int defStyleRes) {
99        super(context, attrs, defStyleAttr, defStyleRes);
100    }
101
102    @Override
103    protected void onFinishInflate() {
104        super.onFinishInflate();
105        mHandle = (ImageButton) findViewById(R.id.docked_divider_handle);
106        mBackground = findViewById(R.id.docked_divider_background);
107        mHandle.setOnTouchListener(this);
108        mDividerWindowWidth = getResources().getDimensionPixelSize(
109                com.android.internal.R.dimen.docked_stack_divider_thickness);
110        mDividerInsets = getResources().getDimensionPixelSize(
111                com.android.internal.R.dimen.docked_stack_divider_insets);
112        mDividerSize = mDividerWindowWidth - 2 * mDividerInsets;
113        mTouchElevation = getResources().getDimensionPixelSize(
114                R.dimen.docked_stack_divider_lift_elevation);
115        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
116                android.R.interpolator.fast_out_slow_in);
117        mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.3f);
118        updateDisplayInfo();
119        boolean landscape = getResources().getConfiguration().orientation
120                == Configuration.ORIENTATION_LANDSCAPE;
121        mHandle.setPointerShape(
122                landscape ? STYLE_HORIZONTAL_DOUBLE_ARROW : STYLE_VERTICAL_DOUBLE_ARROW);
123        getViewTreeObserver().addOnComputeInternalInsetsListener(this);
124    }
125
126    public void setWindowManager(DividerWindowManager windowManager) {
127        mWindowManager = windowManager;
128    }
129
130    public WindowManagerProxy getWindowManagerProxy() {
131        return mWindowManagerProxy;
132    }
133
134    public boolean startDragging() {
135        mDockSide = mWindowManagerProxy.getDockSide();
136        if (mDockSide != WindowManager.DOCKED_INVALID) {
137            mWindowManagerProxy.setResizing(true);
138            mWindowManager.setSlippery(false);
139            liftBackground();
140            return true;
141        } else {
142            return false;
143        }
144    }
145
146    public void stopDragging(int position, float velocity) {
147        fling(position, velocity);
148        mWindowManager.setSlippery(true);
149        releaseBackground();
150    }
151
152    @Override
153    public boolean onTouch(View v, MotionEvent event) {
154        convertToScreenCoordinates(event);
155        final int action = event.getAction() & MotionEvent.ACTION_MASK;
156        switch (action) {
157            case MotionEvent.ACTION_DOWN:
158                mVelocityTracker = VelocityTracker.obtain();
159                mVelocityTracker.addMovement(event);
160                mStartX = (int) event.getX();
161                mStartY = (int) event.getY();
162                getLocationOnScreen(mTempInt2);
163                boolean result = startDragging();
164                if (isHorizontalDivision()) {
165                    mStartPosition = mTempInt2[1] + mDividerInsets;
166                } else {
167                    mStartPosition = mTempInt2[0] + mDividerInsets;
168                }
169                return result;
170            case MotionEvent.ACTION_MOVE:
171                mVelocityTracker.addMovement(event);
172                int x = (int) event.getX();
173                int y = (int) event.getY();
174                if (mDockSide != WindowManager.DOCKED_INVALID) {
175                    resizeStack(calculatePosition(x, y));
176                }
177                break;
178            case MotionEvent.ACTION_UP:
179            case MotionEvent.ACTION_CANCEL:
180                mVelocityTracker.addMovement(event);
181
182                x = (int) event.getRawX();
183                y = (int) event.getRawY();
184
185                mVelocityTracker.computeCurrentVelocity(1000);
186                int position = calculatePosition(x, y);
187                stopDragging(position, isHorizontalDivision() ? mVelocityTracker.getYVelocity()
188                        : mVelocityTracker.getXVelocity());
189                break;
190        }
191        return true;
192    }
193
194    private void convertToScreenCoordinates(MotionEvent event) {
195        event.setLocation(event.getRawX(), event.getRawY());
196    }
197
198    private void fling(int position, float velocity) {
199        final SnapTarget snapTarget = new DividerSnapAlgorithm(getContext(), mFlingAnimationUtils,
200                mDividerSize, isHorizontalDivision()).calculateSnapTarget(position, velocity);
201
202        ValueAnimator anim = ValueAnimator.ofInt(position, snapTarget.position);
203        anim.addUpdateListener(new AnimatorUpdateListener() {
204            @Override
205            public void onAnimationUpdate(ValueAnimator animation) {
206                resizeStack((Integer) animation.getAnimatedValue());
207            }
208        });
209        anim.addListener(new AnimatorListenerAdapter() {
210            @Override
211            public void onAnimationEnd(Animator animation) {
212                commitSnapFlags(snapTarget);
213                mWindowManagerProxy.setResizing(false);
214                mDockSide = WindowManager.DOCKED_INVALID;
215            }
216        });
217        mFlingAnimationUtils.apply(anim, position, snapTarget.position, velocity);
218        anim.start();
219    }
220
221    private void commitSnapFlags(SnapTarget target) {
222        if (target.flag == SnapTarget.FLAG_NONE) {
223            return;
224        }
225        boolean dismissOrMaximize;
226        if (target.flag == SnapTarget.FLAG_DISMISS_START) {
227            dismissOrMaximize = mDockSide == WindowManager.DOCKED_LEFT
228                    || mDockSide == WindowManager.DOCKED_TOP;
229        } else {
230            dismissOrMaximize = mDockSide == WindowManager.DOCKED_RIGHT
231                    || mDockSide == WindowManager.DOCKED_BOTTOM;
232        }
233        if (dismissOrMaximize) {
234            mWindowManagerProxy.dismissDockedStack();
235        } else {
236            mWindowManagerProxy.maximizeDockedStack();
237        }
238    }
239
240    private void liftBackground() {
241        if (isHorizontalDivision()) {
242            mBackground.animate().scaleY(1.5f);
243        } else {
244            mBackground.animate().scaleX(1.5f);
245        }
246        mBackground.animate()
247                .setInterpolator(mTouchResponseInterpolator)
248                .setDuration(150)
249                .translationZ(mTouchElevation);
250
251        // Lift handle as well so it doesn't get behind the background, even though it doesn't
252        // cast shadow.
253        mHandle.animate()
254                .setInterpolator(mTouchResponseInterpolator)
255                .setDuration(150)
256                .translationZ(mTouchElevation);
257    }
258
259    private void releaseBackground() {
260        mBackground.animate()
261                .setInterpolator(mFastOutSlowInInterpolator)
262                .setDuration(200)
263                .translationZ(0)
264                .scaleX(1f)
265                .scaleY(1f);
266        mHandle.animate()
267                .setInterpolator(mFastOutSlowInInterpolator)
268                .setDuration(200)
269                .translationZ(0);
270    }
271
272    @Override
273    protected void onConfigurationChanged(Configuration newConfig) {
274        super.onConfigurationChanged(newConfig);
275        updateDisplayInfo();
276    }
277
278    private void updateDisplayInfo() {
279        final DisplayManager displayManager =
280                (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
281        Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
282        final DisplayInfo info = new DisplayInfo();
283        display.getDisplayInfo(info);
284        mDisplayWidth = info.logicalWidth;
285        mDisplayHeight = info.logicalHeight;
286    }
287
288    private int calculatePosition(int touchX, int touchY) {
289        return isHorizontalDivision() ? calculateYPosition(touchY) : calculateXPosition(touchX);
290    }
291
292    public boolean isHorizontalDivision() {
293        return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
294    }
295
296    private int calculateXPosition(int touchX) {
297        return mStartPosition + touchX - mStartX;
298    }
299
300    private int calculateYPosition(int touchY) {
301        return mStartPosition + touchY - mStartY;
302    }
303
304    public void calculateBoundsForPosition(int position, int dockSide, Rect outRect) {
305        outRect.set(0, 0, mDisplayWidth, mDisplayHeight);
306        switch (dockSide) {
307            case WindowManager.DOCKED_LEFT:
308                outRect.right = position;
309                break;
310            case WindowManager.DOCKED_TOP:
311                outRect.bottom = position;
312                break;
313            case WindowManager.DOCKED_RIGHT:
314                outRect.left = position + mDividerWindowWidth - 2 * mDividerInsets;
315                break;
316            case WindowManager.DOCKED_BOTTOM:
317                outRect.top = position + mDividerWindowWidth - 2 * mDividerInsets;
318                break;
319        }
320        if (outRect.left > outRect.right) {
321            outRect.left = outRect.right;
322        }
323        if (outRect.top > outRect.bottom) {
324            outRect.top = outRect.bottom;
325        }
326        if (outRect.right < outRect.left) {
327            outRect.right = outRect.left;
328        }
329        if (outRect.bottom < outRect.top) {
330            outRect.bottom = outRect.top;
331        }
332    }
333
334    public void resizeStack(int position) {
335        calculateBoundsForPosition(position, mDockSide, mTmpRect);
336        if (mTmpRect.equals(mLastResizeRect)) {
337            return;
338        }
339
340        // Make sure shadows are updated
341        mBackground.invalidate();
342
343        mLastResizeRect.set(mTmpRect);
344        mWindowManagerProxy.resizeDockedStack(mTmpRect);
345    }
346
347    @Override
348    public void onComputeInternalInsets(InternalInsetsInfo inoutInfo) {
349        inoutInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
350        inoutInfo.touchableRegion.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(),
351                mHandle.getBottom());
352        inoutInfo.touchableRegion.op(mBackground.getLeft(), mBackground.getTop(),
353                mBackground.getRight(), mBackground.getBottom(), Op.UNION);
354    }
355}
356