1package com.android.deskclock;
2
3import android.content.Context;
4import android.content.SharedPreferences;
5import android.content.res.Resources;
6import android.graphics.Canvas;
7import android.graphics.Color;
8import android.graphics.Paint;
9import android.graphics.RectF;
10import android.util.AttributeSet;
11import android.view.View;
12
13import com.android.deskclock.stopwatch.Stopwatches;
14
15/**
16 * Class to draw a circle for timers and stopwatches.
17 * These two usages require two different animation modes:
18 * Timer counts down. In this mode the animation is counter-clockwise and stops at 0.
19 * Stopwatch counts up. In this mode the animation is clockwise and will run until stopped.
20 */
21public class CircleTimerView extends View {
22
23    private int mAccentColor;
24    private int mWhiteColor;
25    private long mIntervalTime = 0;
26    private long mIntervalStartTime = -1;
27    private long mMarkerTime = -1;
28    private long mCurrentIntervalTime = 0;
29    private long mAccumulatedTime = 0;
30    private boolean mPaused = false;
31    private boolean mAnimate = false;
32    private static float mStrokeSize = 4;
33    private static float mDotRadius = 6;
34    private static float mMarkerStrokeSize = 2;
35    private final Paint mPaint = new Paint();
36    private final Paint mFill = new Paint();
37    private final RectF mArcRect = new RectF();
38    private float mRadiusOffset;   // amount to remove from radius to account for markers on circle
39    private float mScreenDensity;
40
41    // Stopwatch mode is the default.
42    private boolean mTimerMode = false;
43
44    @SuppressWarnings("unused")
45    public CircleTimerView(Context context) {
46        this(context, null);
47    }
48
49    public CircleTimerView(Context context, AttributeSet attrs) {
50        super(context, attrs);
51        init(context);
52    }
53
54    public void setIntervalTime(long t) {
55        mIntervalTime = t;
56        postInvalidate();
57    }
58
59    public void setMarkerTime(long t) {
60        mMarkerTime = t;
61        postInvalidate();
62    }
63
64    public void reset() {
65        mIntervalStartTime = -1;
66        mMarkerTime = -1;
67        postInvalidate();
68    }
69    public void startIntervalAnimation() {
70        mIntervalStartTime = Utils.getTimeNow();
71        mAnimate = true;
72        invalidate();
73        mPaused = false;
74    }
75    public void stopIntervalAnimation() {
76        mAnimate = false;
77        mIntervalStartTime = -1;
78        mAccumulatedTime = 0;
79    }
80
81    public boolean isAnimating() {
82        return (mIntervalStartTime != -1);
83    }
84
85    public void pauseIntervalAnimation() {
86        mAnimate = false;
87        mAccumulatedTime += Utils.getTimeNow() - mIntervalStartTime;
88        mPaused = true;
89    }
90
91    public void abortIntervalAnimation() {
92        mAnimate = false;
93    }
94
95    public void setPassedTime(long time, boolean drawRed) {
96        // The onDraw() method checks if mIntervalStartTime has been set before drawing any red.
97        // Without drawRed, mIntervalStartTime should not be set here at all, and would remain at -1
98        // when the state is reconfigured after exiting and re-entering the application.
99        // If the timer is currently running, this drawRed will not be set, and will have no effect
100        // because mIntervalStartTime will be set when the thread next runs.
101        // When the timer is not running, mIntervalStartTime will not be set upon loading the state,
102        // and no red will be drawn, so drawRed is used to force onDraw() to draw the red portion,
103        // despite the timer not running.
104        mCurrentIntervalTime = mAccumulatedTime = time;
105        if (drawRed) {
106            mIntervalStartTime = Utils.getTimeNow();
107        }
108        postInvalidate();
109    }
110
111
112
113    private void init(Context c) {
114
115        Resources resources = c.getResources();
116        mStrokeSize = resources.getDimension(R.dimen.circletimer_circle_size);
117        float dotDiameter = resources.getDimension(R.dimen.circletimer_dot_size);
118        mMarkerStrokeSize = resources.getDimension(R.dimen.circletimer_marker_size);
119        mRadiusOffset = Utils.calculateRadiusOffset(
120                mStrokeSize, dotDiameter, mMarkerStrokeSize);
121        mPaint.setAntiAlias(true);
122        mPaint.setStyle(Paint.Style.STROKE);
123        mWhiteColor = resources.getColor(R.color.clock_white);
124        mAccentColor = Utils.obtainStyledColor(c, R.attr.colorAccent, Color.RED);
125        mScreenDensity = resources.getDisplayMetrics().density;
126        mFill.setAntiAlias(true);
127        mFill.setStyle(Paint.Style.FILL);
128        mFill.setColor(mAccentColor);
129        mDotRadius = dotDiameter / 2f;
130    }
131
132    public void setTimerMode(boolean mode) {
133        mTimerMode = mode;
134    }
135
136    @Override
137    public void onDraw(Canvas canvas) {
138        int xCenter = getWidth() / 2 + 1;
139        int yCenter = getHeight() / 2;
140
141        mPaint.setStrokeWidth(mStrokeSize);
142        float radius = Math.min(xCenter, yCenter) - mRadiusOffset;
143
144        if (mIntervalStartTime == -1) {
145            // just draw a complete white circle, no red arc needed
146            mPaint.setColor(mWhiteColor);
147            canvas.drawCircle (xCenter, yCenter, radius, mPaint);
148            if (mTimerMode) {
149                drawRedDot(canvas, 0f, xCenter, yCenter, radius);
150            }
151        } else {
152            if (mAnimate) {
153                mCurrentIntervalTime = Utils.getTimeNow() - mIntervalStartTime + mAccumulatedTime;
154            }
155            //draw a combination of red and white arcs to create a circle
156            mArcRect.top = yCenter - radius;
157            mArcRect.bottom = yCenter + radius;
158            mArcRect.left =  xCenter - radius;
159            mArcRect.right = xCenter + radius;
160            float redPercent = (float)mCurrentIntervalTime / (float)mIntervalTime;
161            // prevent timer from doing more than one full circle
162            redPercent = (redPercent > 1 && mTimerMode) ? 1 : redPercent;
163
164            float whitePercent = 1 - (redPercent > 1 ? 1 : redPercent);
165            // draw red arc here
166            mPaint.setColor(mAccentColor);
167            if (mTimerMode){
168                canvas.drawArc (mArcRect, 270, - redPercent * 360 , false, mPaint);
169            } else {
170                canvas.drawArc (mArcRect, 270, + redPercent * 360 , false, mPaint);
171            }
172
173            // draw white arc here
174            mPaint.setStrokeWidth(mStrokeSize);
175            mPaint.setColor(mWhiteColor);
176            if (mTimerMode) {
177                canvas.drawArc(mArcRect, 270, + whitePercent * 360, false, mPaint);
178            } else {
179                canvas.drawArc(mArcRect, 270 + (1 - whitePercent) * 360,
180                        whitePercent * 360, false, mPaint);
181            }
182
183            if (mMarkerTime != -1 && radius > 0 && mIntervalTime != 0) {
184                mPaint.setStrokeWidth(mMarkerStrokeSize);
185                float angle = (float)(mMarkerTime % mIntervalTime) / (float)mIntervalTime * 360;
186                // draw 2dips thick marker
187                // the formula to draw the marker 1 unit thick is:
188                // 180 / (radius * Math.PI)
189                // after that we have to scale it by the screen density
190                canvas.drawArc (mArcRect, 270 + angle, mScreenDensity *
191                        (float) (360 / (radius * Math.PI)) , false, mPaint);
192            }
193            drawRedDot(canvas, redPercent, xCenter, yCenter, radius);
194        }
195        if (mAnimate) {
196            postInvalidateOnAnimation();
197        }
198   }
199
200    protected void drawRedDot(
201            Canvas canvas, float degrees, int xCenter, int yCenter, float radius) {
202        mPaint.setColor(mAccentColor);
203        float dotPercent;
204        if (mTimerMode) {
205            dotPercent = 270 - degrees * 360;
206        } else {
207            dotPercent = 270 + degrees * 360;
208        }
209
210        final double dotRadians = Math.toRadians(dotPercent);
211        canvas.drawCircle(xCenter + (float) (radius * Math.cos(dotRadians)),
212                yCenter + (float) (radius * Math.sin(dotRadians)), mDotRadius, mFill);
213    }
214
215    public static final String PREF_CTV_PAUSED  = "_ctv_paused";
216    public static final String PREF_CTV_INTERVAL  = "_ctv_interval";
217    public static final String PREF_CTV_INTERVAL_START = "_ctv_interval_start";
218    public static final String PREF_CTV_CURRENT_INTERVAL = "_ctv_current_interval";
219    public static final String PREF_CTV_ACCUM_TIME = "_ctv_accum_time";
220    public static final String PREF_CTV_TIMER_MODE = "_ctv_timer_mode";
221    public static final String PREF_CTV_MARKER_TIME = "_ctv_marker_time";
222
223    // Since this view is used in multiple places, use the key to save different instances
224    public void writeToSharedPref(SharedPreferences prefs, String key) {
225        SharedPreferences.Editor editor = prefs.edit();
226        editor.putBoolean (key + PREF_CTV_PAUSED, mPaused);
227        editor.putLong (key + PREF_CTV_INTERVAL, mIntervalTime);
228        editor.putLong (key + PREF_CTV_INTERVAL_START, mIntervalStartTime);
229        editor.putLong (key + PREF_CTV_CURRENT_INTERVAL, mCurrentIntervalTime);
230        editor.putLong (key + PREF_CTV_ACCUM_TIME, mAccumulatedTime);
231        editor.putLong (key + PREF_CTV_MARKER_TIME, mMarkerTime);
232        editor.putBoolean (key + PREF_CTV_TIMER_MODE, mTimerMode);
233        editor.apply();
234    }
235
236    public void readFromSharedPref(SharedPreferences prefs, String key) {
237        mPaused = prefs.getBoolean(key + PREF_CTV_PAUSED, false);
238        mIntervalTime = prefs.getLong(key + PREF_CTV_INTERVAL, 0);
239        mIntervalStartTime = prefs.getLong(key + PREF_CTV_INTERVAL_START, -1);
240        mCurrentIntervalTime = prefs.getLong(key + PREF_CTV_CURRENT_INTERVAL, 0);
241        mAccumulatedTime = prefs.getLong(key + PREF_CTV_ACCUM_TIME, 0);
242        mMarkerTime = prefs.getLong(key + PREF_CTV_MARKER_TIME, -1);
243        mTimerMode = prefs.getBoolean(key + PREF_CTV_TIMER_MODE, false);
244        mAnimate = (mIntervalStartTime != -1 && !mPaused);
245    }
246
247    public void clearSharedPref(SharedPreferences prefs, String key) {
248        SharedPreferences.Editor editor = prefs.edit();
249        editor.remove (Stopwatches.PREF_START_TIME);
250        editor.remove (Stopwatches.PREF_ACCUM_TIME);
251        editor.remove (Stopwatches.PREF_STATE);
252        editor.remove (key + PREF_CTV_PAUSED);
253        editor.remove (key + PREF_CTV_INTERVAL);
254        editor.remove (key + PREF_CTV_INTERVAL_START);
255        editor.remove (key + PREF_CTV_CURRENT_INTERVAL);
256        editor.remove (key + PREF_CTV_ACCUM_TIME);
257        editor.remove (key + PREF_CTV_MARKER_TIME);
258        editor.remove (key + PREF_CTV_TIMER_MODE);
259        editor.apply();
260    }
261}
262