Home | History | Annotate | Download | only in statusbar
      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 
     17 package com.android.systemui.statusbar;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.ObjectAnimator;
     22 import android.animation.ValueAnimator;
     23 import android.content.Context;
     24 import android.view.MotionEvent;
     25 import android.view.View;
     26 import android.view.ViewConfiguration;
     27 import android.view.animation.AnimationUtils;
     28 import android.view.animation.Interpolator;
     29 import com.android.systemui.ExpandHelper;
     30 import com.android.systemui.Gefingerpoken;
     31 import 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  */
     37 public 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 DragDownCallback mDragDownCallback;
     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     private float mLastHeight;
     57 
     58     public DragDownHelper(Context context, View host, ExpandHelper.Callback callback,
     59             DragDownCallback dragDownCallback) {
     60         mMinDragDistance = context.getResources().getDimensionPixelSize(
     61                 R.dimen.keyguard_drag_down_min_distance);
     62         mInterpolator =
     63                 AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in);
     64         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
     65         mCallback = callback;
     66         mDragDownCallback = dragDownCallback;
     67         mHost = host;
     68     }
     69 
     70     @Override
     71     public boolean onInterceptTouchEvent(MotionEvent event) {
     72         final float x = event.getX();
     73         final float y = event.getY();
     74 
     75         switch (event.getActionMasked()) {
     76             case MotionEvent.ACTION_DOWN:
     77                 mDraggedFarEnough = false;
     78                 mDraggingDown = false;
     79                 mStartingChild = null;
     80                 mInitialTouchY = y;
     81                 mInitialTouchX = x;
     82                 break;
     83 
     84             case MotionEvent.ACTION_MOVE:
     85                 final float h = y - mInitialTouchY;
     86                 if (h > mTouchSlop && h > Math.abs(x - mInitialTouchX)) {
     87                     mDraggingDown = true;
     88                     captureStartingChild(mInitialTouchX, mInitialTouchY);
     89                     mInitialTouchY = y;
     90                     mInitialTouchX = x;
     91                     mDragDownCallback.onTouchSlopExceeded();
     92                     return true;
     93                 }
     94                 break;
     95         }
     96         return false;
     97     }
     98 
     99     @Override
    100     public boolean onTouchEvent(MotionEvent event) {
    101         if (!mDraggingDown) {
    102             return false;
    103         }
    104         final float x = event.getX();
    105         final float y = event.getY();
    106 
    107         switch (event.getActionMasked()) {
    108             case MotionEvent.ACTION_MOVE:
    109                 mLastHeight = y - mInitialTouchY;
    110                 captureStartingChild(mInitialTouchX, mInitialTouchY);
    111                 if (mStartingChild != null) {
    112                     handleExpansion(mLastHeight, mStartingChild);
    113                 } else {
    114                     mDragDownCallback.setEmptyDragAmount(mLastHeight);
    115                 }
    116                 if (mLastHeight > mMinDragDistance) {
    117                     if (!mDraggedFarEnough) {
    118                         mDraggedFarEnough = true;
    119                         mDragDownCallback.onThresholdReached();
    120                     }
    121                 } else {
    122                     if (mDraggedFarEnough) {
    123                         mDraggedFarEnough = false;
    124                         mDragDownCallback.onDragDownReset();
    125                     }
    126                 }
    127                 return true;
    128             case MotionEvent.ACTION_UP:
    129                 if (mDraggedFarEnough && mDragDownCallback.onDraggedDown(mStartingChild,
    130                         (int) (y - mInitialTouchY))) {
    131                     if (mStartingChild == null) {
    132                         mDragDownCallback.setEmptyDragAmount(0f);
    133                     }
    134                     mDraggingDown = false;
    135                 } else {
    136                     stopDragging();
    137                     return false;
    138                 }
    139                 break;
    140             case MotionEvent.ACTION_CANCEL:
    141                 stopDragging();
    142                 return false;
    143         }
    144         return false;
    145     }
    146 
    147     private void captureStartingChild(float x, float y) {
    148         if (mStartingChild == null) {
    149             mStartingChild = findView(x, y);
    150             if (mStartingChild != null) {
    151                 mCallback.setUserLockedChild(mStartingChild, true);
    152             }
    153         }
    154     }
    155 
    156     private void handleExpansion(float heightDelta, ExpandableView child) {
    157         if (heightDelta < 0) {
    158             heightDelta = 0;
    159         }
    160         boolean expandable = child.isContentExpandable();
    161         float rubberbandFactor = expandable
    162                 ? RUBBERBAND_FACTOR_EXPANDABLE
    163                 : RUBBERBAND_FACTOR_STATIC;
    164         float rubberband = heightDelta * rubberbandFactor;
    165         if (expandable && (rubberband + child.getMinHeight()) > child.getMaxContentHeight()) {
    166             float overshoot = (rubberband + child.getMinHeight()) - child.getMaxContentHeight();
    167             overshoot *= (1 - RUBBERBAND_FACTOR_STATIC);
    168             rubberband -= overshoot;
    169         }
    170         child.setContentHeight((int) (child.getMinHeight() + rubberband));
    171     }
    172 
    173     private void cancelExpansion(final ExpandableView child) {
    174         if (child.getContentHeight() == child.getMinHeight()) {
    175             return;
    176         }
    177         ObjectAnimator anim = ObjectAnimator.ofInt(child, "contentHeight",
    178                 child.getContentHeight(), child.getMinHeight());
    179         anim.setInterpolator(mInterpolator);
    180         anim.setDuration(SPRING_BACK_ANIMATION_LENGTH_MS);
    181         anim.addListener(new AnimatorListenerAdapter() {
    182             @Override
    183             public void onAnimationEnd(Animator animation) {
    184                 mCallback.setUserLockedChild(child, false);
    185             }
    186         });
    187         anim.start();
    188     }
    189 
    190     private void cancelExpansion() {
    191         ValueAnimator anim = ValueAnimator.ofFloat(mLastHeight, 0);
    192         anim.setInterpolator(mInterpolator);
    193         anim.setDuration(SPRING_BACK_ANIMATION_LENGTH_MS);
    194         anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    195             @Override
    196             public void onAnimationUpdate(ValueAnimator animation) {
    197                 mDragDownCallback.setEmptyDragAmount((Float) animation.getAnimatedValue());
    198             }
    199         });
    200         anim.start();
    201     }
    202 
    203     private void stopDragging() {
    204         if (mStartingChild != null) {
    205             cancelExpansion(mStartingChild);
    206         } else {
    207             cancelExpansion();
    208         }
    209         mDraggingDown = false;
    210         mDragDownCallback.onDragDownReset();
    211     }
    212 
    213     private ExpandableView findView(float x, float y) {
    214         mHost.getLocationOnScreen(mTemp2);
    215         x += mTemp2[0];
    216         y += mTemp2[1];
    217         return mCallback.getChildAtRawPosition(x, y);
    218     }
    219 
    220     public interface DragDownCallback {
    221 
    222         /**
    223          * @return true if the interaction is accepted, false if it should be cancelled
    224          */
    225         boolean onDraggedDown(View startingChild, int dragLengthY);
    226         void onDragDownReset();
    227         void onThresholdReached();
    228         void onTouchSlopExceeded();
    229         void setEmptyDragAmount(float amount);
    230     }
    231 }
    232