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