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 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 */ 38 public 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 float mLastHeight; 57 private FalsingManager mFalsingManager; 58 DragDownHelper(Context context, View host, ExpandHelper.Callback callback, DragDownCallback dragDownCallback)59 public DragDownHelper(Context context, View host, ExpandHelper.Callback callback, 60 DragDownCallback dragDownCallback) { 61 mMinDragDistance = context.getResources().getDimensionPixelSize( 62 R.dimen.keyguard_drag_down_min_distance); 63 mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 64 mCallback = callback; 65 mDragDownCallback = dragDownCallback; 66 mHost = host; 67 mFalsingManager = FalsingManager.getInstance(context); 68 } 69 70 @Override onInterceptTouchEvent(MotionEvent event)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 mFalsingManager.onNotificatonStartDraggingDown(); 88 mDraggingDown = true; 89 captureStartingChild(mInitialTouchX, mInitialTouchY); 90 mInitialTouchY = y; 91 mInitialTouchX = x; 92 mDragDownCallback.onTouchSlopExceeded(); 93 return true; 94 } 95 break; 96 } 97 return false; 98 } 99 100 @Override onTouchEvent(MotionEvent event)101 public boolean onTouchEvent(MotionEvent event) { 102 if (!mDraggingDown) { 103 return false; 104 } 105 final float x = event.getX(); 106 final float y = event.getY(); 107 108 switch (event.getActionMasked()) { 109 case MotionEvent.ACTION_MOVE: 110 mLastHeight = y - mInitialTouchY; 111 captureStartingChild(mInitialTouchX, mInitialTouchY); 112 if (mStartingChild != null) { 113 handleExpansion(mLastHeight, mStartingChild); 114 } else { 115 mDragDownCallback.setEmptyDragAmount(mLastHeight); 116 } 117 if (mLastHeight > mMinDragDistance) { 118 if (!mDraggedFarEnough) { 119 mDraggedFarEnough = true; 120 mDragDownCallback.onCrossedThreshold(true); 121 } 122 } else { 123 if (mDraggedFarEnough) { 124 mDraggedFarEnough = false; 125 mDragDownCallback.onCrossedThreshold(false); 126 } 127 } 128 return true; 129 case MotionEvent.ACTION_UP: 130 if (!isFalseTouch() && mDragDownCallback.onDraggedDown(mStartingChild, 131 (int) (y - mInitialTouchY))) { 132 if (mStartingChild == null) { 133 mDragDownCallback.setEmptyDragAmount(0f); 134 } else { 135 mCallback.setUserLockedChild(mStartingChild, false); 136 mStartingChild = null; 137 } 138 mDraggingDown = false; 139 } else { 140 stopDragging(); 141 return false; 142 } 143 break; 144 case MotionEvent.ACTION_CANCEL: 145 stopDragging(); 146 return false; 147 } 148 return false; 149 } 150 isFalseTouch()151 private boolean isFalseTouch() { 152 return mFalsingManager.isFalseTouch() || !mDraggedFarEnough; 153 } 154 captureStartingChild(float x, float y)155 private void captureStartingChild(float x, float y) { 156 if (mStartingChild == null) { 157 mStartingChild = findView(x, y); 158 if (mStartingChild != null) { 159 mCallback.setUserLockedChild(mStartingChild, true); 160 } 161 } 162 } 163 handleExpansion(float heightDelta, ExpandableView child)164 private void handleExpansion(float heightDelta, ExpandableView child) { 165 if (heightDelta < 0) { 166 heightDelta = 0; 167 } 168 boolean expandable = child.isContentExpandable(); 169 float rubberbandFactor = expandable 170 ? RUBBERBAND_FACTOR_EXPANDABLE 171 : RUBBERBAND_FACTOR_STATIC; 172 float rubberband = heightDelta * rubberbandFactor; 173 if (expandable 174 && (rubberband + child.getCollapsedHeight()) > child.getMaxContentHeight()) { 175 float overshoot = 176 (rubberband + child.getCollapsedHeight()) - child.getMaxContentHeight(); 177 overshoot *= (1 - RUBBERBAND_FACTOR_STATIC); 178 rubberband -= overshoot; 179 } 180 child.setActualHeight((int) (child.getCollapsedHeight() + rubberband)); 181 } 182 cancelExpansion(final ExpandableView child)183 private void cancelExpansion(final ExpandableView child) { 184 if (child.getActualHeight() == child.getCollapsedHeight()) { 185 mCallback.setUserLockedChild(child, false); 186 return; 187 } 188 ObjectAnimator anim = ObjectAnimator.ofInt(child, "actualHeight", 189 child.getActualHeight(), child.getCollapsedHeight()); 190 anim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); 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 cancelExpansion()201 private void cancelExpansion() { 202 ValueAnimator anim = ValueAnimator.ofFloat(mLastHeight, 0); 203 anim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); 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 stopDragging()214 private void stopDragging() { 215 mFalsingManager.onNotificatonStopDraggingDown(); 216 if (mStartingChild != null) { 217 cancelExpansion(mStartingChild); 218 mStartingChild = null; 219 } else { 220 cancelExpansion(); 221 } 222 mDraggingDown = false; 223 mDragDownCallback.onDragDownReset(); 224 } 225 findView(float x, float y)226 private ExpandableView findView(float x, float y) { 227 mHost.getLocationOnScreen(mTemp2); 228 x += mTemp2[0]; 229 y += mTemp2[1]; 230 return mCallback.getChildAtRawPosition(x, y); 231 } 232 isDraggingDown()233 public boolean isDraggingDown() { 234 return mDraggingDown; 235 } 236 237 public interface DragDownCallback { 238 239 /** 240 * @return true if the interaction is accepted, false if it should be cancelled 241 */ onDraggedDown(View startingChild, int dragLengthY)242 boolean onDraggedDown(View startingChild, int dragLengthY); onDragDownReset()243 void onDragDownReset(); 244 245 /** 246 * The user has dragged either above or below the threshold 247 * @param above whether he dragged above it 248 */ onCrossedThreshold(boolean above)249 void onCrossedThreshold(boolean above); onTouchSlopExceeded()250 void onTouchSlopExceeded(); setEmptyDragAmount(float amount)251 void setEmptyDragAmount(float amount); 252 } 253 } 254