blob: 4691a31fad21ca3a7d52a7c709a003048c23c563 [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 Mankoffdde5ee62019-05-02 17:36:11 -040030import com.android.systemui.classifier.FalsingManagerFactory;
Dave Mankoff468d4f62019-05-08 14:56:29 -040031import com.android.systemui.plugins.FalsingManager;
Selim Cinekbaa23272014-07-08 18:01:07 +020032import com.android.systemui.statusbar.FlingAnimationUtils;
33import com.android.systemui.statusbar.KeyguardAffordanceView;
34
35/**
36 * A touch handler of the keyguard which is responsible for launching phone and camera affordances.
37 */
38public class KeyguardAffordanceHelper {
39
Selim Cinekbaa23272014-07-08 18:01:07 +020040 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;
Jorim Jaggief067fb2014-09-01 16:44:08 +020060 private KeyguardAffordanceView mRightIcon;
Selim Cinekbaa23272014-07-08 18:01:07 +020061 private Animator mSwipeAnimator;
Blazej Magnowski6dc59b42015-09-22 15:14:20 -070062 private FalsingManager mFalsingManager;
Selim Cinekbaa23272014-07-08 18:01:07 +020063 private int mMinBackgroundRadius;
Selim Cinek1dc40652014-11-12 17:53:52 +010064 private boolean mMotionCancelled;
Selim Cinek6746c282015-04-21 19:58:31 -070065 private int mTouchTargetSize;
66 private View mTargetedView;
67 private boolean mTouchSlopExeeded;
Selim Cinekbaa23272014-07-08 18:01:07 +020068 private AnimatorListenerAdapter mFlingEndListener = new AnimatorListenerAdapter() {
69 @Override
70 public void onAnimationEnd(Animator animation) {
71 mSwipeAnimator = null;
Jorim Jaggia86790b2015-04-02 16:32:29 -070072 mSwipingInProgress = false;
Selim Cinek158af6a2015-07-06 18:39:28 -070073 mTargetedView = null;
Selim Cinekbaa23272014-07-08 18:01:07 +020074 }
75 };
76 private Runnable mAnimationEndRunnable = new Runnable() {
77 @Override
78 public void run() {
79 mCallback.onAnimationToSideEnded();
80 }
81 };
82
83 KeyguardAffordanceHelper(Callback callback, Context context) {
84 mContext = context;
85 mCallback = callback;
Jorim Jaggief067fb2014-09-01 16:44:08 +020086 initIcons();
Selim Cinek372d1bd2015-08-14 13:19:37 -070087 updateIcon(mLeftIcon, 0.0f, mLeftIcon.getRestingAlpha(), false, false, true, false);
Selim Cinek372d1bd2015-08-14 13:19:37 -070088 updateIcon(mRightIcon, 0.0f, mRightIcon.getRestingAlpha(), false, false, true, false);
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);
Dave Mankoffdde5ee62019-05-02 17:36:11 -0400105 mFalsingManager = FalsingManagerFactory.getInstance(mContext);
Selim Cinekbaa23272014-07-08 18:01:07 +0200106 }
107
Jorim Jaggief067fb2014-09-01 16:44:08 +0200108 private void initIcons() {
109 mLeftIcon = mCallback.getLeftIcon();
Jorim Jaggief067fb2014-09-01 16:44:08 +0200110 mRightIcon = mCallback.getRightIcon();
Selim Cineke70d6532015-04-24 16:46:13 -0700111 updatePreviews();
112 }
113
114 public void updatePreviews() {
Jorim Jaggief067fb2014-09-01 16:44:08 +0200115 mLeftIcon.setPreviewView(mCallback.getLeftPreview());
116 mRightIcon.setPreviewView(mCallback.getRightPreview());
117 }
118
Selim Cinekbaa23272014-07-08 18:01:07 +0200119 public boolean onTouchEvent(MotionEvent event) {
Jorim Jaggia86790b2015-04-02 16:32:29 -0700120 int action = event.getActionMasked();
Selim Cinek6746c282015-04-21 19:58:31 -0700121 if (mMotionCancelled && action != MotionEvent.ACTION_DOWN) {
Selim Cinek1dc40652014-11-12 17:53:52 +0100122 return false;
Selim Cinekbaa23272014-07-08 18:01:07 +0200123 }
Selim Cinek1dc40652014-11-12 17:53:52 +0100124 final float y = event.getY();
125 final float x = event.getX();
Selim Cinekbaa23272014-07-08 18:01:07 +0200126
127 boolean isUp = false;
Jorim Jaggia86790b2015-04-02 16:32:29 -0700128 switch (action) {
Selim Cinekbaa23272014-07-08 18:01:07 +0200129 case MotionEvent.ACTION_DOWN:
Selim Cinek6746c282015-04-21 19:58:31 -0700130 View targetView = getIconAtPosition(x, y);
131 if (targetView == null || (mTargetedView != null && mTargetedView != targetView)) {
132 mMotionCancelled = true;
133 return false;
Selim Cinekbaa23272014-07-08 18:01:07 +0200134 }
Selim Cinek6746c282015-04-21 19:58:31 -0700135 if (mTargetedView != null) {
136 cancelAnimation();
137 } else {
138 mTouchSlopExeeded = false;
139 }
Selim Cinek372d1bd2015-08-14 13:19:37 -0700140 startSwiping(targetView);
Selim Cinekbaa23272014-07-08 18:01:07 +0200141 mInitialTouchX = x;
Selim Cinek6746c282015-04-21 19:58:31 -0700142 mInitialTouchY = y;
Selim Cinekbaa23272014-07-08 18:01:07 +0200143 mTranslationOnDown = mTranslation;
144 initVelocityTracker();
145 trackMovement(event);
Selim Cinek1dc40652014-11-12 17:53:52 +0100146 mMotionCancelled = false;
Selim Cinekbaa23272014-07-08 18:01:07 +0200147 break;
Selim Cinek1dc40652014-11-12 17:53:52 +0100148 case MotionEvent.ACTION_POINTER_DOWN:
149 mMotionCancelled = true;
Selim Cinek6746c282015-04-21 19:58:31 -0700150 endMotion(true /* forceSnapBack */, x, y);
Selim Cinekbaa23272014-07-08 18:01:07 +0200151 break;
Selim Cinekbaa23272014-07-08 18:01:07 +0200152 case MotionEvent.ACTION_MOVE:
Selim Cinekbaa23272014-07-08 18:01:07 +0200153 trackMovement(event);
Selim Cinek6746c282015-04-21 19:58:31 -0700154 float xDist = x - mInitialTouchX;
155 float yDist = y - mInitialTouchY;
156 float distance = (float) Math.hypot(xDist, yDist);
157 if (!mTouchSlopExeeded && distance > mTouchSlop) {
158 mTouchSlopExeeded = true;
Selim Cinekbaa23272014-07-08 18:01:07 +0200159 }
160 if (mSwipingInProgress) {
Selim Cinek6746c282015-04-21 19:58:31 -0700161 if (mTargetedView == mRightIcon) {
162 distance = mTranslationOnDown - distance;
163 distance = Math.min(0, distance);
164 } else {
165 distance = mTranslationOnDown + distance;
166 distance = Math.max(0, distance);
167 }
Selim Cinekc5fec9a2017-07-05 16:22:53 +0200168 setTranslation(distance, false /* isReset */, false /* animateReset */);
Selim Cinekbaa23272014-07-08 18:01:07 +0200169 }
170 break;
171
172 case MotionEvent.ACTION_UP:
173 isUp = true;
174 case MotionEvent.ACTION_CANCEL:
Selim Cinek6746c282015-04-21 19:58:31 -0700175 boolean hintOnTheRight = mTargetedView == mRightIcon;
Selim Cinekbaa23272014-07-08 18:01:07 +0200176 trackMovement(event);
Selim Cinek6746c282015-04-21 19:58:31 -0700177 endMotion(!isUp, x, y);
178 if (!mTouchSlopExeeded && isUp) {
179 mCallback.onIconClicked(hintOnTheRight);
180 }
Selim Cinekbaa23272014-07-08 18:01:07 +0200181 break;
182 }
183 return true;
184 }
185
Selim Cinek372d1bd2015-08-14 13:19:37 -0700186 private void startSwiping(View targetView) {
187 mCallback.onSwipingStarted(targetView == mRightIcon);
188 mSwipingInProgress = true;
189 mTargetedView = targetView;
190 }
191
Selim Cinek6746c282015-04-21 19:58:31 -0700192 private View getIconAtPosition(float x, float y) {
193 if (leftSwipePossible() && isOnIcon(mLeftIcon, x, y)) {
194 return mLeftIcon;
195 }
196 if (rightSwipePossible() && isOnIcon(mRightIcon, x, y)) {
197 return mRightIcon;
198 }
199 return null;
200 }
201
Selim Cinek9db71052015-04-24 18:54:30 -0700202 public boolean isOnAffordanceIcon(float x, float y) {
203 return isOnIcon(mLeftIcon, x, y) || isOnIcon(mRightIcon, x, y);
204 }
205
Selim Cinek6746c282015-04-21 19:58:31 -0700206 private boolean isOnIcon(View icon, float x, float y) {
207 float iconX = icon.getX() + icon.getWidth() / 2.0f;
208 float iconY = icon.getY() + icon.getHeight() / 2.0f;
209 double distance = Math.hypot(x - iconX, y - iconY);
210 return distance <= mTouchTargetSize / 2;
211 }
212
213 private void endMotion(boolean forceSnapBack, float lastX, float lastY) {
Selim Cinek1dc40652014-11-12 17:53:52 +0100214 if (mSwipingInProgress) {
Selim Cinek6746c282015-04-21 19:58:31 -0700215 flingWithCurrentVelocity(forceSnapBack, lastX, lastY);
216 } else {
217 mTargetedView = null;
Selim Cinek1dc40652014-11-12 17:53:52 +0100218 }
219 if (mVelocityTracker != null) {
220 mVelocityTracker.recycle();
221 mVelocityTracker = null;
222 }
223 }
224
Selim Cinekbaa23272014-07-08 18:01:07 +0200225 private boolean rightSwipePossible() {
226 return mRightIcon.getVisibility() == View.VISIBLE;
227 }
228
229 private boolean leftSwipePossible() {
230 return mLeftIcon.getVisibility() == View.VISIBLE;
231 }
232
233 public boolean onInterceptTouchEvent(MotionEvent ev) {
234 return false;
235 }
236
Selim Cinek6746c282015-04-21 19:58:31 -0700237 public void startHintAnimation(boolean right,
238 Runnable onFinishedListener) {
239 cancelAnimation();
Selim Cinekbaa23272014-07-08 18:01:07 +0200240 startHintAnimationPhase1(right, onFinishedListener);
241 }
242
243 private void startHintAnimationPhase1(final boolean right, final Runnable onFinishedListener) {
244 final KeyguardAffordanceView targetView = right ? mRightIcon : mLeftIcon;
Selim Cinekbaa23272014-07-08 18:01:07 +0200245 ValueAnimator animator = getAnimatorToRadius(right, mHintGrowAmount);
246 animator.addListener(new AnimatorListenerAdapter() {
247 private boolean mCancelled;
248
249 @Override
250 public void onAnimationCancel(Animator animation) {
251 mCancelled = true;
252 }
253
254 @Override
255 public void onAnimationEnd(Animator animation) {
256 if (mCancelled) {
257 mSwipeAnimator = null;
Selim Cinek6746c282015-04-21 19:58:31 -0700258 mTargetedView = null;
Selim Cinekbaa23272014-07-08 18:01:07 +0200259 onFinishedListener.run();
Selim Cinekbaa23272014-07-08 18:01:07 +0200260 } else {
261 startUnlockHintAnimationPhase2(right, onFinishedListener);
262 }
263 }
264 });
Selim Cinekc18010f2016-01-20 13:41:30 -0800265 animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
Selim Cinekbaa23272014-07-08 18:01:07 +0200266 animator.setDuration(HINT_PHASE1_DURATION);
267 animator.start();
268 mSwipeAnimator = animator;
Selim Cinek6746c282015-04-21 19:58:31 -0700269 mTargetedView = targetView;
Selim Cinekbaa23272014-07-08 18:01:07 +0200270 }
271
272 /**
273 * Phase 2: Move back.
274 */
275 private void startUnlockHintAnimationPhase2(boolean right, final Runnable onFinishedListener) {
Selim Cinekbaa23272014-07-08 18:01:07 +0200276 ValueAnimator animator = getAnimatorToRadius(right, 0);
277 animator.addListener(new AnimatorListenerAdapter() {
278 @Override
279 public void onAnimationEnd(Animator animation) {
280 mSwipeAnimator = null;
Selim Cinek6746c282015-04-21 19:58:31 -0700281 mTargetedView = null;
Selim Cinekbaa23272014-07-08 18:01:07 +0200282 onFinishedListener.run();
283 }
Selim Cinekbaa23272014-07-08 18:01:07 +0200284 });
Selim Cinekc18010f2016-01-20 13:41:30 -0800285 animator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
Selim Cinekbaa23272014-07-08 18:01:07 +0200286 animator.setDuration(HINT_PHASE2_DURATION);
287 animator.setStartDelay(HINT_CIRCLE_OPEN_DURATION);
288 animator.start();
289 mSwipeAnimator = animator;
290 }
291
292 private ValueAnimator getAnimatorToRadius(final boolean right, int radius) {
293 final KeyguardAffordanceView targetView = right ? mRightIcon : mLeftIcon;
294 ValueAnimator animator = ValueAnimator.ofFloat(targetView.getCircleRadius(), radius);
295 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
296 @Override
297 public void onAnimationUpdate(ValueAnimator animation) {
298 float newRadius = (float) animation.getAnimatedValue();
299 targetView.setCircleRadiusWithoutAnimation(newRadius);
300 float translation = getTranslationFromRadius(newRadius);
301 mTranslation = right ? -translation : translation;
Selim Cinek6746c282015-04-21 19:58:31 -0700302 updateIconsFromTranslation(targetView);
Selim Cinekbaa23272014-07-08 18:01:07 +0200303 }
304 });
305 return animator;
306 }
307
308 private void cancelAnimation() {
309 if (mSwipeAnimator != null) {
310 mSwipeAnimator.cancel();
311 }
312 }
313
Selim Cinek6746c282015-04-21 19:58:31 -0700314 private void flingWithCurrentVelocity(boolean forceSnapBack, float lastX, float lastY) {
315 float vel = getCurrentVelocity(lastX, lastY);
Selim Cinekbaa23272014-07-08 18:01:07 +0200316
317 // We snap back if the current translation is not far enough
Adrian Roos6a70b882016-04-19 17:09:07 -0700318 boolean snapBack = false;
319 if (mCallback.needsAntiFalsing()) {
320 snapBack = snapBack || mFalsingManager.isFalseTouch();
Blazej Magnowski6dc59b42015-09-22 15:14:20 -0700321 }
Adrian Roos6a70b882016-04-19 17:09:07 -0700322 snapBack = snapBack || isBelowFalsingThreshold();
Selim Cinekbaa23272014-07-08 18:01:07 +0200323
324 // or if the velocity is in the opposite direction.
325 boolean velIsInWrongDirection = vel * mTranslation < 0;
326 snapBack |= Math.abs(vel) > mMinFlingVelocity && velIsInWrongDirection;
327 vel = snapBack ^ velIsInWrongDirection ? 0 : vel;
Selim Cinek372d1bd2015-08-14 13:19:37 -0700328 fling(vel, snapBack || forceSnapBack, mTranslation < 0);
Selim Cinekbaa23272014-07-08 18:01:07 +0200329 }
330
Selim Cinek5386fb32014-09-03 16:37:36 +0200331 private boolean isBelowFalsingThreshold() {
Selim Cinek34cf5c42014-09-26 15:39:00 +0200332 return Math.abs(mTranslation) < Math.abs(mTranslationOnDown) + getMinTranslationAmount();
333 }
334
335 private int getMinTranslationAmount() {
336 float factor = mCallback.getAffordanceFalsingFactor();
337 return (int) (mMinTranslationAmount * factor);
Selim Cinek5386fb32014-09-03 16:37:36 +0200338 }
339
Selim Cinek372d1bd2015-08-14 13:19:37 -0700340 private void fling(float vel, final boolean snapBack, boolean right) {
341 float target = right ? -mCallback.getMaxTranslationDistance()
Selim Cinek6746c282015-04-21 19:58:31 -0700342 : mCallback.getMaxTranslationDistance();
Selim Cinekbaa23272014-07-08 18:01:07 +0200343 target = snapBack ? 0 : target;
344
345 ValueAnimator animator = ValueAnimator.ofFloat(mTranslation, target);
346 mFlingAnimationUtils.apply(animator, mTranslation, target, vel);
347 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
348 @Override
349 public void onAnimationUpdate(ValueAnimator animation) {
350 mTranslation = (float) animation.getAnimatedValue();
351 }
352 });
353 animator.addListener(mFlingEndListener);
354 if (!snapBack) {
Selim Cinek372d1bd2015-08-14 13:19:37 -0700355 startFinishingCircleAnimation(vel * 0.375f, mAnimationEndRunnable, right);
356 mCallback.onAnimationToSideStarted(right, mTranslation, vel);
Selim Cinekbaa23272014-07-08 18:01:07 +0200357 } else {
358 reset(true);
359 }
360 animator.start();
361 mSwipeAnimator = animator;
Jorim Jaggia86790b2015-04-02 16:32:29 -0700362 if (snapBack) {
363 mCallback.onSwipingAborted();
364 }
Selim Cinekbaa23272014-07-08 18:01:07 +0200365 }
366
Lucas Dupin29678e62018-06-19 10:42:42 -0700367 private void startFinishingCircleAnimation(float velocity, Runnable animationEndRunnable,
Selim Cinek372d1bd2015-08-14 13:19:37 -0700368 boolean right) {
369 KeyguardAffordanceView targetView = right ? mRightIcon : mLeftIcon;
Lucas Dupin29678e62018-06-19 10:42:42 -0700370 targetView.finishAnimation(velocity, animationEndRunnable);
Selim Cinekbaa23272014-07-08 18:01:07 +0200371 }
372
Selim Cinekc5fec9a2017-07-05 16:22:53 +0200373 private void setTranslation(float translation, boolean isReset, boolean animateReset) {
Selim Cinekbaa23272014-07-08 18:01:07 +0200374 translation = rightSwipePossible() ? translation : Math.max(0, translation);
375 translation = leftSwipePossible() ? translation : Math.min(0, translation);
376 float absTranslation = Math.abs(translation);
Selim Cinekc5fec9a2017-07-05 16:22:53 +0200377 if (translation != mTranslation || isReset) {
Selim Cinekbaa23272014-07-08 18:01:07 +0200378 KeyguardAffordanceView targetView = translation > 0 ? mLeftIcon : mRightIcon;
379 KeyguardAffordanceView otherView = translation > 0 ? mRightIcon : mLeftIcon;
Selim Cinek34cf5c42014-09-26 15:39:00 +0200380 float alpha = absTranslation / getMinTranslationAmount();
Selim Cinekbaa23272014-07-08 18:01:07 +0200381
382 // We interpolate the alpha of the other icons to 0
Jorim Jaggi27c9b742015-04-09 10:34:49 -0700383 float fadeOutAlpha = 1.0f - alpha;
Selim Cinek6746c282015-04-21 19:58:31 -0700384 fadeOutAlpha = Math.max(fadeOutAlpha, 0.0f);
Selim Cinekbaa23272014-07-08 18:01:07 +0200385
386 boolean animateIcons = isReset && animateReset;
Selim Cinek372d1bd2015-08-14 13:19:37 -0700387 boolean forceNoCircleAnimation = isReset && !animateReset;
Selim Cinekbaa23272014-07-08 18:01:07 +0200388 float radius = getRadiusFromTranslation(absTranslation);
Selim Cinek5386fb32014-09-03 16:37:36 +0200389 boolean slowAnimation = isReset && isBelowFalsingThreshold();
Selim Cinekbaa23272014-07-08 18:01:07 +0200390 if (!isReset) {
Jorim Jaggi27c9b742015-04-09 10:34:49 -0700391 updateIcon(targetView, radius, alpha + fadeOutAlpha * targetView.getRestingAlpha(),
Selim Cinekc5fec9a2017-07-05 16:22:53 +0200392 false, false, false, false);
Selim Cinekbaa23272014-07-08 18:01:07 +0200393 } else {
Jorim Jaggi27c9b742015-04-09 10:34:49 -0700394 updateIcon(targetView, 0.0f, fadeOutAlpha * targetView.getRestingAlpha(),
Selim Cinekc5fec9a2017-07-05 16:22:53 +0200395 animateIcons, slowAnimation, true /* isReset */, forceNoCircleAnimation);
Selim Cinekbaa23272014-07-08 18:01:07 +0200396 }
Jorim Jaggi27c9b742015-04-09 10:34:49 -0700397 updateIcon(otherView, 0.0f, fadeOutAlpha * otherView.getRestingAlpha(),
Selim Cinekc5fec9a2017-07-05 16:22:53 +0200398 animateIcons, slowAnimation, isReset, forceNoCircleAnimation);
Selim Cinekbaa23272014-07-08 18:01:07 +0200399
400 mTranslation = translation;
401 }
402 }
403
Selim Cinek6746c282015-04-21 19:58:31 -0700404 private void updateIconsFromTranslation(KeyguardAffordanceView targetView) {
405 float absTranslation = Math.abs(mTranslation);
406 float alpha = absTranslation / getMinTranslationAmount();
Selim Cinekbaa23272014-07-08 18:01:07 +0200407
408 // We interpolate the alpha of the other icons to 0
Jorim Jaggi27c9b742015-04-09 10:34:49 -0700409 float fadeOutAlpha = 1.0f - alpha;
Selim Cinekbaa23272014-07-08 18:01:07 +0200410 fadeOutAlpha = Math.max(0.0f, fadeOutAlpha);
411
412 // We interpolate the alpha of the targetView to 1
Selim Cinekbaa23272014-07-08 18:01:07 +0200413 KeyguardAffordanceView otherView = targetView == mRightIcon ? mLeftIcon : mRightIcon;
Jorim Jaggi27c9b742015-04-09 10:34:49 -0700414 updateIconAlpha(targetView, alpha + fadeOutAlpha * targetView.getRestingAlpha(), false);
415 updateIconAlpha(otherView, fadeOutAlpha * otherView.getRestingAlpha(), false);
Selim Cinekbaa23272014-07-08 18:01:07 +0200416 }
417
418 private float getTranslationFromRadius(float circleSize) {
Selim Cinek6746c282015-04-21 19:58:31 -0700419 float translation = (circleSize - mMinBackgroundRadius)
420 / BACKGROUND_RADIUS_SCALE_FACTOR;
421 return translation > 0.0f ? translation + mTouchSlop : 0.0f;
Selim Cinekbaa23272014-07-08 18:01:07 +0200422 }
423
424 private float getRadiusFromTranslation(float translation) {
Selim Cinek6746c282015-04-21 19:58:31 -0700425 if (translation <= mTouchSlop) {
426 return 0.0f;
427 }
428 return (translation - mTouchSlop) * BACKGROUND_RADIUS_SCALE_FACTOR + mMinBackgroundRadius;
Selim Cinekbaa23272014-07-08 18:01:07 +0200429 }
430
Selim Cinekbaa23272014-07-08 18:01:07 +0200431 public void animateHideLeftRightIcon() {
Selim Cinek6746c282015-04-21 19:58:31 -0700432 cancelAnimation();
Selim Cinek372d1bd2015-08-14 13:19:37 -0700433 updateIcon(mRightIcon, 0f, 0f, true, false, false, false);
434 updateIcon(mLeftIcon, 0f, 0f, true, false, false, false);
Selim Cinekbaa23272014-07-08 18:01:07 +0200435 }
436
437 private void updateIcon(KeyguardAffordanceView view, float circleRadius, float alpha,
Selim Cinek372d1bd2015-08-14 13:19:37 -0700438 boolean animate, boolean slowRadiusAnimation, boolean force,
439 boolean forceNoCircleAnimation) {
Adrian Roosa4eba9f2015-07-22 18:13:04 -0700440 if (view.getVisibility() != View.VISIBLE && !force) {
Selim Cinekbaa23272014-07-08 18:01:07 +0200441 return;
442 }
Selim Cinek372d1bd2015-08-14 13:19:37 -0700443 if (forceNoCircleAnimation) {
444 view.setCircleRadiusWithoutAnimation(circleRadius);
445 } else {
446 view.setCircleRadius(circleRadius, slowRadiusAnimation);
447 }
Selim Cinekbaa23272014-07-08 18:01:07 +0200448 updateIconAlpha(view, alpha, animate);
449 }
450
451 private void updateIconAlpha(KeyguardAffordanceView view, float alpha, boolean animate) {
Jorim Jaggi27c9b742015-04-09 10:34:49 -0700452 float scale = getScale(alpha, view);
Selim Cinekbaa23272014-07-08 18:01:07 +0200453 alpha = Math.min(1.0f, alpha);
454 view.setImageAlpha(alpha, animate);
455 view.setImageScale(scale, animate);
456 }
457
Jorim Jaggi27c9b742015-04-09 10:34:49 -0700458 private float getScale(float alpha, KeyguardAffordanceView icon) {
459 float scale = alpha / icon.getRestingAlpha() * 0.2f +
Selim Cinekbaa23272014-07-08 18:01:07 +0200460 KeyguardAffordanceView.MIN_ICON_SCALE_AMOUNT;
461 return Math.min(scale, KeyguardAffordanceView.MAX_ICON_SCALE_AMOUNT);
462 }
463
464 private void trackMovement(MotionEvent event) {
465 if (mVelocityTracker != null) {
466 mVelocityTracker.addMovement(event);
467 }
468 }
469
470 private void initVelocityTracker() {
471 if (mVelocityTracker != null) {
472 mVelocityTracker.recycle();
473 }
474 mVelocityTracker = VelocityTracker.obtain();
475 }
476
Selim Cinek6746c282015-04-21 19:58:31 -0700477 private float getCurrentVelocity(float lastX, float lastY) {
Selim Cinekbaa23272014-07-08 18:01:07 +0200478 if (mVelocityTracker == null) {
479 return 0;
480 }
481 mVelocityTracker.computeCurrentVelocity(1000);
Selim Cinek6746c282015-04-21 19:58:31 -0700482 float aX = mVelocityTracker.getXVelocity();
483 float aY = mVelocityTracker.getYVelocity();
484 float bX = lastX - mInitialTouchX;
485 float bY = lastY - mInitialTouchY;
486 float bLen = (float) Math.hypot(bX, bY);
487 // Project the velocity onto the distance vector: a * b / |b|
488 float projectedVelocity = (aX * bX + aY * bY) / bLen;
489 if (mTargetedView == mRightIcon) {
490 projectedVelocity = -projectedVelocity;
491 }
492 return projectedVelocity;
Selim Cinekbaa23272014-07-08 18:01:07 +0200493 }
494
495 public void onConfigurationChanged() {
496 initDimens();
Jorim Jaggief067fb2014-09-01 16:44:08 +0200497 initIcons();
Selim Cinekbaa23272014-07-08 18:01:07 +0200498 }
499
Jorim Jaggi0a818222014-11-10 19:04:34 +0100500 public void onRtlPropertiesChanged() {
501 initIcons();
502 }
503
Selim Cinekbaa23272014-07-08 18:01:07 +0200504 public void reset(boolean animate) {
Selim Cinek158af6a2015-07-06 18:39:28 -0700505 cancelAnimation();
Selim Cinekc5fec9a2017-07-05 16:22:53 +0200506 setTranslation(0.0f, true /* isReset */, animate);
Jorim Jaggia86790b2015-04-02 16:32:29 -0700507 mMotionCancelled = true;
508 if (mSwipingInProgress) {
509 mCallback.onSwipingAborted();
Selim Cinek372d1bd2015-08-14 13:19:37 -0700510 mSwipingInProgress = false;
Jorim Jaggia86790b2015-04-02 16:32:29 -0700511 }
Selim Cinek372d1bd2015-08-14 13:19:37 -0700512 }
513
514 public boolean isSwipingInProgress() {
515 return mSwipingInProgress;
516 }
517
518 public void launchAffordance(boolean animate, boolean left) {
519 if (mSwipingInProgress) {
520 // We don't want to mess with the state if the user is actually swiping already.
521 return;
522 }
523 KeyguardAffordanceView targetView = left ? mLeftIcon : mRightIcon;
524 KeyguardAffordanceView otherView = left ? mRightIcon : mLeftIcon;
525 startSwiping(targetView);
Lucas Dupinf78c5402018-01-05 12:57:27 -0800526
527 // Do not animate the circle expanding if the affordance isn't visible,
528 // otherwise the circle will be meaningless.
529 if (targetView.getVisibility() != View.VISIBLE) {
530 animate = false;
531 }
532
Selim Cinek372d1bd2015-08-14 13:19:37 -0700533 if (animate) {
534 fling(0, false, !left);
535 updateIcon(otherView, 0.0f, 0, true, false, true, false);
Selim Cinek372d1bd2015-08-14 13:19:37 -0700536 } else {
537 mCallback.onAnimationToSideStarted(!left, mTranslation, 0);
538 mTranslation = left ? mCallback.getMaxTranslationDistance()
539 : mCallback.getMaxTranslationDistance();
Selim Cinek372d1bd2015-08-14 13:19:37 -0700540 updateIcon(otherView, 0.0f, 0.0f, false, false, true, false);
541 targetView.instantFinishAnimation();
542 mFlingEndListener.onAnimationEnd(null);
543 mAnimationEndRunnable.run();
544 }
Selim Cinekbaa23272014-07-08 18:01:07 +0200545 }
546
547 public interface Callback {
548
549 /**
550 * Notifies the callback when an animation to a side page was started.
551 *
552 * @param rightPage Is the page animated to the right page?
553 */
Christoph Studerb0183992014-12-22 21:02:26 +0100554 void onAnimationToSideStarted(boolean rightPage, float translation, float vel);
Selim Cinekbaa23272014-07-08 18:01:07 +0200555
556 /**
557 * Notifies the callback the animation to a side page has ended.
558 */
559 void onAnimationToSideEnded();
560
Selim Cinek6746c282015-04-21 19:58:31 -0700561 float getMaxTranslationDistance();
Selim Cinekbaa23272014-07-08 18:01:07 +0200562
Jorim Jaggid9449862015-05-29 14:49:08 -0700563 void onSwipingStarted(boolean rightIcon);
Jorim Jaggia86790b2015-04-02 16:32:29 -0700564
565 void onSwipingAborted();
Selim Cinekbaa23272014-07-08 18:01:07 +0200566
Selim Cinek6746c282015-04-21 19:58:31 -0700567 void onIconClicked(boolean rightIcon);
568
Selim Cinekbaa23272014-07-08 18:01:07 +0200569 KeyguardAffordanceView getLeftIcon();
570
Selim Cinekbaa23272014-07-08 18:01:07 +0200571 KeyguardAffordanceView getRightIcon();
Jorim Jaggib6cdcbc2014-07-25 21:58:29 +0200572
573 View getLeftPreview();
574
575 View getRightPreview();
Selim Cinek34cf5c42014-09-26 15:39:00 +0200576
577 /**
578 * @return The factor the minimum swipe amount should be multiplied with.
579 */
580 float getAffordanceFalsingFactor();
Adrian Roos6a70b882016-04-19 17:09:07 -0700581
582 boolean needsAntiFalsing();
Selim Cinekbaa23272014-07-08 18:01:07 +0200583 }
584}