DragDownHelper.java revision 98fb09c2b2dbf57803a8737ee7b73cf167721312
1/*
2 * Copyright (C) 2014 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.statusbar;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.content.Context;
23import android.view.MotionEvent;
24import android.view.View;
25import android.view.ViewConfiguration;
26import android.view.animation.AnimationUtils;
27import android.view.animation.Interpolator;
28
29import com.android.systemui.ExpandHelper;
30import com.android.systemui.Gefingerpoken;
31import com.android.systemui.R;
32
33/**
34 * A utility class to enable the downward swipe on the lockscreen to go to the full shade and expand
35 * the notification where the drag started.
36 */
37public class DragDownHelper implements Gefingerpoken {
38
39    private static final float RUBBERBAND_FACTOR_EXPANDABLE = 0.5f;
40    private static final float RUBBERBAND_FACTOR_STATIC = 0.15f;
41
42    private static final int SPRING_BACK_ANIMATION_LENGTH_MS = 375;
43
44    private int mMinDragDistance;
45    private ExpandHelper.Callback mCallback;
46    private float mInitialTouchX;
47    private float mInitialTouchY;
48    private boolean mDraggingDown;
49    private float mTouchSlop;
50    private OnDragDownListener mOnDragDownListener;
51    private View mHost;
52    private final int[] mTemp2 = new int[2];
53    private boolean mDraggedFarEnough;
54    private ExpandableView mStartingChild;
55    private Interpolator mInterpolator;
56
57    public DragDownHelper(Context context, View host, ExpandHelper.Callback callback,
58            OnDragDownListener onDragDownListener) {
59        mMinDragDistance = context.getResources().getDimensionPixelSize(
60                R.dimen.keyguard_drag_down_min_distance);
61        mInterpolator =
62                AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in);
63        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
64        mCallback = callback;
65        mOnDragDownListener = onDragDownListener;
66        mHost = host;
67    }
68
69    @Override
70    public boolean onInterceptTouchEvent(MotionEvent event) {
71        final float x = event.getX();
72        final float y = event.getY();
73
74        switch (event.getActionMasked()) {
75            case MotionEvent.ACTION_DOWN:
76                mDraggedFarEnough = false;
77                mDraggingDown = false;
78                mStartingChild = null;
79                mInitialTouchY = y;
80                mInitialTouchX = x;
81                break;
82
83            case MotionEvent.ACTION_MOVE:
84                final float h = y - mInitialTouchY;
85                if (h > mTouchSlop && h > Math.abs(x - mInitialTouchX)) {
86                    mDraggingDown = true;
87                    captureStartingChild(mInitialTouchX, mInitialTouchY);
88                    mInitialTouchY = y;
89                    mInitialTouchX = x;
90                    return true;
91                }
92                break;
93        }
94        return false;
95    }
96
97    @Override
98    public boolean onTouchEvent(MotionEvent event) {
99        if (!mDraggingDown) {
100            return false;
101        }
102        final float x = event.getX();
103        final float y = event.getY();
104
105        switch (event.getActionMasked()) {
106            case MotionEvent.ACTION_MOVE:
107                final float h = y - mInitialTouchY;
108                captureStartingChild(mInitialTouchX, mInitialTouchY);
109                if (mStartingChild != null) {
110                    handleExpansion(h, mStartingChild);
111                }
112                if (h > mMinDragDistance) {
113                    if (!mDraggedFarEnough) {
114                        mDraggedFarEnough = true;
115                        mOnDragDownListener.onThresholdReached();
116                    }
117                } else {
118                    if (mDraggedFarEnough) {
119                        mDraggedFarEnough = false;
120                        mOnDragDownListener.onReset();
121                    }
122                }
123                return true;
124            case MotionEvent.ACTION_UP:
125                if (mDraggedFarEnough) {
126                    if (mStartingChild != null) {
127                        mCallback.setUserLockedChild(mStartingChild, false);
128                    }
129                    mOnDragDownListener.onDraggedDown(mStartingChild);
130                    mDraggingDown = false;
131                } else {
132                    stopDragging();
133                    return false;
134                }
135                break;
136            case MotionEvent.ACTION_CANCEL:
137                stopDragging();
138                return false;
139        }
140        return false;
141    }
142
143    private void captureStartingChild(float x, float y) {
144        if (mStartingChild == null) {
145            mStartingChild = findView(x, y);
146            if (mStartingChild != null) {
147                mCallback.setUserLockedChild(mStartingChild, true);
148            }
149        }
150    }
151
152    private void handleExpansion(float heightDelta, ExpandableView child) {
153        if (heightDelta < 0) {
154            heightDelta = 0;
155        }
156        boolean expandable = child.isContentExpandable();
157        float rubberbandFactor = expandable
158                ? RUBBERBAND_FACTOR_EXPANDABLE
159                : RUBBERBAND_FACTOR_STATIC;
160        float rubberband = heightDelta * rubberbandFactor;
161        if (expandable && (rubberband + child.getMinHeight()) > child.getMaxHeight()) {
162            float overshoot = (rubberband + child.getMinHeight()) - child.getMaxHeight();
163            overshoot *= (1 - RUBBERBAND_FACTOR_STATIC);
164            rubberband -= overshoot;
165        }
166        child.setActualHeight((int) (child.getMinHeight() + rubberband));
167    }
168
169    private void cancelExpansion(final ExpandableView child) {
170        if (child.getActualHeight() == child.getMinHeight()) {
171            return;
172        }
173        ObjectAnimator anim = ObjectAnimator.ofInt(child, "actualHeight",
174                child.getActualHeight(), child.getMinHeight());
175        anim.setInterpolator(mInterpolator);
176        anim.setDuration(SPRING_BACK_ANIMATION_LENGTH_MS);
177        anim.addListener(new AnimatorListenerAdapter() {
178            @Override
179            public void onAnimationEnd(Animator animation) {
180                mCallback.setUserLockedChild(child, false);
181            }
182        });
183        anim.start();
184    }
185
186    private void stopDragging() {
187        if (mStartingChild != null) {
188            cancelExpansion(mStartingChild);
189        }
190        mDraggingDown = false;
191        mOnDragDownListener.onReset();
192    }
193
194    private ExpandableView findView(float x, float y) {
195        mHost.getLocationOnScreen(mTemp2);
196        x += mTemp2[0];
197        y += mTemp2[1];
198        return mCallback.getChildAtRawPosition(x, y);
199    }
200
201    public interface OnDragDownListener {
202        void onDraggedDown(View startingChild);
203        void onReset();
204        void onThresholdReached();
205    }
206}
207