1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.content.browser.selection;
6 
7 import android.animation.ValueAnimator;
8 import android.view.animation.LinearInterpolator;
9 
10 import org.chromium.base.Log;
11 
12 /**
13  * MagnifierAnimator adds animation to MagnifierWrapper when there is a change in y direction.
14  * MagnifierWrapper class isolated P APIs out so we could write test for MagnifierAnimator.
15  */
16 public class MagnifierAnimator implements SelectionInsertionHandleObserver {
17     private static final boolean DEBUG = false;
18     private static final String TAG = "Magnifier";
19 
20     private static final long DURATION_MS = 100;
21 
22     private MagnifierWrapper mMagnifier;
23     private ValueAnimator mAnimator;
24 
25     private boolean mMagnifierIsShowing;
26 
27     // Current point for showing magnifier.
28     private float mAnimationCurrentX;
29     private float mAnimationCurrentY;
30 
31     // Start point for animation.
32     private float mAnimationStartX;
33     private float mAnimationStartY;
34 
35     // Target point for end of animation.
36     private float mTargetX;
37     private float mTargetY;
38 
39     /**
40      * Constructor.
41      */
MagnifierAnimator(MagnifierWrapper magnifier)42     public MagnifierAnimator(MagnifierWrapper magnifier) {
43         mMagnifier = magnifier;
44 
45         createValueAnimator();
46 
47         mTargetX = -1.0f;
48         mTargetY = -1.0f;
49     }
50 
51     @Override
handleDragStartedOrMoved(float x, float y)52     public void handleDragStartedOrMoved(float x, float y) {
53         if (!mMagnifier.isAvailable()) return;
54         if (DEBUG) {
55             Log.i(TAG,
56                     "handleDragStartedOrMoved: "
57                             + "(" + x + ", " + y + ")");
58         }
59         // We only do animation if this is not the first time to show magnifier and y coordinate
60         // is different from last target.
61         if (mMagnifierIsShowing && y != mTargetY) {
62             // If the animator is running, we cancel it and reset the starting point to current
63             // point, then start a new animation. Otherwise set the starting point to previous
64             // place.
65             if (mAnimator.isRunning()) {
66                 mAnimator.cancel();
67                 // Create another ValueAnimator because the test uses
68                 // ValueAnimator#setCurrentFraction() to adjust animation fraction, the internal
69                 // fraction is not being reset in ValueAnimator#cancel(), so the next
70                 // ValueAnimator#start() will use this fraction first, which will cause a jump.
71                 createValueAnimator();
72                 mAnimationStartX = mAnimationCurrentX;
73                 mAnimationStartY = mAnimationCurrentY;
74             } else {
75                 mAnimationStartX = mTargetX;
76                 mAnimationStartY = mTargetY;
77             }
78             mAnimator.start();
79         } else {
80             if (!mAnimator.isRunning()) mMagnifier.show(x, y);
81             // if mAnimator is running, we simply change the target coordinate. x-coordinate won't
82             // change dramatically, so let the previous animation finish to the target point.
83         }
84 
85         mTargetX = x;
86         mTargetY = y;
87 
88         mMagnifierIsShowing = true;
89     }
90 
91     @Override
handleDragStopped()92     public void handleDragStopped() {
93         mMagnifier.dismiss();
94         mAnimator.cancel();
95         mMagnifierIsShowing = false;
96     }
97 
getValueAnimatorForTesting()98     /* package */ ValueAnimator getValueAnimatorForTesting() {
99         return mAnimator;
100     }
101 
currentValue(float start, float target, ValueAnimator animation)102     private float currentValue(float start, float target, ValueAnimator animation) {
103         return start + (target - start) * animation.getAnimatedFraction();
104     }
105 
createValueAnimator()106     private void createValueAnimator() {
107         mAnimator = ValueAnimator.ofFloat(0, 1);
108         mAnimator.setDuration(DURATION_MS);
109         mAnimator.setInterpolator(new LinearInterpolator());
110         mAnimator.addUpdateListener(animation -> {
111             mAnimationCurrentX = currentValue(mAnimationStartX, mTargetX, animation);
112             mAnimationCurrentY = currentValue(mAnimationStartY, mTargetY, animation);
113             mMagnifier.show(mAnimationCurrentX, mAnimationCurrentY);
114         });
115     }
116 }
117