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