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.camera.ui.focus;
18 
19 import android.graphics.Paint;
20 
21 import com.android.camera.debug.Log;
22 import com.android.camera.debug.Log.Tag;
23 import com.android.camera.ui.motion.DampedSpring;
24 import com.android.camera.ui.motion.DynamicAnimation;
25 import com.android.camera.ui.motion.Invalidator;
26 import com.android.camera.ui.motion.UnitCurve;
27 import com.android.camera.ui.motion.UnitCurves;
28 
29 /**
30  * Base class for defining the focus ring states, enter and exit durations, and
31  * positioning logic.
32  */
33 abstract class FocusRingRenderer implements DynamicAnimation {
34     private static final Tag TAG = new Tag("FocusRingRenderer");
35 
36     /**
37      * Primary focus states that a focus ring renderer can go through.
38      */
39     protected static enum FocusState {
40         STATE_INACTIVE,
41         STATE_ENTER,
42         STATE_ACTIVE,
43         STATE_FADE_OUT,
44         STATE_HARD_STOP,
45     }
46 
47     protected final Invalidator mInvalidator;
48     protected final Paint mRingPaint;
49     protected final DampedSpring mRingRadius;
50     protected final UnitCurve mEnterOpacityCurve;
51     protected final UnitCurve mExitOpacityCurve;
52     protected final UnitCurve mHardExitOpacityCurve;
53     protected final float mEnterDurationMillis;
54     protected final float mExitDurationMillis;
55     protected final float mHardExitDurationMillis = 64;
56 
57     private int mCenterX;
58     private int mCenterY;
59     protected long mEnterStartMillis = 0;
60     protected long mExitStartMillis = 0;
61     protected long mHardExitStartMillis = 0;
62 
63     protected FocusState mFocusState = FocusState.STATE_INACTIVE;
64 
65     /**
66      * A dynamic, configurable, self contained ring render that will inform
67      * via invalidation if it should continue to be receive updates
68      * and re-draws.
69      *
70      * @param invalidator the object to inform if it requires more draw calls.
71      * @param ringPaint the paint to use to draw the ring.
72      * @param enterDurationMillis the fade in duration in milliseconds
73      * @param exitDurationMillis the fade out duration in milliseconds.
74      */
FocusRingRenderer(Invalidator invalidator, Paint ringPaint, float enterDurationMillis, float exitDurationMillis)75     FocusRingRenderer(Invalidator invalidator, Paint ringPaint, float enterDurationMillis,
76           float exitDurationMillis) {
77         mInvalidator = invalidator;
78         mRingPaint = ringPaint;
79         mEnterDurationMillis = enterDurationMillis;
80         mExitDurationMillis = exitDurationMillis;
81 
82         mEnterOpacityCurve = UnitCurves.FAST_OUT_SLOW_IN;
83         mExitOpacityCurve = UnitCurves.FAST_OUT_LINEAR_IN;
84         mHardExitOpacityCurve = UnitCurves.FAST_OUT_LINEAR_IN;
85 
86         mRingRadius = new DampedSpring();
87     }
88 
89     /**
90      * Set the centerX position for this focus ring renderer.
91      *
92      * @param value the x position
93      */
setCenterX(int value)94     public void setCenterX(int value) {
95         mCenterX = value;
96     }
97 
getCenterX()98     protected int getCenterX() {
99         return mCenterX;
100     }
101 
102     /**
103      * Set the centerY position for this focus ring renderer.
104      *
105      * @param value the y position
106      */
setCenterY(int value)107     public void setCenterY(int value) {
108         mCenterY = value;
109     }
110 
getCenterY()111     protected int getCenterY() {
112         return mCenterY;
113     }
114 
115     /**
116      * Set the physical radius of this ring.
117      *
118      * @param value the radius of the ring.
119      */
setRadius(long tMs, float value)120     public void setRadius(long tMs, float value) {
121         if (mFocusState == FocusState.STATE_FADE_OUT
122               && Math.abs(mRingRadius.getTarget() - value) > 0.1) {
123             Log.v(TAG, "FOCUS STATE ENTER VIA setRadius(" + tMs + ", " + value + ")");
124             mFocusState = FocusState.STATE_ENTER;
125             mEnterStartMillis = computeEnterStartTimeMillis(tMs, mEnterDurationMillis);
126         }
127 
128         mRingRadius.setTarget(value);
129     }
130 
131     /**
132      * returns true if the renderer is not in an inactive state.
133      */
134     @Override
isActive()135     public boolean isActive() {
136         return mFocusState != FocusState.STATE_INACTIVE;
137     }
138 
139     /**
140      * returns true if the renderer is in an exit state.
141      */
isExiting()142     public boolean isExiting() {
143         return mFocusState == FocusState.STATE_FADE_OUT
144               || mFocusState == FocusState.STATE_HARD_STOP;
145     }
146 
147     /**
148      * returns true if the renderer is in an enter state.
149      */
isEntering()150     public boolean isEntering() {
151         return mFocusState == FocusState.STATE_ENTER;
152     }
153 
154     /**
155      * Initialize and start the animation with the given start and
156      * target radius.
157      */
start(long startMs, float initialRadius, float targetRadius)158     public void start(long startMs, float initialRadius, float targetRadius) {
159         if (mFocusState != FocusState.STATE_INACTIVE) {
160             Log.w(TAG, "start() called while the ring was still focusing!");
161         }
162         mRingRadius.stop();
163         mRingRadius.setValue(initialRadius);
164         mRingRadius.setTarget(targetRadius);
165         mEnterStartMillis = startMs;
166 
167         mFocusState = FocusState.STATE_ENTER;
168         mInvalidator.invalidate();
169     }
170 
171     /**
172      * Put the animation in the exit state regardless of the current
173      * dynamic transition. If the animation is currently in an enter state
174      * this will compute an exit start time such that the exit time lines
175      * up with the enter time at the current transition value.
176      *
177      * @param t the current animation time.
178      */
exit(long t)179     public void exit(long t) {
180         if (mRingRadius.isActive()) {
181             mRingRadius.stop();
182         }
183 
184         mFocusState = FocusState.STATE_FADE_OUT;
185         mExitStartMillis = computeExitStartTimeMs(t, mExitDurationMillis);
186     }
187 
188     /**
189      * Put the animation in the hard stop state regardless of the current
190      * dynamic transition. If the animation is currently in an enter state
191      * this will compute an exit start time such that the exit time lines
192      * up with the enter time at the current transition value.
193      *
194      * @param tMillis the current animation time in milliseconds.
195      */
stop(long tMillis)196     public void stop(long tMillis) {
197         if (mRingRadius.isActive()) {
198             mRingRadius.stop();
199         }
200 
201         mFocusState = FocusState.STATE_HARD_STOP;
202         mHardExitStartMillis = computeExitStartTimeMs(tMillis, mHardExitDurationMillis);
203     }
204 
computeExitStartTimeMs(long tMillis, float exitDuration)205     private long computeExitStartTimeMs(long tMillis, float exitDuration) {
206         if (mEnterStartMillis + mEnterDurationMillis <= tMillis) {
207             return tMillis;
208         }
209 
210         // Compute the current progress on the enter animation.
211         float enterT = (tMillis - mEnterStartMillis) / mEnterDurationMillis;
212 
213         // Find a time on the exit curve such that it will produce the same value.
214         float exitT = UnitCurves.mapEnterCurveToExitCurveAtT(mEnterOpacityCurve, mExitOpacityCurve,
215               enterT);
216 
217         // Compute the a start time before tMs such that the ratio of time completed
218         // equals the computed exit curve animation position.
219         return tMillis - (long) (exitT * exitDuration);
220     }
221 
computeEnterStartTimeMillis(long tMillis, float enterDuration)222     private long computeEnterStartTimeMillis(long tMillis, float enterDuration) {
223         if (mExitStartMillis + mExitDurationMillis <= tMillis) {
224             return tMillis;
225         }
226 
227         // Compute the current progress on the enter animation.
228         float exitT = (tMillis - mExitStartMillis) / mExitDurationMillis;
229 
230         // Find a time on the exit curve such that it will produce the same value.
231         float enterT = UnitCurves.mapEnterCurveToExitCurveAtT(mExitOpacityCurve, mEnterOpacityCurve,
232               exitT);
233 
234         // Compute the a start time before tMs such that the ratio of time completed
235         // equals the computed exit curve animation position.
236         return tMillis - (long) (enterT * enterDuration);
237     }
238 }
239