blob: 66b1dd8db12361a6a331ae11e580e782e02ad716 [file] [log] [blame]
Selim Cinekbaa23272014-07-08 18:01:07 +02001/*
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.phone;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ValueAnimator;
22import android.content.Context;
Selim Cinekbaa23272014-07-08 18:01:07 +020023import android.view.MotionEvent;
24import android.view.VelocityTracker;
25import android.view.View;
26import android.view.ViewConfiguration;
Selim Cinekbaa23272014-07-08 18:01:07 +020027
Winsonc0d70582016-01-29 10:24:39 -080028import com.android.systemui.Interpolators;
Selim Cinekbaa23272014-07-08 18:01:07 +020029import com.android.systemui.R;
Dave Mankoff468d4f62019-05-08 14:56:29 -040030import com.android.systemui.plugins.FalsingManager;
Selim Cinekbaa23272014-07-08 18:01:07 +020031import com.android.systemui.statusbar.FlingAnimationUtils;
32import com.android.systemui.statusbar.KeyguardAffordanceView;
33
34/**
35 * A touch handler of the keyguard which is responsible for launching phone and camera affordances.
36 */
37public class KeyguardAffordanceHelper {
38
Selim Cinekbaa23272014-07-08 18:01:07 +020039 public static final long HINT_PHASE1_DURATION = 200;
40 private static final long HINT_PHASE2_DURATION = 350;
Selim Cinek6746c282015-04-21 19:58:31 -070041 private static final float BACKGROUND_RADIUS_SCALE_FACTOR = 0.25f;
Selim Cinekbaa23272014-07-08 18:01:07 +020042 private static final int HINT_CIRCLE_OPEN_DURATION = 500;
43
44 private final Context mContext;
Adrian Roos6a70b882016-04-19 17:09:07 -070045 private final Callback mCallback;
Selim Cinekbaa23272014-07-08 18:01:07 +020046
47 private FlingAnimationUtils mFlingAnimationUtils;
Selim Cinekbaa23272014-07-08 18:01:07 +020048 private VelocityTracker mVelocityTracker;
49 private boolean mSwipingInProgress;
50 private float mInitialTouchX;
51 private float mInitialTouchY;
52 private float mTranslation;
53 private float mTranslationOnDown;
54 private int mTouchSlop;
55 private int mMinTranslationAmount;
56 private int mMinFlingVelocity;
57 private int mHintGrowAmount;
Jorim Jaggief067fb2014-09-01 16:44:08 +020058 private KeyguardAffordanceView mLeftIcon;
Jorim Jaggief067fb2014-09-01 16:44:08 +020059 private KeyguardAffordanceView mRightIcon;
Selim Cinekbaa23272014-07-08 18:01:07 +020060 private Animator mSwipeAnimator;
Dave Mankoff781ef7e2019-06-28 16:33:25 -040061 private final FalsingManager mFalsingManager;
Selim Cinekbaa23272014-07-08 18:01:07 +020062 private int mMinBackgroundRadius;
Selim Cinek1dc40652014-11-12 17:53:52 +010063 private boolean mMotionCancelled;
Selim Cinek6746c282015-04-21 19:58:31 -070064 private int mTouchTargetSize;
65 private View mTargetedView;
66 private boolean mTouchSlopExeeded;
Selim Cinekbaa23272014-07-08 18:01:07 +020067 private AnimatorListenerAdapter mFlingEndListener = new AnimatorListenerAdapter() {
68 @Override
69 public void onAnimationEnd(Animator animation) {
70 mSwipeAnimator = null;
Jorim Jaggia86790b2015-04-02 16:32:29 -070071 mSwipingInProgress = false;
Selim Cinek158af6a2015-07-06 18:39:28 -070072 mTargetedView = null;
Selim Cinekbaa23272014-07-08 18:01:07 +020073 }
74 };
75 private Runnable mAnimationEndRunnable = new Runnable() {
76 @Override
77 public void run() {
78 mCallback.onAnimationToSideEnded();
79 }
80 };
81
Dave Mankoff781ef7e2019-06-28 16:33:25 -040082 KeyguardAffordanceHelper(Callback callback, Context context, FalsingManager falsingManager) {
Selim Cinekbaa23272014-07-08 18:01:07 +020083 mContext = context;
84 mCallback = callback;
Jorim Jaggief067fb2014-09-01 16:44:08 +020085 initIcons();
Selim Cinek372d1bd2015-08-14 13:19:37 -070086 updateIcon(mLeftIcon, 0.0f, mLeftIcon.getRestingAlpha(), false, false, true, false);
Selim Cinek372d1bd2015-08-14 13:19:37 -070087 updateIcon(mRightIcon, 0.0f, mRightIcon.getRestingAlpha(), false, false, true, false);
Dave Mankoff781ef7e2019-06-28 16:33:25 -040088 mFalsingManager = falsingManager;
Selim Cinekbaa23272014-07-08 18:01:07 +020089 initDimens();
Selim Cinekbaa23272014-07-08 18:01:07 +020090 }
91
92 private void initDimens() {
93 final ViewConfiguration configuration = ViewConfiguration.get(mContext);
Jorim Jaggi28f0e592014-08-05 22:03:07 +020094 mTouchSlop = configuration.getScaledPagingTouchSlop();
Selim Cinekbaa23272014-07-08 18:01:07 +020095 mMinFlingVelocity = configuration.getScaledMinimumFlingVelocity();
96 mMinTranslationAmount = mContext.getResources().getDimensionPixelSize(
97 R.dimen.keyguard_min_swipe_amount);
98 mMinBackgroundRadius = mContext.getResources().getDimensionPixelSize(
99 R.dimen.keyguard_affordance_min_background_radius);
Selim Cinek6746c282015-04-21 19:58:31 -0700100 mTouchTargetSize = mContext.getResources().getDimensionPixelSize(
101 R.dimen.keyguard_affordance_touch_target_size);
Selim Cinekbaa23272014-07-08 18:01:07 +0200102 mHintGrowAmount =
103 mContext.getResources().getDimensionPixelSize(R.dimen.hint_grow_amount_sideways);
104 mFlingAnimationUtils = new FlingAnimationUtils(mContext, 0.4f);
Selim Cinekbaa23272014-07-08 18:01:07 +0200105 }
106
Jorim Jaggief067fb2014-09-01 16:44:08 +0200107 private void initIcons() {
108 mLeftIcon = mCallback.getLeftIcon();
Jorim Jaggief067fb2014-09-01 16:44:08 +0200109 mRightIcon = mCallback.getRightIcon();
Selim Cineke70d6532015-04-24 16:46:13 -0700110 updatePreviews();
111 }
112
113 public void updatePreviews() {
Jorim Jaggief067fb2014-09-01 16:44:08 +0200114 mLeftIcon.setPreviewView(mCallback.getLeftPreview());
115 mRightIcon.setPreviewView(mCallback.getRightPreview());
116 }
117
Selim Cinekbaa23272014-07-08 18:01:07 +0200118 public boolean onTouchEvent(MotionEvent event) {
Jorim Jaggia86790b2015-04-02 16:32:29 -0700119 int action = event.getActionMasked();
Selim Cinek6746c282015-04-21 19:58:31 -0700120 if (mMotionCancelled && action != MotionEvent.ACTION_DOWN) {
Selim Cinek1dc40652014-11-12 17:53:52 +0100121 return false;
Selim Cinekbaa23272014-07-08 18:01:07 +0200122 }
Selim Cinek1dc40652014-11-12 17:53:52 +0100123 final float y = event.getY();
124 final float x = event.getX();
Selim Cinekbaa23272014-07-08 18:01:07 +0200125
126 boolean isUp = false;
Jorim Jaggia86790b2015-04-02 16:32:29 -0700127 switch (action) {
Selim Cinekbaa23272014-07-08 18:01:07 +0200128 case MotionEvent.ACTION_DOWN:
Selim Cinek6746c282015-04-21 19:58:31 -0700129 View targetView = getIconAtPosition(x, y);
130 if (targetView == null || (mTargetedView != null && mTargetedView != targetView)) {
131 mMotionCancelled = true;
132 return false;
Selim Cinekbaa23272014-07-08 18:01:07 +0200133 }
Selim Cinek6746c282015-04-21 19:58:31 -0700134 if (mTargetedView != null) {
135 cancelAnimation();
136 } else {
137 mTouchSlopExeeded = false;
138 }
Selim Cinek372d1bd2015-08-14 13:19:37 -0700139 startSwiping(targetView);
Selim Cinekbaa23272014-07-08 18:01:07 +0200140 mInitialTouchX = x;
Selim Cinek6746c282015-04-21 19:58:31 -0700141 mInitialTouchY = y;
Selim Cinekbaa23272014-07-08 18:01:07 +0200142 mTranslationOnDown = mTranslation;
143 initVelocityTracker();
144 trackMovement(event);
Selim Cinek1dc40652014-11-12 17:53:52 +0100145 mMotionCancelled = false;
Selim Cinekbaa23272014-07-08 18:01:07 +0200146 break;
Selim Cinek1dc40652014-11-12 17:53:52 +0100147 case MotionEvent.ACTION_POINTER_DOWN:
148 mMotionCancelled = true;
Selim Cinek6746c282015-04-21 19:58:31 -0700149 endMotion(true /* forceSnapBack */, x, y);
Selim Cinekbaa23272014-07-08 18:01:07 +0200150 break;
Selim Cinekbaa23272014-07-08 18:01:07 +0200151 case MotionEvent.ACTION_MOVE:
Selim Cinekbaa23272014-07-08 18:01:07 +0200152 trackMovement(event);
Selim Cinek6746c282015-04-21 19:58:31 -0700153 float xDist = x - mInitialTouchX;
154 float yDist = y - mInitialTouchY;
155 float distance = (float) Math.hypot(xDist, yDist);
156 if (!mTouchSlopExeeded && distance > mTouchSlop) {
157 mTouchSlopExeeded = true;
Selim Cinekbaa23272014-07-08 18:01:07 +0200158 }
159 if (mSwipingInProgress) {
Selim Cinek6746c282015-04-21 19:58:31 -0700160 if (mTargetedView == mRightIcon) {
161 distance = mTranslationOnDown - distance;
162 distance = Math.min(0, distance);
163 } else {
164 distance = mTranslationOnDown + distance;
165 distance = Math.max(0, distance);
166 }
Selim Cinekc5fec9a2017-07-05 16:22:53 +0200167 setTranslation(distance, false /* isReset */, false /* animateReset */);
Selim Cinekbaa23272014-07-08 18:01:07 +0200168 }
169 break;
170
171 case MotionEvent.ACTION_UP:
172 isUp = true;
173 case MotionEvent.ACTION_CANCEL:
Selim Cinek6746c282015-04-21 19:58:31 -0700174 boolean hintOnTheRight = mTargetedView == mRightIcon;
Selim Cinekbaa23272014-07-08 18:01:07 +0200175 trackMovement(event);
Selim Cinek6746c282015-04-21 19:58:31 -0700176 endMotion(!isUp, x, y);
177 if (!mTouchSlopExeeded && isUp) {
178 mCallback.onIconClicked(hintOnTheRight);
179 }
Selim Cinekbaa23272014-07-08 18:01:07 +0200180 break;
181 }
182 return true;
183 }
184
Selim Cinek372d1bd2015-08-14 13:19:37 -0700185 private void startSwiping(View targetView) {
186 mCallback.onSwipingStarted(targetView == mRightIcon);
187 mSwipingInProgress = true;
188 mTargetedView = targetView;
189 }
190
Selim Cinek6746c282015-04-21 19:58:31 -0700191 private View getIconAtPosition(float x, float y) {
192 if (leftSwipePossible() && isOnIcon(mLeftIcon, x, y)) {
193 return mLeftIcon;
194 }
195 if (rightSwipePossible() && isOnIcon(mRightIcon, x, y)) {
196 return mRightIcon;
197 }
198 return null;
199 }
200
Selim Cinek9db71052015-04-24 18:54:30 -0700201 public boolean isOnAffordanceIcon(float x, float y) {
202 return isOnIcon(mLeftIcon, x, y) || isOnIcon(mRightIcon, x, y);
203 }
204
Selim Cinek6746c282015-04-21 19:58:31 -0700205 private boolean isOnIcon(View icon, float x, float y) {
206 float iconX = icon.getX() + icon.getWidth() / 2.0f;
207 float iconY = icon.getY() + icon.getHeight() / 2.0f;
208 double distance = Math.hypot(x - iconX, y - iconY);
209 return distance <= mTouchTargetSize / 2;
210 }
211
212 private void endMotion(boolean forceSnapBack, float lastX, float lastY) {
Selim Cinek1dc40652014-11-12 17:53:52 +0100213 if (mSwipingInProgress) {
Selim Cinek6746c282015-04-21 19:58:31 -0700214 flingWithCurrentVelocity(forceSnapBack, lastX, lastY);
215 } else {
216 mTargetedView = null;
Selim Cinek1dc40652014-11-12 17:53:52 +0100217 }
218 if (mVelocityTracker != null) {
219 mVelocityTracker.recycle();
220 mVelocityTracker = null;
221 }
222 }
223
Selim Cinekbaa23272014-07-08 18:01:07 +0200224 private boolean rightSwipePossible() {
225 return mRightIcon.getVisibility() == View.VISIBLE;
226 }
227
228 private boolean leftSwipePossible() {
229 return mLeftIcon.getVisibility() == View.VISIBLE;
230 }
231
232 public boolean onInterceptTouchEvent(MotionEvent ev) {
233 return false;
234 }
235
Selim Cinek6746c282015-04-21 19:58:31 -0700236 public void startHintAnimation(boolean right,
237 Runnable onFinishedListener) {
238 cancelAnimation();
Selim Cinekbaa23272014-07-08 18:01:07 +0200239 startHintAnimationPhase1(right, onFinishedListener);
240 }
241
242 private void startHintAnimationPhase1(final boolean right, final Runnable onFinishedListener) {
243 final KeyguardAffordanceView targetView = right ? mRightIcon : mLeftIcon;
Selim Cinekbaa23272014-07-08 18:01:07 +0200244 ValueAnimator animator = getAnimatorToRadius(right, mHintGrowAmount);
245 animator.addListener(new AnimatorListenerAdapter() {
246 private boolean mCancelled;
247
248 @Override
249 public void onAnimationCancel(Animator animation) {
250 mCancelled = true;
251 }
252
253 @Override
254 public void onAnimationEnd(Animator animation) {
255 if (mCancelled) {
256 mSwipeAnimator = null;
Selim Cinek6746c282015-04-21 19:58:31 -0700257 mTargetedView = null;
Selim Cinekbaa23272014-07-08 18:01:07 +0200258 onFinishedListener.run();
Selim Cinekbaa23272014-07-08 18:01:07 +0200259 } else {
260 startUnlockHintAnimationPhase2(right, onFinishedListener);
261 }
262 }
263 });
Selim Cinekc18010f2016-01-20 13:41:30 -0800264 animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
Selim Cinekbaa23272014-07-08 18:01:07 +0200265 animator.setDuration(HINT_PHASE1_DURATION);
266 animator.start();
267 mSwipeAnimator = animator;
Selim Cinek6746c282015-04-21 19:58:31 -0700268 mTargetedView = targetView;
Selim Cinekbaa23272014-07-08 18:01:07 +0200269 }
270
271 /**
272 * Phase 2: Move back.
273 */
274 private void startUnlockHintAnimationPhase2(boolean right, final Runnable onFinishedListener) {
Selim Cinekbaa23272014-07-08 18:01:07 +0200275 ValueAnimator animator = getAnimatorToRadius(right, 0);
276 animator.addListener(new AnimatorListenerAdapter() {
277 @Override
278 public void onAnimationEnd(Animator animation) {
279 mSwipeAnimator = null;
Selim Cinek6746c282015-04-21 19:58:31 -0700280 mTargetedView = null;
Selim Cinekbaa23272014-07-08 18:01:07 +0200281 onFinishedListener.run();
282 }
Selim Cinekbaa23272014-07-08 18:01:07 +0200283 });
Selim Cinekc18010f2016-01-20 13:41:30 -0800284 animator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
Selim Cinekbaa23272014-07-08 18:01:07 +0200285 animator.setDuration(HINT_PHASE2_DURATION);
286 animator.setStartDelay(HINT_CIRCLE_OPEN_DURATION);
287 animator.start();
288 mSwipeAnimator = animator;
289 }
290
291 private ValueAnimator getAnimatorToRadius(final boolean right, int radius) {
292 final KeyguardAffordanceView targetView = right ? mRightIcon : mLeftIcon;
293 ValueAnimator animator = ValueAnimator.ofFloat(targetView.getCircleRadius(), radius);
294 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
295 @Override
296 public void onAnimationUpdate(ValueAnimator animation) {
297 float newRadius = (float) animation.getAnimatedValue();
298 targetView.setCircleRadiusWithoutAnimation(newRadius);
299 float translation = getTranslationFromRadius(newRadius);
300 mTranslation = right ? -translation : translation;
Selim Cinek6746c282015-04-21 19:58:31 -0700301 updateIconsFromTranslation(targetView);
Selim Cinekbaa23272014-07-08 18:01:07 +0200302 }
303 });
304 return animator;
305 }
306
307 private void cancelAnimation() {
308 if (mSwipeAnimator != null) {
309 mSwipeAnimator.cancel();
310 }
311 }
312
Selim Cinek6746c282015-04-21 19:58:31 -0700313 private void flingWithCurrentVelocity(boolean forceSnapBack, float lastX, float lastY) {
314 float vel = getCurrentVelocity(lastX, lastY);
Selim Cinekbaa23272014-07-08 18:01:07 +0200315
316 // We snap back if the current translation is not far enough
Adrian Roos6a70b882016-04-19 17:09:07 -0700317 boolean snapBack = false;
318 if (mCallback.needsAntiFalsing()) {
319 snapBack = snapBack || mFalsingManager.isFalseTouch();
Blazej Magnowski6dc59b42015-09-22 15:14:20 -0700320 }
Adrian Roos6a70b882016-04-19 17:09:07 -0700321 snapBack = snapBack || isBelowFalsingThreshold();
Selim Cinekbaa23272014-07-08 18:01:07 +0200322
323 // or if the velocity is in the opposite direction.
324 boolean velIsInWrongDirection = vel * mTranslation < 0;
325 snapBack |= Math.abs(vel) > mMinFlingVelocity && velIsInWrongDirection;
326 vel = snapBack ^ velIsInWrongDirection ? 0 : vel;
Selim Cinek372d1bd2015-08-14 13:19:37 -0700327 fling(vel, snapBack || forceSnapBack, mTranslation < 0);
Selim Cinekbaa23272014-07-08 18:01:07 +0200328 }
329
Selim Cinek5386fb32014-09-03 16:37:36 +0200330 private boolean isBelowFalsingThreshold() {
Selim Cinek34cf5c42014-09-26 15:39:00 +0200331 return Math.abs(mTranslation) < Math.abs(mTranslationOnDown) + getMinTranslationAmount();
332 }
333
334 private int getMinTranslationAmount() {
335 float factor = mCallback.getAffordanceFalsingFactor();
336 return (int) (mMinTranslationAmount * factor);
Selim Cinek5386fb32014-09-03 16:37:36 +0200337 }
338
Selim Cinek372d1bd2015-08-14 13:19:37 -0700339 private void fling(float vel, final boolean snapBack, boolean right) {
340 float target = right ? -mCallback.getMaxTranslationDistance()
Selim Cinek6746c282015-04-21 19:58:31 -0700341 : mCallback.getMaxTranslationDistance();
Selim Cinekbaa23272014-07-08 18:01:07 +0200342 target = snapBack ? 0 : target;
343
344 ValueAnimator animator = ValueAnimator.ofFloat(mTranslation, target);
345 mFlingAnimationUtils.apply(animator, mTranslation, target, vel);
346 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
347 @Override
348 public void onAnimationUpdate(ValueAnimator animation) {
349 mTranslation = (float) animation.getAnimatedValue();
350 }
351 });
352 animator.addListener(mFlingEndListener);
353 if (!snapBack) {
Selim Cinek372d1bd2015-08-14 13:19:37 -0700354 startFinishingCircleAnimation(vel * 0.375f, mAnimationEndRunnable, right);
355 mCallback.onAnimationToSideStarted(right, mTranslation, vel);
Selim Cinekbaa23272014-07-08 18:01:07 +0200356 } else {
357 reset(true);
358 }
359 animator.start();
360 mSwipeAnimator = animator;
Jorim Jaggia86790b2015-04-02 16:32:29 -0700361 if (snapBack) {
362 mCallback.onSwipingAborted();
363 }
Selim Cinekbaa23272014-07-08 18:01:07 +0200364 }
365
Lucas Dupin29678e62018-06-19 10:42:42 -0700366 private void startFinishingCircleAnimation(float velocity, Runnable animationEndRunnable,
Selim Cinek372d1bd2015-08-14 13:19:37 -0700367 boolean right) {
368 KeyguardAffordanceView targetView = right ? mRightIcon : mLeftIcon;
Lucas Dupin29678e62018-06-19 10:42:42 -0700369 targetView.finishAnimation(velocity, animationEndRunnable);
Selim Cinekbaa23272014-07-08 18:01:07 +0200370 }
371
Selim Cinekc5fec9a2017-07-05 16:22:53 +0200372 private void setTranslation(float translation, boolean isReset, boolean animateReset) {
Selim Cinekbaa23272014-07-08 18:01:07 +0200373 translation = rightSwipePossible() ? translation : Math.max(0, translation);
374 translation = leftSwipePossible() ? translation : Math.min(0, translation);
375 float absTranslation = Math.abs(translation);
Selim Cinekc5fec9a2017-07-05 16:22:53 +0200376 if (translation != mTranslation || isReset) {
Selim Cinekbaa23272014-07-08 18:01:07 +0200377 KeyguardAffordanceView targetView = translation > 0 ? mLeftIcon : mRightIcon;
378 KeyguardAffordanceView otherView = translation > 0 ? mRightIcon : mLeftIcon;
Selim Cinek34cf5c42014-09-26 15:39:00 +0200379 float alpha = absTranslation / getMinTranslationAmount();
Selim Cinekbaa23272014-07-08 18:01:07 +0200380
381 // We interpolate the alpha of the other icons to 0
Jorim Jaggi27c9b742015-04-09 10:34:49 -0700382 float fadeOutAlpha = 1.0f - alpha;
Selim Cinek6746c282015-04-21 19:58:31 -0700383 fadeOutAlpha = Math.max(fadeOutAlpha, 0.0f);
Selim Cinekbaa23272014-07-08 18:01:07 +0200384
385 boolean animateIcons = isReset && animateReset;
Selim Cinek372d1bd2015-08-14 13:19:37 -0700386 boolean forceNoCircleAnimation = isReset && !animateReset;
Selim Cinekbaa23272014-07-08 18:01:07 +0200387 float radius = getRadiusFromTranslation(absTranslation);
Selim Cinek5386fb32014-09-03 16:37:36 +0200388 boolean slowAnimation = isReset && isBelowFalsingThreshold();
Selim Cinekbaa23272014-07-08 18:01:07 +0200389 if (!isReset) {
Jorim Jaggi27c9b742015-04-09 10:34:49 -0700390 updateIcon(targetView, radius, alpha + fadeOutAlpha * targetView.getRestingAlpha(),
Selim Cinekc5fec9a2017-07-05 16:22:53 +0200391 false, false, false, false);
Selim Cinekbaa23272014-07-08 18:01:07 +0200392 } else {
Jorim Jaggi27c9b742015-04-09 10:34:49 -0700393 updateIcon(targetView, 0.0f, fadeOutAlpha * targetView.getRestingAlpha(),
Selim Cinekc5fec9a2017-07-05 16:22:53 +0200394 animateIcons, slowAnimation, true /* isReset */, forceNoCircleAnimation);
Selim Cinekbaa23272014-07-08 18:01:07 +0200395 }
Jorim Jaggi27c9b742015-04-09 10:34:49 -0700396 updateIcon(otherView, 0.0f, fadeOutAlpha * otherView.getRestingAlpha(),
Selim Cinekc5fec9a2017-07-05 16:22:53 +0200397 animateIcons, slowAnimation, isReset, forceNoCircleAnimation);
Selim Cinekbaa23272014-07-08 18:01:07 +0200398
399 mTranslation = translation;
400 }
401 }
402
Selim Cinek6746c282015-04-21 19:58:31 -0700403 private void updateIconsFromTranslation(KeyguardAffordanceView targetView) {
404 float absTranslation = Math.abs(mTranslation);
405 float alpha = absTranslation / getMinTranslationAmount();
Selim Cinekbaa23272014-07-08 18:01:07 +0200406
407 // We interpolate the alpha of the other icons to 0
Jorim Jaggi27c9b742015-04-09 10:34:49 -0700408 float fadeOutAlpha = 1.0f - alpha;
Selim Cinekbaa23272014-07-08 18:01:07 +0200409 fadeOutAlpha = Math.max(0.0f, fadeOutAlpha);
410
411 // We interpolate the alpha of the targetView to 1
Selim Cinekbaa23272014-07-08 18:01:07 +0200412 KeyguardAffordanceView otherView = targetView == mRightIcon ? mLeftIcon : mRightIcon;
Jorim Jaggi27c9b742015-04-09 10:34:49 -0700413 updateIconAlpha(targetView, alpha + fadeOutAlpha * targetView.getRestingAlpha(), false);
414 updateIconAlpha(otherView, fadeOutAlpha * otherView.getRestingAlpha(), false);
Selim Cinekbaa23272014-07-08 18:01:07 +0200415 }
416
417 private float getTranslationFromRadius(float circleSize) {
Selim Cinek6746c282015-04-21 19:58:31 -0700418 float translation = (circleSize - mMinBackgroundRadius)
419 / BACKGROUND_RADIUS_SCALE_FACTOR;
420 return translation > 0.0f ? translation + mTouchSlop : 0.0f;
Selim Cinekbaa23272014-07-08 18:01:07 +0200421 }
422
423 private float getRadiusFromTranslation(float translation) {
Selim Cinek6746c282015-04-21 19:58:31 -0700424 if (translation <= mTouchSlop) {
425 return 0.0f;
426 }
427 return (translation - mTouchSlop) * BACKGROUND_RADIUS_SCALE_FACTOR + mMinBackgroundRadius;
Selim Cinekbaa23272014-07-08 18:01:07 +0200428 }
429
Selim Cinekbaa23272014-07-08 18:01:07 +0200430 public void animateHideLeftRightIcon() {
Selim Cinek6746c282015-04-21 19:58:31 -0700431 cancelAnimation();
Selim Cinek372d1bd2015-08-14 13:19:37 -0700432 updateIcon(mRightIcon, 0f, 0f, true, false, false, false);
433 updateIcon(mLeftIcon, 0f, 0f, true, false, false, false);
Selim Cinekbaa23272014-07-08 18:01:07 +0200434 }
435
436 private void updateIcon(KeyguardAffordanceView view, float circleRadius, float alpha,
Selim Cinek372d1bd2015-08-14 13:19:37 -0700437 boolean animate, boolean slowRadiusAnimation, boolean force,
438 boolean forceNoCircleAnimation) {
Adrian Roosa4eba9f2015-07-22 18:13:04 -0700439 if (view.getVisibility() != View.VISIBLE && !force) {
Selim Cinekbaa23272014-07-08 18:01:07 +0200440 return;
441 }
Selim Cinek372d1bd2015-08-14 13:19:37 -0700442 if (forceNoCircleAnimation) {
443 view.setCircleRadiusWithoutAnimation(circleRadius);
444 } else {
445 view.setCircleRadius(circleRadius, slowRadiusAnimation);
446 }
Selim Cinekbaa23272014-07-08 18:01:07 +0200447 updateIconAlpha(view, alpha, animate);
448 }
449
450 private void updateIconAlpha(KeyguardAffordanceView view, float alpha, boolean animate) {
Jorim Jaggi27c9b742015-04-09 10:34:49 -0700451 float scale = getScale(alpha, view);
Selim Cinekbaa23272014-07-08 18:01:07 +0200452 alpha = Math.min(1.0f, alpha);
453 view.setImageAlpha(alpha, animate);
454 view.setImageScale(scale, animate);
455 }
456
Jorim Jaggi27c9b742015-04-09 10:34:49 -0700457 private float getScale(float alpha, KeyguardAffordanceView icon) {
458 float scale = alpha / icon.getRestingAlpha() * 0.2f +
Selim Cinekbaa23272014-07-08 18:01:07 +0200459 KeyguardAffordanceView.MIN_ICON_SCALE_AMOUNT;
460 return Math.min(scale, KeyguardAffordanceView.MAX_ICON_SCALE_AMOUNT);
461 }
462
463 private void trackMovement(MotionEvent event) {
464 if (mVelocityTracker != null) {
465 mVelocityTracker.addMovement(event);
466 }
467 }
468
469 private void initVelocityTracker() {
470 if (mVelocityTracker != null) {
471 mVelocityTracker.recycle();
472 }
473 mVelocityTracker = VelocityTracker.obtain();
474 }
475
Selim Cinek6746c282015-04-21 19:58:31 -0700476 private float getCurrentVelocity(float lastX, float lastY) {
Selim Cinekbaa23272014-07-08 18:01:07 +0200477 if (mVelocityTracker == null) {
478 return 0;
479 }
480 mVelocityTracker.computeCurrentVelocity(1000);
Selim Cinek6746c282015-04-21 19:58:31 -0700481 float aX = mVelocityTracker.getXVelocity();
482 float aY = mVelocityTracker.getYVelocity();
483 float bX = lastX - mInitialTouchX;
484 float bY = lastY - mInitialTouchY;
485 float bLen = (float) Math.hypot(bX, bY);
486 // Project the velocity onto the distance vector: a * b / |b|
487 float projectedVelocity = (aX * bX + aY * bY) / bLen;
488 if (mTargetedView == mRightIcon) {
489 projectedVelocity = -projectedVelocity;
490 }
491 return projectedVelocity;
Selim Cinekbaa23272014-07-08 18:01:07 +0200492 }
493
494 public void onConfigurationChanged() {
495 initDimens();
Jorim Jaggief067fb2014-09-01 16:44:08 +0200496 initIcons();
Selim Cinekbaa23272014-07-08 18:01:07 +0200497 }
498
Jorim Jaggi0a818222014-11-10 19:04:34 +0100499 public void onRtlPropertiesChanged() {
500 initIcons();
501 }
502
Selim Cinekbaa23272014-07-08 18:01:07 +0200503 public void reset(boolean animate) {
Selim Cinek158af6a2015-07-06 18:39:28 -0700504 cancelAnimation();
Selim Cinekc5fec9a2017-07-05 16:22:53 +0200505 setTranslation(0.0f, true /* isReset */, animate);
Jorim Jaggia86790b2015-04-02 16:32:29 -0700506 mMotionCancelled = true;
507 if (mSwipingInProgress) {
508 mCallback.onSwipingAborted();
Selim Cinek372d1bd2015-08-14 13:19:37 -0700509 mSwipingInProgress = false;
Jorim Jaggia86790b2015-04-02 16:32:29 -0700510 }
Selim Cinek372d1bd2015-08-14 13:19:37 -0700511 }
512
513 public boolean isSwipingInProgress() {
514 return mSwipingInProgress;
515 }
516
517 public void launchAffordance(boolean animate, boolean left) {
518 if (mSwipingInProgress) {
519 // We don't want to mess with the state if the user is actually swiping already.
520 return;
521 }
522 KeyguardAffordanceView targetView = left ? mLeftIcon : mRightIcon;
523 KeyguardAffordanceView otherView = left ? mRightIcon : mLeftIcon;
524 startSwiping(targetView);
Lucas Dupinf78c5402018-01-05 12:57:27 -0800525
526 // Do not animate the circle expanding if the affordance isn't visible,
527 // otherwise the circle will be meaningless.
528 if (targetView.getVisibility() != View.VISIBLE) {
529 animate = false;
530 }
531
Selim Cinek372d1bd2015-08-14 13:19:37 -0700532 if (animate) {
533 fling(0, false, !left);
534 updateIcon(otherView, 0.0f, 0, true, false, true, false);
Selim Cinek372d1bd2015-08-14 13:19:37 -0700535 } else {
536 mCallback.onAnimationToSideStarted(!left, mTranslation, 0);
537 mTranslation = left ? mCallback.getMaxTranslationDistance()
538 : mCallback.getMaxTranslationDistance();
Selim Cinek372d1bd2015-08-14 13:19:37 -0700539 updateIcon(otherView, 0.0f, 0.0f, false, false, true, false);
540 targetView.instantFinishAnimation();
541 mFlingEndListener.onAnimationEnd(null);
542 mAnimationEndRunnable.run();
543 }
Selim Cinekbaa23272014-07-08 18:01:07 +0200544 }
545
546 public interface Callback {
547
548 /**
549 * Notifies the callback when an animation to a side page was started.
550 *
551 * @param rightPage Is the page animated to the right page?
552 */
Christoph Studerb0183992014-12-22 21:02:26 +0100553 void onAnimationToSideStarted(boolean rightPage, float translation, float vel);
Selim Cinekbaa23272014-07-08 18:01:07 +0200554
555 /**
556 * Notifies the callback the animation to a side page has ended.
557 */
558 void onAnimationToSideEnded();
559
Selim Cinek6746c282015-04-21 19:58:31 -0700560 float getMaxTranslationDistance();
Selim Cinekbaa23272014-07-08 18:01:07 +0200561
Jorim Jaggid9449862015-05-29 14:49:08 -0700562 void onSwipingStarted(boolean rightIcon);
Jorim Jaggia86790b2015-04-02 16:32:29 -0700563
564 void onSwipingAborted();
Selim Cinekbaa23272014-07-08 18:01:07 +0200565
Selim Cinek6746c282015-04-21 19:58:31 -0700566 void onIconClicked(boolean rightIcon);
567
Selim Cinekbaa23272014-07-08 18:01:07 +0200568 KeyguardAffordanceView getLeftIcon();
569
Selim Cinekbaa23272014-07-08 18:01:07 +0200570 KeyguardAffordanceView getRightIcon();
Jorim Jaggib6cdcbc2014-07-25 21:58:29 +0200571
572 View getLeftPreview();
573
574 View getRightPreview();
Selim Cinek34cf5c42014-09-26 15:39:00 +0200575
576 /**
577 * @return The factor the minimum swipe amount should be multiplied with.
578 */
579 float getAffordanceFalsingFactor();
Adrian Roos6a70b882016-04-19 17:09:07 -0700580
581 boolean needsAntiFalsing();
Selim Cinekbaa23272014-07-08 18:01:07 +0200582 }
583}