blob: 1a9a40b243326be576612d712e6ac38191e78a13 [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;
Blazej Magnowski6dc59b42015-09-22 15:14:20 -070030import com.android.systemui.classifier.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
39 public static final float SWIPE_RESTING_ALPHA_AMOUNT = 0.5f;
40 public static final long HINT_PHASE1_DURATION = 200;
41 private static final long HINT_PHASE2_DURATION = 350;
Selim Cinek6746c282015-04-21 19:58:31 -070042 private static final float BACKGROUND_RADIUS_SCALE_FACTOR = 0.25f;
Selim Cinekbaa23272014-07-08 18:01:07 +020043 private static final int HINT_CIRCLE_OPEN_DURATION = 500;
44
45 private final Context mContext;
Adrian Roos6a70b882016-04-19 17:09:07 -070046 private final Callback mCallback;
Selim Cinekbaa23272014-07-08 18:01:07 +020047
48 private FlingAnimationUtils mFlingAnimationUtils;
Selim Cinekbaa23272014-07-08 18:01:07 +020049 private VelocityTracker mVelocityTracker;
50 private boolean mSwipingInProgress;
51 private float mInitialTouchX;
52 private float mInitialTouchY;
53 private float mTranslation;
54 private float mTranslationOnDown;
55 private int mTouchSlop;
56 private int mMinTranslationAmount;
57 private int mMinFlingVelocity;
58 private int mHintGrowAmount;
Jorim Jaggief067fb2014-09-01 16:44:08 +020059 private KeyguardAffordanceView mLeftIcon;
60 private KeyguardAffordanceView mCenterIcon;
61 private KeyguardAffordanceView mRightIcon;
Selim Cinekbaa23272014-07-08 18:01:07 +020062 private Animator mSwipeAnimator;
Blazej Magnowski6dc59b42015-09-22 15:14:20 -070063 private FalsingManager mFalsingManager;
Selim Cinekbaa23272014-07-08 18:01:07 +020064 private int mMinBackgroundRadius;
Selim Cinek1dc40652014-11-12 17:53:52 +010065 private boolean mMotionCancelled;
Selim Cinek6746c282015-04-21 19:58:31 -070066 private int mTouchTargetSize;
67 private View mTargetedView;
68 private boolean mTouchSlopExeeded;
Selim Cinekbaa23272014-07-08 18:01:07 +020069 private AnimatorListenerAdapter mFlingEndListener = new AnimatorListenerAdapter() {
70 @Override
71 public void onAnimationEnd(Animator animation) {
72 mSwipeAnimator = null;
Jorim Jaggia86790b2015-04-02 16:32:29 -070073 mSwipingInProgress = false;
Selim Cinek158af6a2015-07-06 18:39:28 -070074 mTargetedView = null;
Selim Cinekbaa23272014-07-08 18:01:07 +020075 }
76 };
77 private Runnable mAnimationEndRunnable = new Runnable() {
78 @Override
79 public void run() {
80 mCallback.onAnimationToSideEnded();
81 }
82 };
83
84 KeyguardAffordanceHelper(Callback callback, Context context) {
85 mContext = context;
86 mCallback = callback;
Jorim Jaggief067fb2014-09-01 16:44:08 +020087 initIcons();
Selim Cinek372d1bd2015-08-14 13:19:37 -070088 updateIcon(mLeftIcon, 0.0f, mLeftIcon.getRestingAlpha(), false, false, true, false);
89 updateIcon(mCenterIcon, 0.0f, mCenterIcon.getRestingAlpha(), false, false, true, false);
90 updateIcon(mRightIcon, 0.0f, mRightIcon.getRestingAlpha(), false, false, true, false);
Selim Cinekbaa23272014-07-08 18:01:07 +020091 initDimens();
Selim Cinekbaa23272014-07-08 18:01:07 +020092 }
93
94 private void initDimens() {
95 final ViewConfiguration configuration = ViewConfiguration.get(mContext);
Jorim Jaggi28f0e592014-08-05 22:03:07 +020096 mTouchSlop = configuration.getScaledPagingTouchSlop();
Selim Cinekbaa23272014-07-08 18:01:07 +020097 mMinFlingVelocity = configuration.getScaledMinimumFlingVelocity();
98 mMinTranslationAmount = mContext.getResources().getDimensionPixelSize(
99 R.dimen.keyguard_min_swipe_amount);
100 mMinBackgroundRadius = mContext.getResources().getDimensionPixelSize(
101 R.dimen.keyguard_affordance_min_background_radius);
Selim Cinek6746c282015-04-21 19:58:31 -0700102 mTouchTargetSize = mContext.getResources().getDimensionPixelSize(
103 R.dimen.keyguard_affordance_touch_target_size);
Selim Cinekbaa23272014-07-08 18:01:07 +0200104 mHintGrowAmount =
105 mContext.getResources().getDimensionPixelSize(R.dimen.hint_grow_amount_sideways);
106 mFlingAnimationUtils = new FlingAnimationUtils(mContext, 0.4f);
Blazej Magnowski6dc59b42015-09-22 15:14:20 -0700107 mFalsingManager = FalsingManager.getInstance(mContext);
Selim Cinekbaa23272014-07-08 18:01:07 +0200108 }
109
Jorim Jaggief067fb2014-09-01 16:44:08 +0200110 private void initIcons() {
111 mLeftIcon = mCallback.getLeftIcon();
Jorim Jaggief067fb2014-09-01 16:44:08 +0200112 mCenterIcon = mCallback.getCenterIcon();
113 mRightIcon = mCallback.getRightIcon();
Selim Cineke70d6532015-04-24 16:46:13 -0700114 updatePreviews();
115 }
116
117 public void updatePreviews() {
Jorim Jaggief067fb2014-09-01 16:44:08 +0200118 mLeftIcon.setPreviewView(mCallback.getLeftPreview());
119 mRightIcon.setPreviewView(mCallback.getRightPreview());
120 }
121
Selim Cinekbaa23272014-07-08 18:01:07 +0200122 public boolean onTouchEvent(MotionEvent event) {
Jorim Jaggia86790b2015-04-02 16:32:29 -0700123 int action = event.getActionMasked();
Selim Cinek6746c282015-04-21 19:58:31 -0700124 if (mMotionCancelled && action != MotionEvent.ACTION_DOWN) {
Selim Cinek1dc40652014-11-12 17:53:52 +0100125 return false;
Selim Cinekbaa23272014-07-08 18:01:07 +0200126 }
Selim Cinek1dc40652014-11-12 17:53:52 +0100127 final float y = event.getY();
128 final float x = event.getX();
Selim Cinekbaa23272014-07-08 18:01:07 +0200129
130 boolean isUp = false;
Jorim Jaggia86790b2015-04-02 16:32:29 -0700131 switch (action) {
Selim Cinekbaa23272014-07-08 18:01:07 +0200132 case MotionEvent.ACTION_DOWN:
Selim Cinek6746c282015-04-21 19:58:31 -0700133 View targetView = getIconAtPosition(x, y);
134 if (targetView == null || (mTargetedView != null && mTargetedView != targetView)) {
135 mMotionCancelled = true;
136 return false;
Selim Cinekbaa23272014-07-08 18:01:07 +0200137 }
Selim Cinek6746c282015-04-21 19:58:31 -0700138 if (mTargetedView != null) {
139 cancelAnimation();
140 } else {
141 mTouchSlopExeeded = false;
142 }
Selim Cinek372d1bd2015-08-14 13:19:37 -0700143 startSwiping(targetView);
Selim Cinekbaa23272014-07-08 18:01:07 +0200144 mInitialTouchX = x;
Selim Cinek6746c282015-04-21 19:58:31 -0700145 mInitialTouchY = y;
Selim Cinekbaa23272014-07-08 18:01:07 +0200146 mTranslationOnDown = mTranslation;
147 initVelocityTracker();
148 trackMovement(event);
Selim Cinek1dc40652014-11-12 17:53:52 +0100149 mMotionCancelled = false;
Selim Cinekbaa23272014-07-08 18:01:07 +0200150 break;
Selim Cinek1dc40652014-11-12 17:53:52 +0100151 case MotionEvent.ACTION_POINTER_DOWN:
152 mMotionCancelled = true;
Selim Cinek6746c282015-04-21 19:58:31 -0700153 endMotion(true /* forceSnapBack */, x, y);
Selim Cinekbaa23272014-07-08 18:01:07 +0200154 break;
Selim Cinekbaa23272014-07-08 18:01:07 +0200155 case MotionEvent.ACTION_MOVE:
Selim Cinekbaa23272014-07-08 18:01:07 +0200156 trackMovement(event);
Selim Cinek6746c282015-04-21 19:58:31 -0700157 float xDist = x - mInitialTouchX;
158 float yDist = y - mInitialTouchY;
159 float distance = (float) Math.hypot(xDist, yDist);
160 if (!mTouchSlopExeeded && distance > mTouchSlop) {
161 mTouchSlopExeeded = true;
Selim Cinekbaa23272014-07-08 18:01:07 +0200162 }
163 if (mSwipingInProgress) {
Selim Cinek6746c282015-04-21 19:58:31 -0700164 if (mTargetedView == mRightIcon) {
165 distance = mTranslationOnDown - distance;
166 distance = Math.min(0, distance);
167 } else {
168 distance = mTranslationOnDown + distance;
169 distance = Math.max(0, distance);
170 }
171 setTranslation(distance, false /* isReset */, false /* animateReset */);
Selim Cinekbaa23272014-07-08 18:01:07 +0200172 }
173 break;
174
175 case MotionEvent.ACTION_UP:
176 isUp = true;
177 case MotionEvent.ACTION_CANCEL:
Selim Cinek6746c282015-04-21 19:58:31 -0700178 boolean hintOnTheRight = mTargetedView == mRightIcon;
Selim Cinekbaa23272014-07-08 18:01:07 +0200179 trackMovement(event);
Selim Cinek6746c282015-04-21 19:58:31 -0700180 endMotion(!isUp, x, y);
181 if (!mTouchSlopExeeded && isUp) {
182 mCallback.onIconClicked(hintOnTheRight);
183 }
Selim Cinekbaa23272014-07-08 18:01:07 +0200184 break;
185 }
186 return true;
187 }
188
Selim Cinek372d1bd2015-08-14 13:19:37 -0700189 private void startSwiping(View targetView) {
190 mCallback.onSwipingStarted(targetView == mRightIcon);
191 mSwipingInProgress = true;
192 mTargetedView = targetView;
193 }
194
Selim Cinek6746c282015-04-21 19:58:31 -0700195 private View getIconAtPosition(float x, float y) {
196 if (leftSwipePossible() && isOnIcon(mLeftIcon, x, y)) {
197 return mLeftIcon;
198 }
199 if (rightSwipePossible() && isOnIcon(mRightIcon, x, y)) {
200 return mRightIcon;
201 }
202 return null;
203 }
204
Selim Cinek9db71052015-04-24 18:54:30 -0700205 public boolean isOnAffordanceIcon(float x, float y) {
206 return isOnIcon(mLeftIcon, x, y) || isOnIcon(mRightIcon, x, y);
207 }
208
Selim Cinek6746c282015-04-21 19:58:31 -0700209 private boolean isOnIcon(View icon, float x, float y) {
210 float iconX = icon.getX() + icon.getWidth() / 2.0f;
211 float iconY = icon.getY() + icon.getHeight() / 2.0f;
212 double distance = Math.hypot(x - iconX, y - iconY);
213 return distance <= mTouchTargetSize / 2;
214 }
215
216 private void endMotion(boolean forceSnapBack, float lastX, float lastY) {
Selim Cinek1dc40652014-11-12 17:53:52 +0100217 if (mSwipingInProgress) {
Selim Cinek6746c282015-04-21 19:58:31 -0700218 flingWithCurrentVelocity(forceSnapBack, lastX, lastY);
219 } else {
220 mTargetedView = null;
Selim Cinek1dc40652014-11-12 17:53:52 +0100221 }
222 if (mVelocityTracker != null) {
223 mVelocityTracker.recycle();
224 mVelocityTracker = null;
225 }
226 }
227
Selim Cinekbaa23272014-07-08 18:01:07 +0200228 private boolean rightSwipePossible() {
229 return mRightIcon.getVisibility() == View.VISIBLE;
230 }
231
232 private boolean leftSwipePossible() {
233 return mLeftIcon.getVisibility() == View.VISIBLE;
234 }
235
236 public boolean onInterceptTouchEvent(MotionEvent ev) {
237 return false;
238 }
239
Selim Cinek6746c282015-04-21 19:58:31 -0700240 public void startHintAnimation(boolean right,
241 Runnable onFinishedListener) {
242 cancelAnimation();
Selim Cinekbaa23272014-07-08 18:01:07 +0200243 startHintAnimationPhase1(right, onFinishedListener);
244 }
245
246 private void startHintAnimationPhase1(final boolean right, final Runnable onFinishedListener) {
247 final KeyguardAffordanceView targetView = right ? mRightIcon : mLeftIcon;
Selim Cinekbaa23272014-07-08 18:01:07 +0200248 ValueAnimator animator = getAnimatorToRadius(right, mHintGrowAmount);
249 animator.addListener(new AnimatorListenerAdapter() {
250 private boolean mCancelled;
251
252 @Override
253 public void onAnimationCancel(Animator animation) {
254 mCancelled = true;
255 }
256
257 @Override
258 public void onAnimationEnd(Animator animation) {
259 if (mCancelled) {
260 mSwipeAnimator = null;
Selim Cinek6746c282015-04-21 19:58:31 -0700261 mTargetedView = null;
Selim Cinekbaa23272014-07-08 18:01:07 +0200262 onFinishedListener.run();
Selim Cinekbaa23272014-07-08 18:01:07 +0200263 } else {
264 startUnlockHintAnimationPhase2(right, onFinishedListener);
265 }
266 }
267 });
Selim Cinekc18010f2016-01-20 13:41:30 -0800268 animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
Selim Cinekbaa23272014-07-08 18:01:07 +0200269 animator.setDuration(HINT_PHASE1_DURATION);
270 animator.start();
271 mSwipeAnimator = animator;
Selim Cinek6746c282015-04-21 19:58:31 -0700272 mTargetedView = targetView;
Selim Cinekbaa23272014-07-08 18:01:07 +0200273 }
274
275 /**
276 * Phase 2: Move back.
277 */
278 private void startUnlockHintAnimationPhase2(boolean right, final Runnable onFinishedListener) {
Selim Cinekbaa23272014-07-08 18:01:07 +0200279 ValueAnimator animator = getAnimatorToRadius(right, 0);
280 animator.addListener(new AnimatorListenerAdapter() {
281 @Override
282 public void onAnimationEnd(Animator animation) {
283 mSwipeAnimator = null;
Selim Cinek6746c282015-04-21 19:58:31 -0700284 mTargetedView = null;
Selim Cinekbaa23272014-07-08 18:01:07 +0200285 onFinishedListener.run();
286 }
Selim Cinekbaa23272014-07-08 18:01:07 +0200287 });
Selim Cinekc18010f2016-01-20 13:41:30 -0800288 animator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
Selim Cinekbaa23272014-07-08 18:01:07 +0200289 animator.setDuration(HINT_PHASE2_DURATION);
290 animator.setStartDelay(HINT_CIRCLE_OPEN_DURATION);
291 animator.start();
292 mSwipeAnimator = animator;
293 }
294
295 private ValueAnimator getAnimatorToRadius(final boolean right, int radius) {
296 final KeyguardAffordanceView targetView = right ? mRightIcon : mLeftIcon;
297 ValueAnimator animator = ValueAnimator.ofFloat(targetView.getCircleRadius(), radius);
298 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
299 @Override
300 public void onAnimationUpdate(ValueAnimator animation) {
301 float newRadius = (float) animation.getAnimatedValue();
302 targetView.setCircleRadiusWithoutAnimation(newRadius);
303 float translation = getTranslationFromRadius(newRadius);
304 mTranslation = right ? -translation : translation;
Selim Cinek6746c282015-04-21 19:58:31 -0700305 updateIconsFromTranslation(targetView);
Selim Cinekbaa23272014-07-08 18:01:07 +0200306 }
307 });
308 return animator;
309 }
310
311 private void cancelAnimation() {
312 if (mSwipeAnimator != null) {
313 mSwipeAnimator.cancel();
314 }
315 }
316
Selim Cinek6746c282015-04-21 19:58:31 -0700317 private void flingWithCurrentVelocity(boolean forceSnapBack, float lastX, float lastY) {
318 float vel = getCurrentVelocity(lastX, lastY);
Selim Cinekbaa23272014-07-08 18:01:07 +0200319
320 // We snap back if the current translation is not far enough
Adrian Roos6a70b882016-04-19 17:09:07 -0700321 boolean snapBack = false;
322 if (mCallback.needsAntiFalsing()) {
323 snapBack = snapBack || mFalsingManager.isFalseTouch();
Blazej Magnowski6dc59b42015-09-22 15:14:20 -0700324 }
Adrian Roos6a70b882016-04-19 17:09:07 -0700325 snapBack = snapBack || isBelowFalsingThreshold();
Selim Cinekbaa23272014-07-08 18:01:07 +0200326
327 // or if the velocity is in the opposite direction.
328 boolean velIsInWrongDirection = vel * mTranslation < 0;
329 snapBack |= Math.abs(vel) > mMinFlingVelocity && velIsInWrongDirection;
330 vel = snapBack ^ velIsInWrongDirection ? 0 : vel;
Selim Cinek372d1bd2015-08-14 13:19:37 -0700331 fling(vel, snapBack || forceSnapBack, mTranslation < 0);
Selim Cinekbaa23272014-07-08 18:01:07 +0200332 }
333
Selim Cinek5386fb32014-09-03 16:37:36 +0200334 private boolean isBelowFalsingThreshold() {
Selim Cinek34cf5c42014-09-26 15:39:00 +0200335 return Math.abs(mTranslation) < Math.abs(mTranslationOnDown) + getMinTranslationAmount();
336 }
337
338 private int getMinTranslationAmount() {
339 float factor = mCallback.getAffordanceFalsingFactor();
340 return (int) (mMinTranslationAmount * factor);
Selim Cinek5386fb32014-09-03 16:37:36 +0200341 }
342
Selim Cinek372d1bd2015-08-14 13:19:37 -0700343 private void fling(float vel, final boolean snapBack, boolean right) {
344 float target = right ? -mCallback.getMaxTranslationDistance()
Selim Cinek6746c282015-04-21 19:58:31 -0700345 : mCallback.getMaxTranslationDistance();
Selim Cinekbaa23272014-07-08 18:01:07 +0200346 target = snapBack ? 0 : target;
347
348 ValueAnimator animator = ValueAnimator.ofFloat(mTranslation, target);
349 mFlingAnimationUtils.apply(animator, mTranslation, target, vel);
350 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
351 @Override
352 public void onAnimationUpdate(ValueAnimator animation) {
353 mTranslation = (float) animation.getAnimatedValue();
354 }
355 });
356 animator.addListener(mFlingEndListener);
357 if (!snapBack) {
Selim Cinek372d1bd2015-08-14 13:19:37 -0700358 startFinishingCircleAnimation(vel * 0.375f, mAnimationEndRunnable, right);
359 mCallback.onAnimationToSideStarted(right, mTranslation, vel);
Selim Cinekbaa23272014-07-08 18:01:07 +0200360 } else {
361 reset(true);
362 }
363 animator.start();
364 mSwipeAnimator = animator;
Jorim Jaggia86790b2015-04-02 16:32:29 -0700365 if (snapBack) {
366 mCallback.onSwipingAborted();
367 }
Selim Cinekbaa23272014-07-08 18:01:07 +0200368 }
369
Selim Cinek372d1bd2015-08-14 13:19:37 -0700370 private void startFinishingCircleAnimation(float velocity, Runnable mAnimationEndRunnable,
371 boolean right) {
372 KeyguardAffordanceView targetView = right ? mRightIcon : mLeftIcon;
Selim Cinekbaa23272014-07-08 18:01:07 +0200373 targetView.finishAnimation(velocity, mAnimationEndRunnable);
374 }
375
376 private void setTranslation(float translation, boolean isReset, boolean animateReset) {
377 translation = rightSwipePossible() ? translation : Math.max(0, translation);
378 translation = leftSwipePossible() ? translation : Math.min(0, translation);
379 float absTranslation = Math.abs(translation);
Selim Cinekbaa23272014-07-08 18:01:07 +0200380 if (translation != mTranslation || isReset) {
381 KeyguardAffordanceView targetView = translation > 0 ? mLeftIcon : mRightIcon;
382 KeyguardAffordanceView otherView = translation > 0 ? mRightIcon : mLeftIcon;
Selim Cinek34cf5c42014-09-26 15:39:00 +0200383 float alpha = absTranslation / getMinTranslationAmount();
Selim Cinekbaa23272014-07-08 18:01:07 +0200384
385 // We interpolate the alpha of the other icons to 0
Jorim Jaggi27c9b742015-04-09 10:34:49 -0700386 float fadeOutAlpha = 1.0f - alpha;
Selim Cinek6746c282015-04-21 19:58:31 -0700387 fadeOutAlpha = Math.max(fadeOutAlpha, 0.0f);
Selim Cinekbaa23272014-07-08 18:01:07 +0200388
389 boolean animateIcons = isReset && animateReset;
Selim Cinek372d1bd2015-08-14 13:19:37 -0700390 boolean forceNoCircleAnimation = isReset && !animateReset;
Selim Cinekbaa23272014-07-08 18:01:07 +0200391 float radius = getRadiusFromTranslation(absTranslation);
Selim Cinek5386fb32014-09-03 16:37:36 +0200392 boolean slowAnimation = isReset && isBelowFalsingThreshold();
Selim Cinekbaa23272014-07-08 18:01:07 +0200393 if (!isReset) {
Jorim Jaggi27c9b742015-04-09 10:34:49 -0700394 updateIcon(targetView, radius, alpha + fadeOutAlpha * targetView.getRestingAlpha(),
Selim Cinek372d1bd2015-08-14 13:19:37 -0700395 false, false, false, false);
Selim Cinekbaa23272014-07-08 18:01:07 +0200396 } else {
Jorim Jaggi27c9b742015-04-09 10:34:49 -0700397 updateIcon(targetView, 0.0f, fadeOutAlpha * targetView.getRestingAlpha(),
Selim Cinek372d1bd2015-08-14 13:19:37 -0700398 animateIcons, slowAnimation, false, forceNoCircleAnimation);
Selim Cinekbaa23272014-07-08 18:01:07 +0200399 }
Jorim Jaggi27c9b742015-04-09 10:34:49 -0700400 updateIcon(otherView, 0.0f, fadeOutAlpha * otherView.getRestingAlpha(),
Selim Cinek372d1bd2015-08-14 13:19:37 -0700401 animateIcons, slowAnimation, false, forceNoCircleAnimation);
Jorim Jaggi27c9b742015-04-09 10:34:49 -0700402 updateIcon(mCenterIcon, 0.0f, fadeOutAlpha * mCenterIcon.getRestingAlpha(),
Selim Cinek372d1bd2015-08-14 13:19:37 -0700403 animateIcons, slowAnimation, false, forceNoCircleAnimation);
Selim Cinekbaa23272014-07-08 18:01:07 +0200404
405 mTranslation = translation;
406 }
407 }
408
Selim Cinek6746c282015-04-21 19:58:31 -0700409 private void updateIconsFromTranslation(KeyguardAffordanceView targetView) {
410 float absTranslation = Math.abs(mTranslation);
411 float alpha = absTranslation / getMinTranslationAmount();
Selim Cinekbaa23272014-07-08 18:01:07 +0200412
413 // We interpolate the alpha of the other icons to 0
Jorim Jaggi27c9b742015-04-09 10:34:49 -0700414 float fadeOutAlpha = 1.0f - alpha;
Selim Cinekbaa23272014-07-08 18:01:07 +0200415 fadeOutAlpha = Math.max(0.0f, fadeOutAlpha);
416
417 // We interpolate the alpha of the targetView to 1
Selim Cinekbaa23272014-07-08 18:01:07 +0200418 KeyguardAffordanceView otherView = targetView == mRightIcon ? mLeftIcon : mRightIcon;
Jorim Jaggi27c9b742015-04-09 10:34:49 -0700419 updateIconAlpha(targetView, alpha + fadeOutAlpha * targetView.getRestingAlpha(), false);
420 updateIconAlpha(otherView, fadeOutAlpha * otherView.getRestingAlpha(), false);
421 updateIconAlpha(mCenterIcon, fadeOutAlpha * mCenterIcon.getRestingAlpha(), false);
Selim Cinekbaa23272014-07-08 18:01:07 +0200422 }
423
424 private float getTranslationFromRadius(float circleSize) {
Selim Cinek6746c282015-04-21 19:58:31 -0700425 float translation = (circleSize - mMinBackgroundRadius)
426 / BACKGROUND_RADIUS_SCALE_FACTOR;
427 return translation > 0.0f ? translation + mTouchSlop : 0.0f;
Selim Cinekbaa23272014-07-08 18:01:07 +0200428 }
429
430 private float getRadiusFromTranslation(float translation) {
Selim Cinek6746c282015-04-21 19:58:31 -0700431 if (translation <= mTouchSlop) {
432 return 0.0f;
433 }
434 return (translation - mTouchSlop) * BACKGROUND_RADIUS_SCALE_FACTOR + mMinBackgroundRadius;
Selim Cinekbaa23272014-07-08 18:01:07 +0200435 }
436
Selim Cinekbaa23272014-07-08 18:01:07 +0200437 public void animateHideLeftRightIcon() {
Selim Cinek6746c282015-04-21 19:58:31 -0700438 cancelAnimation();
Selim Cinek372d1bd2015-08-14 13:19:37 -0700439 updateIcon(mRightIcon, 0f, 0f, true, false, false, false);
440 updateIcon(mLeftIcon, 0f, 0f, true, false, false, false);
Selim Cinekbaa23272014-07-08 18:01:07 +0200441 }
442
443 private void updateIcon(KeyguardAffordanceView view, float circleRadius, float alpha,
Selim Cinek372d1bd2015-08-14 13:19:37 -0700444 boolean animate, boolean slowRadiusAnimation, boolean force,
445 boolean forceNoCircleAnimation) {
Adrian Roosa4eba9f2015-07-22 18:13:04 -0700446 if (view.getVisibility() != View.VISIBLE && !force) {
Selim Cinekbaa23272014-07-08 18:01:07 +0200447 return;
448 }
Selim Cinek372d1bd2015-08-14 13:19:37 -0700449 if (forceNoCircleAnimation) {
450 view.setCircleRadiusWithoutAnimation(circleRadius);
451 } else {
452 view.setCircleRadius(circleRadius, slowRadiusAnimation);
453 }
Selim Cinekbaa23272014-07-08 18:01:07 +0200454 updateIconAlpha(view, alpha, animate);
455 }
456
457 private void updateIconAlpha(KeyguardAffordanceView view, float alpha, boolean animate) {
Jorim Jaggi27c9b742015-04-09 10:34:49 -0700458 float scale = getScale(alpha, view);
Selim Cinekbaa23272014-07-08 18:01:07 +0200459 alpha = Math.min(1.0f, alpha);
460 view.setImageAlpha(alpha, animate);
461 view.setImageScale(scale, animate);
462 }
463
Jorim Jaggi27c9b742015-04-09 10:34:49 -0700464 private float getScale(float alpha, KeyguardAffordanceView icon) {
465 float scale = alpha / icon.getRestingAlpha() * 0.2f +
Selim Cinekbaa23272014-07-08 18:01:07 +0200466 KeyguardAffordanceView.MIN_ICON_SCALE_AMOUNT;
467 return Math.min(scale, KeyguardAffordanceView.MAX_ICON_SCALE_AMOUNT);
468 }
469
470 private void trackMovement(MotionEvent event) {
471 if (mVelocityTracker != null) {
472 mVelocityTracker.addMovement(event);
473 }
474 }
475
476 private void initVelocityTracker() {
477 if (mVelocityTracker != null) {
478 mVelocityTracker.recycle();
479 }
480 mVelocityTracker = VelocityTracker.obtain();
481 }
482
Selim Cinek6746c282015-04-21 19:58:31 -0700483 private float getCurrentVelocity(float lastX, float lastY) {
Selim Cinekbaa23272014-07-08 18:01:07 +0200484 if (mVelocityTracker == null) {
485 return 0;
486 }
487 mVelocityTracker.computeCurrentVelocity(1000);
Selim Cinek6746c282015-04-21 19:58:31 -0700488 float aX = mVelocityTracker.getXVelocity();
489 float aY = mVelocityTracker.getYVelocity();
490 float bX = lastX - mInitialTouchX;
491 float bY = lastY - mInitialTouchY;
492 float bLen = (float) Math.hypot(bX, bY);
493 // Project the velocity onto the distance vector: a * b / |b|
494 float projectedVelocity = (aX * bX + aY * bY) / bLen;
495 if (mTargetedView == mRightIcon) {
496 projectedVelocity = -projectedVelocity;
497 }
498 return projectedVelocity;
Selim Cinekbaa23272014-07-08 18:01:07 +0200499 }
500
501 public void onConfigurationChanged() {
502 initDimens();
Jorim Jaggief067fb2014-09-01 16:44:08 +0200503 initIcons();
Selim Cinekbaa23272014-07-08 18:01:07 +0200504 }
505
Jorim Jaggi0a818222014-11-10 19:04:34 +0100506 public void onRtlPropertiesChanged() {
507 initIcons();
508 }
509
Selim Cinekbaa23272014-07-08 18:01:07 +0200510 public void reset(boolean animate) {
Selim Cinek158af6a2015-07-06 18:39:28 -0700511 cancelAnimation();
Selim Cinekbaa23272014-07-08 18:01:07 +0200512 setTranslation(0.0f, true, animate);
Jorim Jaggia86790b2015-04-02 16:32:29 -0700513 mMotionCancelled = true;
514 if (mSwipingInProgress) {
515 mCallback.onSwipingAborted();
Selim Cinek372d1bd2015-08-14 13:19:37 -0700516 mSwipingInProgress = false;
Jorim Jaggia86790b2015-04-02 16:32:29 -0700517 }
Selim Cinek372d1bd2015-08-14 13:19:37 -0700518 }
519
520 public boolean isSwipingInProgress() {
521 return mSwipingInProgress;
522 }
523
524 public void launchAffordance(boolean animate, boolean left) {
525 if (mSwipingInProgress) {
526 // We don't want to mess with the state if the user is actually swiping already.
527 return;
528 }
529 KeyguardAffordanceView targetView = left ? mLeftIcon : mRightIcon;
530 KeyguardAffordanceView otherView = left ? mRightIcon : mLeftIcon;
531 startSwiping(targetView);
532 if (animate) {
533 fling(0, false, !left);
534 updateIcon(otherView, 0.0f, 0, true, false, true, false);
535 updateIcon(mCenterIcon, 0.0f, 0, true, false, true, false);
536 } else {
537 mCallback.onAnimationToSideStarted(!left, mTranslation, 0);
538 mTranslation = left ? mCallback.getMaxTranslationDistance()
539 : mCallback.getMaxTranslationDistance();
540 updateIcon(mCenterIcon, 0.0f, 0.0f, false, false, true, false);
541 updateIcon(otherView, 0.0f, 0.0f, false, false, true, false);
542 targetView.instantFinishAnimation();
543 mFlingEndListener.onAnimationEnd(null);
544 mAnimationEndRunnable.run();
545 }
Selim Cinekbaa23272014-07-08 18:01:07 +0200546 }
547
548 public interface Callback {
549
550 /**
551 * Notifies the callback when an animation to a side page was started.
552 *
553 * @param rightPage Is the page animated to the right page?
554 */
Christoph Studerb0183992014-12-22 21:02:26 +0100555 void onAnimationToSideStarted(boolean rightPage, float translation, float vel);
Selim Cinekbaa23272014-07-08 18:01:07 +0200556
557 /**
558 * Notifies the callback the animation to a side page has ended.
559 */
560 void onAnimationToSideEnded();
561
Selim Cinek6746c282015-04-21 19:58:31 -0700562 float getMaxTranslationDistance();
Selim Cinekbaa23272014-07-08 18:01:07 +0200563
Jorim Jaggid9449862015-05-29 14:49:08 -0700564 void onSwipingStarted(boolean rightIcon);
Jorim Jaggia86790b2015-04-02 16:32:29 -0700565
566 void onSwipingAborted();
Selim Cinekbaa23272014-07-08 18:01:07 +0200567
Selim Cinek6746c282015-04-21 19:58:31 -0700568 void onIconClicked(boolean rightIcon);
569
Selim Cinekbaa23272014-07-08 18:01:07 +0200570 KeyguardAffordanceView getLeftIcon();
571
572 KeyguardAffordanceView getCenterIcon();
573
574 KeyguardAffordanceView getRightIcon();
Jorim Jaggib6cdcbc2014-07-25 21:58:29 +0200575
576 View getLeftPreview();
577
578 View getRightPreview();
Selim Cinek34cf5c42014-09-26 15:39:00 +0200579
580 /**
581 * @return The factor the minimum swipe amount should be multiplied with.
582 */
583 float getAffordanceFalsingFactor();
Adrian Roos6a70b882016-04-19 17:09:07 -0700584
585 boolean needsAntiFalsing();
Selim Cinekbaa23272014-07-08 18:01:07 +0200586 }
587}