DragDownHelper.java revision 177fd4324ab89dc138b3d4c7472623ae9dc8c9d8
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.animation.ValueAnimator; 23import android.content.Context; 24import android.view.MotionEvent; 25import android.view.View; 26import android.view.ViewConfiguration; 27import android.view.animation.AnimationUtils; 28import android.view.animation.Interpolator; 29import com.android.systemui.ExpandHelper; 30import com.android.systemui.Gefingerpoken; 31import com.android.systemui.R; 32import com.android.systemui.classifier.FalsingManager; 33 34/** 35 * A utility class to enable the downward swipe on the lockscreen to go to the full shade and expand 36 * the notification where the drag started. 37 */ 38public class DragDownHelper implements Gefingerpoken { 39 40 private static final float RUBBERBAND_FACTOR_EXPANDABLE = 0.5f; 41 private static final float RUBBERBAND_FACTOR_STATIC = 0.15f; 42 43 private static final int SPRING_BACK_ANIMATION_LENGTH_MS = 375; 44 45 private int mMinDragDistance; 46 private ExpandHelper.Callback mCallback; 47 private float mInitialTouchX; 48 private float mInitialTouchY; 49 private boolean mDraggingDown; 50 private float mTouchSlop; 51 private DragDownCallback mDragDownCallback; 52 private View mHost; 53 private final int[] mTemp2 = new int[2]; 54 private boolean mDraggedFarEnough; 55 private ExpandableView mStartingChild; 56 private Interpolator mInterpolator; 57 private float mLastHeight; 58 private FalsingManager mFalsingManager; 59 60 public DragDownHelper(Context context, View host, ExpandHelper.Callback callback, 61 DragDownCallback dragDownCallback) { 62 mMinDragDistance = context.getResources().getDimensionPixelSize( 63 R.dimen.keyguard_drag_down_min_distance); 64 mInterpolator = 65 AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in); 66 mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 67 mCallback = callback; 68 mDragDownCallback = dragDownCallback; 69 mHost = host; 70 mFalsingManager = FalsingManager.getInstance(context); 71 } 72 73 @Override 74 public boolean onInterceptTouchEvent(MotionEvent event) { 75 final float x = event.getX(); 76 final float y = event.getY(); 77 78 switch (event.getActionMasked()) { 79 case MotionEvent.ACTION_DOWN: 80 mDraggedFarEnough = false; 81 mDraggingDown = false; 82 mStartingChild = null; 83 mInitialTouchY = y; 84 mInitialTouchX = x; 85 break; 86 87 case MotionEvent.ACTION_MOVE: 88 final float h = y - mInitialTouchY; 89 if (h > mTouchSlop && h > Math.abs(x - mInitialTouchX)) { 90 mFalsingManager.onNotificatonStartDraggingDown(); 91 mDraggingDown = true; 92 captureStartingChild(mInitialTouchX, mInitialTouchY); 93 mInitialTouchY = y; 94 mInitialTouchX = x; 95 mDragDownCallback.onTouchSlopExceeded(); 96 return true; 97 } 98 break; 99 } 100 return false; 101 } 102 103 @Override 104 public boolean onTouchEvent(MotionEvent event) { 105 if (!mDraggingDown) { 106 return false; 107 } 108 final float x = event.getX(); 109 final float y = event.getY(); 110 111 switch (event.getActionMasked()) { 112 case MotionEvent.ACTION_MOVE: 113 mLastHeight = y - mInitialTouchY; 114 captureStartingChild(mInitialTouchX, mInitialTouchY); 115 if (mStartingChild != null) { 116 handleExpansion(mLastHeight, mStartingChild); 117 } else { 118 mDragDownCallback.setEmptyDragAmount(mLastHeight); 119 } 120 if (mLastHeight > mMinDragDistance) { 121 if (!mDraggedFarEnough) { 122 mDraggedFarEnough = true; 123 mDragDownCallback.onCrossedThreshold(true); 124 } 125 } else { 126 if (mDraggedFarEnough) { 127 mDraggedFarEnough = false; 128 mDragDownCallback.onCrossedThreshold(false); 129 } 130 } 131 return true; 132 case MotionEvent.ACTION_UP: 133 if (!isFalseTouch() && mDragDownCallback.onDraggedDown(mStartingChild, 134 (int) (y - mInitialTouchY))) { 135 if (mStartingChild == null) { 136 mDragDownCallback.setEmptyDragAmount(0f); 137 } else { 138 mCallback.setUserLockedChild(mStartingChild, false); 139 } 140 mDraggingDown = false; 141 } else { 142 stopDragging(); 143 return false; 144 } 145 break; 146 case MotionEvent.ACTION_CANCEL: 147 stopDragging(); 148 return false; 149 } 150 return false; 151 } 152 153 private boolean isFalseTouch() { 154 return mFalsingManager.isFalseTouch() || !mDraggedFarEnough; 155 } 156 157 private void captureStartingChild(float x, float y) { 158 if (mStartingChild == null) { 159 mStartingChild = findView(x, y); 160 if (mStartingChild != null) { 161 mCallback.setUserLockedChild(mStartingChild, true); 162 } 163 } 164 } 165 166 private void handleExpansion(float heightDelta, ExpandableView child) { 167 if (heightDelta < 0) { 168 heightDelta = 0; 169 } 170 boolean expandable = child.isContentExpandable(); 171 float rubberbandFactor = expandable 172 ? RUBBERBAND_FACTOR_EXPANDABLE 173 : RUBBERBAND_FACTOR_STATIC; 174 float rubberband = heightDelta * rubberbandFactor; 175 if (expandable && (rubberband + child.getMinHeight()) > child.getMaxContentHeight()) { 176 float overshoot = (rubberband + child.getMinHeight()) - child.getMaxContentHeight(); 177 overshoot *= (1 - RUBBERBAND_FACTOR_STATIC); 178 rubberband -= overshoot; 179 } 180 child.setActualHeight((int) (child.getMinHeight() + rubberband)); 181 } 182 183 private void cancelExpansion(final ExpandableView child) { 184 if (child.getActualHeight() == child.getMinHeight()) { 185 mCallback.setUserLockedChild(child, false); 186 return; 187 } 188 ObjectAnimator anim = ObjectAnimator.ofInt(child, "actualHeight", 189 child.getActualHeight(), child.getMinHeight()); 190 anim.setInterpolator(mInterpolator); 191 anim.setDuration(SPRING_BACK_ANIMATION_LENGTH_MS); 192 anim.addListener(new AnimatorListenerAdapter() { 193 @Override 194 public void onAnimationEnd(Animator animation) { 195 mCallback.setUserLockedChild(child, false); 196 } 197 }); 198 anim.start(); 199 } 200 201 private void cancelExpansion() { 202 ValueAnimator anim = ValueAnimator.ofFloat(mLastHeight, 0); 203 anim.setInterpolator(mInterpolator); 204 anim.setDuration(SPRING_BACK_ANIMATION_LENGTH_MS); 205 anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 206 @Override 207 public void onAnimationUpdate(ValueAnimator animation) { 208 mDragDownCallback.setEmptyDragAmount((Float) animation.getAnimatedValue()); 209 } 210 }); 211 anim.start(); 212 } 213 214 private void stopDragging() { 215 mFalsingManager.onNotificatonStopDraggingDown(); 216 if (mStartingChild != null) { 217 cancelExpansion(mStartingChild); 218 } else { 219 cancelExpansion(); 220 } 221 mDraggingDown = false; 222 mDragDownCallback.onDragDownReset(); 223 } 224 225 private ExpandableView findView(float x, float y) { 226 mHost.getLocationOnScreen(mTemp2); 227 x += mTemp2[0]; 228 y += mTemp2[1]; 229 return mCallback.getChildAtRawPosition(x, y); 230 } 231 232 public interface DragDownCallback { 233 234 /** 235 * @return true if the interaction is accepted, false if it should be cancelled 236 */ 237 boolean onDraggedDown(View startingChild, int dragLengthY); 238 void onDragDownReset(); 239 240 /** 241 * The user has dragged either above or below the threshold 242 * @param above whether he dragged above it 243 */ 244 void onCrossedThreshold(boolean above); 245 void onTouchSlopExceeded(); 246 void setEmptyDragAmount(float amount); 247 } 248} 249