RadialTimePickerView.java revision 11a68e18039b6bb2b67c5f7a82fa13d60583b2c3
1eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio/*
2eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Copyright (C) 2013 The Android Open Source Project
3eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio *
4eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Licensed under the Apache License, Version 2.0 (the "License");
5eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * you may not use this file except in compliance with the License.
6eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * You may obtain a copy of the License at
7eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio *
8eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio *      http://www.apache.org/licenses/LICENSE-2.0
9eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio *
10eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Unless required by applicable law or agreed to in writing, software
11eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * distributed under the License is distributed on an "AS IS" BASIS,
12eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * See the License for the specific language governing permissions and
14eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * limitations under the License.
15eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */
16eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
17eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Megliopackage android.widget;
18eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
19eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.animation.Animator;
20eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.animation.AnimatorSet;
21eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.animation.Keyframe;
22eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.animation.ObjectAnimator;
23eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.animation.PropertyValuesHolder;
24eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.animation.ValueAnimator;
25eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.content.Context;
26eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.content.res.Resources;
27eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.content.res.TypedArray;
28eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Canvas;
29eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Color;
30eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Paint;
31ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.graphics.Rect;
32eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Typeface;
33eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.os.Bundle;
34eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.util.AttributeSet;
35ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.util.IntArray;
36eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.util.Log;
37ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.util.MathUtils;
385134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viveretteimport android.util.TypedValue;
39eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.HapticFeedbackConstants;
40eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.MotionEvent;
41eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.View;
42eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.ViewGroup;
43eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.accessibility.AccessibilityEvent;
44eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.accessibility.AccessibilityNodeInfo;
45ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
46eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
47eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport com.android.internal.R;
48ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport com.android.internal.widget.ExploreByTouchHelper;
49eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
50eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport java.util.ArrayList;
51eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport java.util.Calendar;
52eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport java.util.Locale;
53eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
54eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio/**
55eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * View to show a clock circle picker (with one or two picking circles)
56eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio *
57eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * @hide
58eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */
59eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Megliopublic class RadialTimePickerView extends View implements View.OnTouchListener {
60eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final String TAG = "ClockView";
61eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
62eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final boolean DEBUG = false;
63eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
64eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int DEBUG_COLOR = 0x20FF0000;
65eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int DEBUG_TEXT_COLOR = 0x60FF0000;
66eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int DEBUG_STROKE_WIDTH = 2;
67eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
68eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int HOURS = 0;
69eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int MINUTES = 1;
70eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int HOURS_INNER = 2;
71eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
72eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int SELECTOR_CIRCLE = 0;
73eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int SELECTOR_DOT = 1;
74eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int SELECTOR_LINE = 2;
75eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
76eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int AM = 0;
77eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int PM = 1;
78eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
79eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    // Opaque alpha level
80eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int ALPHA_OPAQUE = 255;
81eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
82eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    // Transparent alpha level
83eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int ALPHA_TRANSPARENT = 0;
84eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
85eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    // Alpha level of color for selector.
86518ff0de95e64116ecb07706fc564d4c19197ca7Alan Viverette    private static final int ALPHA_SELECTOR = 60; // was 51
87eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
88eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final float COSINE_30_DEGREES = ((float) Math.sqrt(3)) * 0.5f;
89eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final float SINE_30_DEGREES = 0.5f;
90eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
91eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int DEGREES_FOR_ONE_HOUR = 30;
92eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int DEGREES_FOR_ONE_MINUTE = 6;
93eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
94eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int[] HOURS_NUMBERS = {12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
95eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int[] HOURS_NUMBERS_24 = {0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23};
96eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int[] MINUTES_NUMBERS = {0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55};
97eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
98eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int CENTER_RADIUS = 2;
99eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
100eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static int[] sSnapPrefer30sMap = new int[361];
101eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
102ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final InvalidateUpdateListener mInvalidateUpdateListener =
103ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            new InvalidateUpdateListener();
104ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
105eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final String[] mHours12Texts = new String[12];
106eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final String[] mOuterHours24Texts = new String[12];
107eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final String[] mInnerHours24Texts = new String[12];
108eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final String[] mMinutesTexts = new String[12];
109eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
110eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final Paint[] mPaint = new Paint[2];
111eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette    private final int[] mColor = new int[2];
112eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette    private final IntHolder[] mAlpha = new IntHolder[2];
113eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
114eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final Paint mPaintCenter = new Paint();
115eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
116eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final Paint[][] mPaintSelector = new Paint[2][3];
117eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette    private final int[][] mColorSelector = new int[2][3];
118eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette    private final IntHolder[][] mAlphaSelector = new IntHolder[2][3];
119eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
120eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final Paint mPaintBackground = new Paint();
121eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final Paint mPaintDebug = new Paint();
122eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
123ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final Typeface mTypeface;
124ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
125ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final float[] mCircleRadius = new float[3];
126ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
127ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final float[] mTextSize = new float[2];
128ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
129ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final float[][] mTextGridHeights = new float[2][7];
130ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final float[][] mTextGridWidths = new float[2][7];
131ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
132ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final float[] mInnerTextGridHeights = new float[7];
133ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final float[] mInnerTextGridWidths = new float[7];
134ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
135ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final float[] mCircleRadiusMultiplier = new float[2];
136ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final float[] mNumbersRadiusMultiplier = new float[3];
137ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
138ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final float[] mTextSizeMultiplier = new float[3];
139ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
140ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final float[] mAnimationRadiusMultiplier = new float[3];
141ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
142ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final float mTransitionMidRadiusMultiplier;
143ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final float mTransitionEndRadiusMultiplier;
144ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
145ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final int[] mLineLength = new int[3];
146ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final int[] mSelectionRadius = new int[3];
147ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final float mSelectionRadiusMultiplier;
148ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final int[] mSelectionDegrees = new int[3];
149ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
150ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final ArrayList<Animator> mHoursToMinutesAnims = new ArrayList<Animator>();
151ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final ArrayList<Animator> mMinuteToHoursAnims = new ArrayList<Animator>();
152ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
153ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final RadialPickerTouchHelper mTouchHelper;
154ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
155ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private float mInnerTextSize;
156eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
157eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private boolean mIs24HourMode;
158eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private boolean mShowHours;
159d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette
160d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette    /**
161d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * When in 24-hour mode, indicates that the current hour is between
162d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * 1 and 12 (inclusive).
163d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     */
164eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private boolean mIsOnInnerCircle;
165eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
166eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private int mXCenter;
167eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private int mYCenter;
168eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
169eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private int mMinHypotenuseForInnerNumber;
170eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private int mMaxHypotenuseForOuterNumber;
171eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private int mHalfwayHypotenusePoint;
172eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
173eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private String[] mOuterTextHours;
174eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private String[] mInnerTextHours;
175eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private String[] mOuterTextMinutes;
176eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private AnimatorSet mTransition;
177eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
178eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private int mAmOrPm;
1795134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette    private int mDisabledAlpha;
1805134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette
181eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private OnValueSelectedListener mListener;
182eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
183ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private boolean mInputEnabled = true;
184eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
185eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public interface OnValueSelectedListener {
186eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance);
187eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
188eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
189eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    static {
190eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Prepare mapping to snap touchable degrees to selectable degrees.
191eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        preparePrefer30sMap();
192eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
193eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
194eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    /**
195eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * Split up the 360 degrees of the circle among the 60 selectable values. Assigns a larger
196eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * selectable area to each of the 12 visible values, such that the ratio of space apportioned
197eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * to a visible value : space apportioned to a non-visible value will be 14 : 4.
198eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * E.g. the output of 30 degrees should have a higher range of input associated with it than
199eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * the output of 24 degrees, because 30 degrees corresponds to a visible number on the clock
200eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * circle (5 on the minutes, 1 or 13 on the hours).
201eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     */
202eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static void preparePrefer30sMap() {
203eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // We'll split up the visible output and the non-visible output such that each visible
204eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // output will correspond to a range of 14 associated input degrees, and each non-visible
205eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // output will correspond to a range of 4 associate input degrees, so visible numbers
206eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // are more than 3 times easier to get than non-visible numbers:
207eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // {354-359,0-7}:0, {8-11}:6, {12-15}:12, {16-19}:18, {20-23}:24, {24-37}:30, etc.
208eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        //
209eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // If an output of 30 degrees should correspond to a range of 14 associated degrees, then
210eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // we'll need any input between 24 - 37 to snap to 30. Working out from there, 20-23 should
211eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // snap to 24, while 38-41 should snap to 36. This is somewhat counter-intuitive, that you
212eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // can be touching 36 degrees but have the selection snapped to 30 degrees; however, this
213eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // inconsistency isn't noticeable at such fine-grained degrees, and it affords us the
214eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // ability to aggressively prefer the visible values by a factor of more than 3:1, which
215eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // greatly contributes to the selectability of these values.
216eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
217eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // The first output is 0, and each following output will increment by 6 {0, 6, 12, ...}.
218eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int snappedOutputDegrees = 0;
219eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Count of how many inputs we've designated to the specified output.
220eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int count = 1;
221eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // How many input we expect for a specified output. This will be 14 for output divisible
222eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // by 30, and 4 for the remaining output. We'll special case the outputs of 0 and 360, so
223eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // the caller can decide which they need.
224eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int expectedCount = 8;
225eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Iterate through the input.
226eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        for (int degrees = 0; degrees < 361; degrees++) {
227eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            // Save the input-output mapping.
228eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            sSnapPrefer30sMap[degrees] = snappedOutputDegrees;
229eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            // If this is the last input for the specified output, calculate the next output and
230eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            // the next expected count.
231eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if (count == expectedCount) {
232eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                snappedOutputDegrees += 6;
233eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                if (snappedOutputDegrees == 360) {
234eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    expectedCount = 7;
235eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                } else if (snappedOutputDegrees % 30 == 0) {
236eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    expectedCount = 14;
237eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                } else {
238eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    expectedCount = 4;
239eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                }
240eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                count = 1;
241eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            } else {
242eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                count++;
243eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
244eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
245eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
246eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
247eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    /**
248eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * Returns mapping of any input degrees (0 to 360) to one of 60 selectable output degrees,
249eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * where the degrees corresponding to visible numbers (i.e. those divisible by 30) will be
250eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * weighted heavier than the degrees corresponding to non-visible numbers.
251eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * See {@link #preparePrefer30sMap()} documentation for the rationale and generation of the
252eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * mapping.
253eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     */
254eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static int snapPrefer30s(int degrees) {
255eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (sSnapPrefer30sMap == null) {
256eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            return -1;
257eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
258eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        return sSnapPrefer30sMap[degrees];
259eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
260eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
261eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    /**
262eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * Returns mapping of any input degrees (0 to 360) to one of 12 visible output degrees (all
263eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * multiples of 30), where the input will be "snapped" to the closest visible degrees.
264eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * @param degrees The input degrees
265eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * @param forceHigherOrLower The output may be forced to either the higher or lower step, or may
266eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * be allowed to snap to whichever is closer. Use 1 to force strictly higher, -1 to force
267eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * strictly lower, and 0 to snap to the closer one.
268eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * @return output degrees, will be a multiple of 30
269eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     */
270eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static int snapOnly30s(int degrees, int forceHigherOrLower) {
271eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int stepSize = DEGREES_FOR_ONE_HOUR;
272eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int floor = (degrees / stepSize) * stepSize;
273eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int ceiling = floor + stepSize;
274eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (forceHigherOrLower == 1) {
275eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            degrees = ceiling;
276eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        } else if (forceHigherOrLower == -1) {
277eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if (degrees == floor) {
278eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                floor -= stepSize;
279eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
280eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            degrees = floor;
281eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        } else {
282eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if ((degrees - floor) < (ceiling - degrees)) {
283eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                degrees = floor;
284eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            } else {
285eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                degrees = ceiling;
286eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
287eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
288eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        return degrees;
289eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
290eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
291ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    @SuppressWarnings("unused")
292ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    public RadialTimePickerView(Context context)  {
293ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        this(context, null);
294ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
295ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
296eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public RadialTimePickerView(Context context, AttributeSet attrs)  {
297eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        this(context, attrs, R.attr.timePickerStyle);
298eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
299eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
300ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    public RadialTimePickerView(Context context, AttributeSet attrs, int defStyleAttr)  {
301ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        this(context, attrs, defStyleAttr, 0);
302ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
303ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
304ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    public RadialTimePickerView(
305ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)  {
306eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        super(context, attrs);
307eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
3085134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette        // Pull disabled alpha from theme.
3095134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette        final TypedValue outValue = new TypedValue();
3105134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette        context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true);
3115134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette        mDisabledAlpha = (int) (outValue.getFloat() * 255 + 0.5f);
3125134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette
313eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // process style attributes
3145134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette        final Resources res = getResources();
315eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.TimePicker,
316ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                defStyleAttr, defStyleRes);
317eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
318eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mTypeface = Typeface.create("sans-serif", Typeface.NORMAL);
319eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
320eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        // Initialize all alpha values to opaque.
321eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        for (int i = 0; i < mAlpha.length; i++) {
322eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mAlpha[i] = new IntHolder(ALPHA_OPAQUE);
323eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        }
324eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        for (int i = 0; i < mAlphaSelector.length; i++) {
325eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            for (int j = 0; j < mAlphaSelector[i].length; j++) {
326eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette                mAlphaSelector[i][j] = new IntHolder(ALPHA_OPAQUE);
327eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            }
328eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        }
329eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
330eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        final int numbersTextColor = a.getColor(R.styleable.TimePicker_numbersTextColor,
331830960cce032a1b0dc0cf54bcc44ffa339388c21Alan Viverette                res.getColor(R.color.timepicker_default_text_color_material));
332eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
333eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[HOURS] = new Paint();
334eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[HOURS].setAntiAlias(true);
335eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[HOURS].setTextAlign(Paint.Align.CENTER);
336eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        mColor[HOURS] = numbersTextColor;
337eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
338eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[MINUTES] = new Paint();
339eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[MINUTES].setAntiAlias(true);
340eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[MINUTES].setTextAlign(Paint.Align.CENTER);
341eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        mColor[MINUTES] = numbersTextColor;
342eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
343eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintCenter.setColor(numbersTextColor);
344eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintCenter.setAntiAlias(true);
345eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintCenter.setTextAlign(Paint.Align.CENTER);
346eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
347eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[HOURS][SELECTOR_CIRCLE] = new Paint();
348eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[HOURS][SELECTOR_CIRCLE].setAntiAlias(true);
349eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        mColorSelector[HOURS][SELECTOR_CIRCLE] = a.getColor(
350eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette                R.styleable.TimePicker_numbersSelectorColor,
351830960cce032a1b0dc0cf54bcc44ffa339388c21Alan Viverette                R.color.timepicker_default_selector_color_material);
352eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
353eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[HOURS][SELECTOR_DOT] = new Paint();
354eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[HOURS][SELECTOR_DOT].setAntiAlias(true);
355eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        mColorSelector[HOURS][SELECTOR_DOT] = a.getColor(
356eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette                R.styleable.TimePicker_numbersSelectorColor,
357830960cce032a1b0dc0cf54bcc44ffa339388c21Alan Viverette                R.color.timepicker_default_selector_color_material);
358eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
359eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[HOURS][SELECTOR_LINE] = new Paint();
360eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[HOURS][SELECTOR_LINE].setAntiAlias(true);
361eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[HOURS][SELECTOR_LINE].setStrokeWidth(2);
362eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        mColorSelector[HOURS][SELECTOR_LINE] = a.getColor(
363eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette                R.styleable.TimePicker_numbersSelectorColor,
364830960cce032a1b0dc0cf54bcc44ffa339388c21Alan Viverette                R.color.timepicker_default_selector_color_material);
365eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
366eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[MINUTES][SELECTOR_CIRCLE] = new Paint();
367eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[MINUTES][SELECTOR_CIRCLE].setAntiAlias(true);
368eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        mColorSelector[MINUTES][SELECTOR_CIRCLE] = a.getColor(
369eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette                R.styleable.TimePicker_numbersSelectorColor,
370830960cce032a1b0dc0cf54bcc44ffa339388c21Alan Viverette                R.color.timepicker_default_selector_color_material);
371eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
372eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[MINUTES][SELECTOR_DOT] = new Paint();
373eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[MINUTES][SELECTOR_DOT].setAntiAlias(true);
374eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        mColorSelector[MINUTES][SELECTOR_DOT] = a.getColor(
375eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette                R.styleable.TimePicker_numbersSelectorColor,
376830960cce032a1b0dc0cf54bcc44ffa339388c21Alan Viverette                R.color.timepicker_default_selector_color_material);
377eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
378eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[MINUTES][SELECTOR_LINE] = new Paint();
379eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[MINUTES][SELECTOR_LINE].setAntiAlias(true);
380eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[MINUTES][SELECTOR_LINE].setStrokeWidth(2);
381eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        mColorSelector[MINUTES][SELECTOR_LINE] = a.getColor(
382eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette                R.styleable.TimePicker_numbersSelectorColor,
383830960cce032a1b0dc0cf54bcc44ffa339388c21Alan Viverette                R.color.timepicker_default_selector_color_material);
384eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
385eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        mPaintBackground.setColor(a.getColor(R.styleable.TimePicker_numbersBackgroundColor,
386830960cce032a1b0dc0cf54bcc44ffa339388c21Alan Viverette                res.getColor(R.color.timepicker_default_numbers_background_color_material)));
387eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintBackground.setAntiAlias(true);
388eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
389eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (DEBUG) {
390eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mPaintDebug.setColor(DEBUG_COLOR);
391eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mPaintDebug.setAntiAlias(true);
392eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mPaintDebug.setStrokeWidth(DEBUG_STROKE_WIDTH);
393eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mPaintDebug.setStyle(Paint.Style.STROKE);
394eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mPaintDebug.setTextAlign(Paint.Align.CENTER);
395eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
396eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
397eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mShowHours = true;
398eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mIs24HourMode = false;
399eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mAmOrPm = AM;
400eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
401ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        // Set up accessibility components.
402ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        mTouchHelper = new RadialPickerTouchHelper();
403ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setAccessibilityDelegate(mTouchHelper);
404ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
405ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
406ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
407ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
408ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
409eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        initHoursAndMinutesText();
410eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        initData();
411eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
412eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mTransitionMidRadiusMultiplier =  Float.parseFloat(
413eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                res.getString(R.string.timepicker_transition_mid_radius_multiplier));
414eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mTransitionEndRadiusMultiplier = Float.parseFloat(
415eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                res.getString(R.string.timepicker_transition_end_radius_multiplier));
416eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
417eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mTextGridHeights[HOURS] = new float[7];
418eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mTextGridHeights[MINUTES] = new float[7];
419eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
420eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mSelectionRadiusMultiplier = Float.parseFloat(
421eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                res.getString(R.string.timepicker_selection_radius_multiplier));
422eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
423eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        a.recycle();
424eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
425eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        setOnTouchListener(this);
426ba9bf41a77261471a4dc9d7964aec41726b4e4e6Alan Viverette        setClickable(true);
427eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
428eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Initial values
429eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final Calendar calendar = Calendar.getInstance(Locale.getDefault());
430eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int currentHour = calendar.get(Calendar.HOUR_OF_DAY);
431eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int currentMinute = calendar.get(Calendar.MINUTE);
432eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
433ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentHourInternal(currentHour, false, false);
434ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentMinuteInternal(currentMinute, false);
435eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
436eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        setHapticFeedbackEnabled(true);
437eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
438eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
439eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    /**
440eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * Measure the view to end up as a square, based on the minimum of the height and width.
441eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     */
442eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    @Override
443eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
444eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
445eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
446eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
447eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
448eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int minDimension = Math.min(measuredWidth, measuredHeight);
449eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
450eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        super.onMeasure(MeasureSpec.makeMeasureSpec(minDimension, widthMode),
451eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                MeasureSpec.makeMeasureSpec(minDimension, heightMode));
452eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
453eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
454eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void initialize(int hour, int minute, boolean is24HourMode) {
455448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette        if (mIs24HourMode != is24HourMode) {
456448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette            mIs24HourMode = is24HourMode;
457448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette            initData();
458448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette        }
459ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
460ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentHourInternal(hour, false, false);
461ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentMinuteInternal(minute, false);
462eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
463eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
464eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void setCurrentItemShowing(int item, boolean animate) {
465eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        switch (item){
466eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            case HOURS:
467eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                showHours(animate);
468eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                break;
469eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            case MINUTES:
470eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                showMinutes(animate);
471eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                break;
472eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            default:
473eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                Log.e(TAG, "ClockView does not support showing item " + item);
474eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
475eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
476eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
477eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public int getCurrentItemShowing() {
478eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        return mShowHours ? HOURS : MINUTES;
479eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
480eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
481eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void setOnValueSelectedListener(OnValueSelectedListener listener) {
482eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mListener = listener;
483eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
484eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
485d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette    /**
486d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * Sets the current hour in 24-hour time.
487d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     *
488d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * @param hour the current hour between 0 and 23 (inclusive)
489d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     */
490eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void setCurrentHour(int hour) {
491ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentHourInternal(hour, true, false);
492ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
493ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
494ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    /**
495ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     * Sets the current hour.
496ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     *
497ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     * @param hour The current hour
498ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     * @param callback Whether the value listener should be invoked
499ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     * @param autoAdvance Whether the listener should auto-advance to the next
500ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     *                    selection mode, e.g. hour to minutes
501ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     */
502ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private void setCurrentHourInternal(int hour, boolean callback, boolean autoAdvance) {
503eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int degrees = (hour % 12) * DEGREES_FOR_ONE_HOUR;
504eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mSelectionDegrees[HOURS] = degrees;
505eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mSelectionDegrees[HOURS_INNER] = degrees;
506d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette
507d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette        // 0 is 12 AM (midnight) and 12 is 12 PM (noon).
508ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        final int amOrPm = (hour == 0 || (hour % 24) < 12) ? AM : PM;
509ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        final boolean isOnInnerCircle = mIs24HourMode && hour >= 1 && hour <= 12;
510ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (mAmOrPm != amOrPm || mIsOnInnerCircle != isOnInnerCircle) {
511ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            mAmOrPm = amOrPm;
512ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            mIsOnInnerCircle = isOnInnerCircle;
513ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
514ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            initData();
515ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            updateLayoutData();
516ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            mTouchHelper.invalidateRoot();
517ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
518d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette
519eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        invalidate();
520ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
521ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (callback && mListener != null) {
522ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            mListener.onValueSelected(HOURS, hour, autoAdvance);
523ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
524eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
525eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
526d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette    /**
527d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * Returns the current hour in 24-hour time.
528d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     *
529d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * @return the current hour between 0 and 23 (inclusive)
530d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     */
531eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public int getCurrentHour() {
532ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return getHourForDegrees(
533ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                mSelectionDegrees[mIsOnInnerCircle ? HOURS_INNER : HOURS], mIsOnInnerCircle);
534ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
535ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
536ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private int getHourForDegrees(int degrees, boolean innerCircle) {
537ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        int hour = (degrees / DEGREES_FOR_ONE_HOUR) % 12;
538eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mIs24HourMode) {
539d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette            // Convert the 12-hour value into 24-hour time based on where the
540d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette            // selector is positioned.
541ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (innerCircle && hour == 0) {
542d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette                // Inner circle is 1 through 12.
543d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette                hour = 12;
544ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else if (!innerCircle && hour != 0) {
545d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette                // Outer circle is 13 through 23 and 0.
546d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette                hour += 12;
547eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
548d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette        } else if (mAmOrPm == PM) {
549d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette            hour += 12;
550eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
551d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette        return hour;
552eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
553eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
554ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private int getDegreesForHour(int hour) {
555ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        // Convert to be 0-11.
556ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (mIs24HourMode) {
557ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (hour >= 12) {
558ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                hour -= 12;
559ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
560ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        } else if (hour == 12) {
561ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            hour = 0;
562ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
563ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return hour * DEGREES_FOR_ONE_HOUR;
564ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
565ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
566eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void setCurrentMinute(int minute) {
567ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentMinuteInternal(minute, true);
568ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
569ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
570ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private void setCurrentMinuteInternal(int minute, boolean callback) {
571eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mSelectionDegrees[MINUTES] = (minute % 60) * DEGREES_FOR_ONE_MINUTE;
572ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
573eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        invalidate();
574ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
575ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (callback && mListener != null) {
576ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            mListener.onValueSelected(MINUTES, minute, false);
577ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
578eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
579eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
580eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    // Returns minutes in 0-59 range
581eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public int getCurrentMinute() {
582ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return getMinuteForDegrees(mSelectionDegrees[MINUTES]);
583ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
584ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
585ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private int getMinuteForDegrees(int degrees) {
586ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return degrees / DEGREES_FOR_ONE_MINUTE;
587ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
588ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
589ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private int getDegreesForMinute(int minute) {
590ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return minute * DEGREES_FOR_ONE_MINUTE;
591eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
592eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
593eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void setAmOrPm(int val) {
594eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mAmOrPm = (val % 2);
595eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        invalidate();
596ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        mTouchHelper.invalidateRoot();
597eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
598eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
599eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public int getAmOrPm() {
600eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        return mAmOrPm;
601eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
602eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
603eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void showHours(boolean animate) {
604eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mShowHours) return;
605eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mShowHours = true;
606eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (animate) {
607eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            startMinutesToHoursAnimation();
608eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
609eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        initData();
610eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        updateLayoutData();
611eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        invalidate();
612eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
613eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
614eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void showMinutes(boolean animate) {
615eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (!mShowHours) return;
616eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mShowHours = false;
617eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (animate) {
618eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            startHoursToMinutesAnimation();
619eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
620eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        initData();
621eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        updateLayoutData();
622eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        invalidate();
623eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
624eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
625eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void initHoursAndMinutesText() {
626eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Initialize the hours and minutes numbers.
627eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        for (int i = 0; i < 12; i++) {
628eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mHours12Texts[i] = String.format("%d", HOURS_NUMBERS[i]);
629eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mOuterHours24Texts[i] = String.format("%02d", HOURS_NUMBERS_24[i]);
630eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mInnerHours24Texts[i] = String.format("%d", HOURS_NUMBERS[i]);
631eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mMinutesTexts[i] = String.format("%02d", MINUTES_NUMBERS[i]);
632eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
633eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
634eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
635eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void initData() {
636eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mIs24HourMode) {
637eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mOuterTextHours = mOuterHours24Texts;
638eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mInnerTextHours = mInnerHours24Texts;
639eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        } else {
640eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mOuterTextHours = mHours12Texts;
641eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mInnerTextHours = null;
642eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
643eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
644eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mOuterTextMinutes = mMinutesTexts;
645eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
646eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final Resources res = getResources();
647eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
648eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mShowHours) {
649eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if (mIs24HourMode) {
650eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                mCircleRadiusMultiplier[HOURS] = Float.parseFloat(
651eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                        res.getString(R.string.timepicker_circle_radius_multiplier_24HourMode));
652eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                mNumbersRadiusMultiplier[HOURS] = Float.parseFloat(
653eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                        res.getString(R.string.timepicker_numbers_radius_multiplier_outer));
654eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                mTextSizeMultiplier[HOURS] = Float.parseFloat(
655eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                        res.getString(R.string.timepicker_text_size_multiplier_outer));
656eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
657eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                mNumbersRadiusMultiplier[HOURS_INNER] = Float.parseFloat(
658eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                        res.getString(R.string.timepicker_numbers_radius_multiplier_inner));
659eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                mTextSizeMultiplier[HOURS_INNER] = Float.parseFloat(
660eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                        res.getString(R.string.timepicker_text_size_multiplier_inner));
661eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            } else {
662eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                mCircleRadiusMultiplier[HOURS] = Float.parseFloat(
663eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                        res.getString(R.string.timepicker_circle_radius_multiplier));
664eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                mNumbersRadiusMultiplier[HOURS] = Float.parseFloat(
665eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                        res.getString(R.string.timepicker_numbers_radius_multiplier_normal));
666eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                mTextSizeMultiplier[HOURS] = Float.parseFloat(
667eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                        res.getString(R.string.timepicker_text_size_multiplier_normal));
668eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
669eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        } else {
670eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mCircleRadiusMultiplier[MINUTES] = Float.parseFloat(
671eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    res.getString(R.string.timepicker_circle_radius_multiplier));
672eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mNumbersRadiusMultiplier[MINUTES] = Float.parseFloat(
673eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    res.getString(R.string.timepicker_numbers_radius_multiplier_normal));
674eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mTextSizeMultiplier[MINUTES] = Float.parseFloat(
675eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    res.getString(R.string.timepicker_text_size_multiplier_normal));
676eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
677eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
678eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mAnimationRadiusMultiplier[HOURS] = 1;
679eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mAnimationRadiusMultiplier[HOURS_INNER] = 1;
680eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mAnimationRadiusMultiplier[MINUTES] = 1;
681eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
682eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        mAlpha[HOURS].setValue(mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT);
683eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        mAlpha[MINUTES].setValue(mShowHours ? ALPHA_TRANSPARENT : ALPHA_OPAQUE);
684eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
685eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        mAlphaSelector[HOURS][SELECTOR_CIRCLE].setValue(
686eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette                mShowHours ? ALPHA_SELECTOR : ALPHA_TRANSPARENT);
687eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        mAlphaSelector[HOURS][SELECTOR_DOT].setValue(
688eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT);
689eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        mAlphaSelector[HOURS][SELECTOR_LINE].setValue(
690eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                mShowHours ? ALPHA_SELECTOR : ALPHA_TRANSPARENT);
691eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
692eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        mAlphaSelector[MINUTES][SELECTOR_CIRCLE].setValue(
693eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                mShowHours ? ALPHA_TRANSPARENT : ALPHA_SELECTOR);
694eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        mAlphaSelector[MINUTES][SELECTOR_DOT].setValue(
695eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                mShowHours ? ALPHA_TRANSPARENT : ALPHA_OPAQUE);
696eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        mAlphaSelector[MINUTES][SELECTOR_LINE].setValue(
697eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                mShowHours ? ALPHA_TRANSPARENT : ALPHA_SELECTOR);
698eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
699eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
700eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    @Override
701eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
702eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        updateLayoutData();
703eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
704eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
705eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void updateLayoutData() {
706eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mXCenter = getWidth() / 2;
707eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mYCenter = getHeight() / 2;
708eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
709eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int min = Math.min(mXCenter, mYCenter);
710eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
711eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mCircleRadius[HOURS] = min * mCircleRadiusMultiplier[HOURS];
712eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mCircleRadius[HOURS_INNER] = min * mCircleRadiusMultiplier[HOURS];
713eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mCircleRadius[MINUTES] = min * mCircleRadiusMultiplier[MINUTES];
714eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
715eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mMinHypotenuseForInnerNumber = (int) (mCircleRadius[HOURS]
716eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                * mNumbersRadiusMultiplier[HOURS_INNER]) - mSelectionRadius[HOURS];
717eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mMaxHypotenuseForOuterNumber = (int) (mCircleRadius[HOURS]
718eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                * mNumbersRadiusMultiplier[HOURS]) + mSelectionRadius[HOURS];
719eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mHalfwayHypotenusePoint = (int) (mCircleRadius[HOURS]
720eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                * ((mNumbersRadiusMultiplier[HOURS] + mNumbersRadiusMultiplier[HOURS_INNER]) / 2));
721eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
722eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mTextSize[HOURS] = mCircleRadius[HOURS] * mTextSizeMultiplier[HOURS];
723eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mTextSize[MINUTES] = mCircleRadius[MINUTES] * mTextSizeMultiplier[MINUTES];
724eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
725eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mIs24HourMode) {
726eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mInnerTextSize = mCircleRadius[HOURS] * mTextSizeMultiplier[HOURS_INNER];
727eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
728eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
729eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        calculateGridSizesHours();
730eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        calculateGridSizesMinutes();
731eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
732eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mSelectionRadius[HOURS] = (int) (mCircleRadius[HOURS] * mSelectionRadiusMultiplier);
733eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mSelectionRadius[HOURS_INNER] = mSelectionRadius[HOURS];
734eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mSelectionRadius[MINUTES] = (int) (mCircleRadius[MINUTES] * mSelectionRadiusMultiplier);
735ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
736ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        mTouchHelper.invalidateRoot();
737eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
738eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
739eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    @Override
740eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void onDraw(Canvas canvas) {
7415134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette        if (!mInputEnabled) {
7425134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette            canvas.saveLayerAlpha(0, 0, getWidth(), getHeight(), mDisabledAlpha);
7435134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette        } else {
7445134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette            canvas.save();
7455134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette        }
746eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
747eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        calculateGridSizesHours();
748eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        calculateGridSizesMinutes();
749eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
750eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        drawCircleBackground(canvas);
751eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        drawSelector(canvas);
752eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
753eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        drawTextElements(canvas, mTextSize[HOURS], mTypeface, mOuterTextHours,
754eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette                mTextGridWidths[HOURS], mTextGridHeights[HOURS], mPaint[HOURS],
755eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette                mColor[HOURS], mAlpha[HOURS].getValue());
756eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
757eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mIs24HourMode && mInnerTextHours != null) {
758eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            drawTextElements(canvas, mInnerTextSize, mTypeface, mInnerTextHours,
759eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette                    mInnerTextGridWidths, mInnerTextGridHeights, mPaint[HOURS],
760eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette                    mColor[HOURS], mAlpha[HOURS].getValue());
761eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
762eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
763eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mOuterTextMinutes,
764eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette                mTextGridWidths[MINUTES], mTextGridHeights[MINUTES], mPaint[MINUTES],
765eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette                mColor[MINUTES], mAlpha[MINUTES].getValue());
766eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
767eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        drawCenter(canvas);
768eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
769eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (DEBUG) {
770eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            drawDebug(canvas);
771eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
772eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
773eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        canvas.restore();
774eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
775eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
776eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void drawCircleBackground(Canvas canvas) {
777eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        canvas.drawCircle(mXCenter, mYCenter, mCircleRadius[HOURS], mPaintBackground);
778eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
779eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
780eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void drawCenter(Canvas canvas) {
781eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        canvas.drawCircle(mXCenter, mYCenter, CENTER_RADIUS, mPaintCenter);
782eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
783eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
784eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void drawSelector(Canvas canvas) {
785eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        drawSelector(canvas, mIsOnInnerCircle ? HOURS_INNER : HOURS);
786eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        drawSelector(canvas, MINUTES);
787eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
788eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
789eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette    private int getMultipliedAlpha(int argb, int alpha) {
790eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        return (int) (Color.alpha(argb) * (alpha / 255.0) + 0.5);
791eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette    }
792eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
793eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void drawSelector(Canvas canvas, int index) {
794eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Calculate the current radius at which to place the selection circle.
795eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mLineLength[index] = (int) (mCircleRadius[index]
796eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                * mNumbersRadiusMultiplier[index] * mAnimationRadiusMultiplier[index]);
797eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
798eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        double selectionRadians = Math.toRadians(mSelectionDegrees[index]);
799eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
800eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int pointX = mXCenter + (int) (mLineLength[index] * Math.sin(selectionRadians));
801eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int pointY = mYCenter - (int) (mLineLength[index] * Math.cos(selectionRadians));
802eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
803eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        int color;
804eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        int alpha;
805eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        Paint paint;
806eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
807eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Draw the selection circle
808eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        color = mColorSelector[index % 2][SELECTOR_CIRCLE];
809eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        alpha = mAlphaSelector[index % 2][SELECTOR_CIRCLE].getValue();
810eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        paint = mPaintSelector[index % 2][SELECTOR_CIRCLE];
811eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        paint.setColor(color);
812eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        paint.setAlpha(getMultipliedAlpha(color, alpha));
813eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        canvas.drawCircle(pointX, pointY, mSelectionRadius[index], paint);
814eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
815eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Draw the dot if needed
816eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mSelectionDegrees[index] % 30 != 0) {
817eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            // We're not on a direct tick
818eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            color = mColorSelector[index % 2][SELECTOR_DOT];
819eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            alpha = mAlphaSelector[index % 2][SELECTOR_DOT].getValue();
820eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            paint = mPaintSelector[index % 2][SELECTOR_DOT];
821eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            paint.setColor(color);
822eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            paint.setAlpha(getMultipliedAlpha(color, alpha));
823eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            canvas.drawCircle(pointX, pointY, (mSelectionRadius[index] * 2 / 7), paint);
824eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        } else {
825eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            // We're not drawing the dot, so shorten the line to only go as far as the edge of the
826eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            // selection circle
827eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            int lineLength = mLineLength[index] - mSelectionRadius[index];
828eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            pointX = mXCenter + (int) (lineLength * Math.sin(selectionRadians));
829eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            pointY = mYCenter - (int) (lineLength * Math.cos(selectionRadians));
830eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
831eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
832eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Draw the line
833eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        color = mColorSelector[index % 2][SELECTOR_LINE];
834eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        alpha = mAlphaSelector[index % 2][SELECTOR_LINE].getValue();
835eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        paint = mPaintSelector[index % 2][SELECTOR_LINE];
836eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        paint.setColor(color);
837eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        paint.setAlpha(getMultipliedAlpha(color, alpha));
838eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        canvas.drawLine(mXCenter, mYCenter, pointX, pointY, paint);
839eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
840eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
841eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void drawDebug(Canvas canvas) {
842eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Draw outer numbers circle
843eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final float outerRadius = mCircleRadius[HOURS] * mNumbersRadiusMultiplier[HOURS];
844eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        canvas.drawCircle(mXCenter, mYCenter, outerRadius, mPaintDebug);
845eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
846eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Draw inner numbers circle
847eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final float innerRadius = mCircleRadius[HOURS] * mNumbersRadiusMultiplier[HOURS_INNER];
848eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        canvas.drawCircle(mXCenter, mYCenter, innerRadius, mPaintDebug);
849eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
850eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Draw outer background circle
851eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        canvas.drawCircle(mXCenter, mYCenter, mCircleRadius[HOURS], mPaintDebug);
852eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
853eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Draw outer rectangle for circles
854eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        float left = mXCenter - outerRadius;
855eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        float top = mYCenter - outerRadius;
856eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        float right = mXCenter + outerRadius;
857eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        float bottom = mYCenter + outerRadius;
858ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        canvas.drawRect(left, top, right, bottom, mPaintDebug);
859eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
860eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Draw outer rectangle for background
861eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        left = mXCenter - mCircleRadius[HOURS];
862eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        top = mYCenter - mCircleRadius[HOURS];
863eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        right = mXCenter + mCircleRadius[HOURS];
864eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        bottom = mYCenter + mCircleRadius[HOURS];
865ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        canvas.drawRect(left, top, right, bottom, mPaintDebug);
866eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
867eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Draw outer view rectangle
868ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        canvas.drawRect(0, 0, getWidth(), getHeight(), mPaintDebug);
869eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
870eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Draw selected time
871eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final String selected = String.format("%02d:%02d", getCurrentHour(), getCurrentMinute());
872eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
873eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
874eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                ViewGroup.LayoutParams.WRAP_CONTENT);
875eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        TextView tv = new TextView(getContext());
876eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        tv.setLayoutParams(lp);
877eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        tv.setText(selected);
878eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        tv.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
879eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        Paint paint = tv.getPaint();
880eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        paint.setColor(DEBUG_TEXT_COLOR);
881eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
882eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int width = tv.getMeasuredWidth();
883eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
884eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        float height = paint.descent() - paint.ascent();
885eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        float x = mXCenter - width / 2;
886eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        float y = mYCenter + 1.5f * height;
887eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
88867945c11a5e9547f71be91ceb99e7b9ff15a6292Alan Viverette        canvas.drawText(selected, x, y, paint);
889eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
890eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
891eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void calculateGridSizesHours() {
892eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Calculate the text positions
893eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        float numbersRadius = mCircleRadius[HOURS]
894eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                * mNumbersRadiusMultiplier[HOURS] * mAnimationRadiusMultiplier[HOURS];
895eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
896eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Calculate the positions for the 12 numbers in the main circle.
897eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        calculateGridSizes(mPaint[HOURS], numbersRadius, mXCenter, mYCenter,
898eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                mTextSize[HOURS], mTextGridHeights[HOURS], mTextGridWidths[HOURS]);
899eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
900eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // If we have an inner circle, calculate those positions too.
901eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mIs24HourMode) {
902eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            float innerNumbersRadius = mCircleRadius[HOURS_INNER]
903eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    * mNumbersRadiusMultiplier[HOURS_INNER]
904eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    * mAnimationRadiusMultiplier[HOURS_INNER];
905eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
906eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            calculateGridSizes(mPaint[HOURS], innerNumbersRadius, mXCenter, mYCenter,
907eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    mInnerTextSize, mInnerTextGridHeights, mInnerTextGridWidths);
908eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
909eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
910eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
911eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void calculateGridSizesMinutes() {
912eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Calculate the text positions
913eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        float numbersRadius = mCircleRadius[MINUTES]
914eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                * mNumbersRadiusMultiplier[MINUTES] * mAnimationRadiusMultiplier[MINUTES];
915eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
916eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Calculate the positions for the 12 numbers in the main circle.
917eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        calculateGridSizes(mPaint[MINUTES], numbersRadius, mXCenter, mYCenter,
918eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                mTextSize[MINUTES], mTextGridHeights[MINUTES], mTextGridWidths[MINUTES]);
919eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
920eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
921eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
922eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    /**
923eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * Using the trigonometric Unit Circle, calculate the positions that the text will need to be
924eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * drawn at based on the specified circle radius. Place the values in the textGridHeights and
925eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * textGridWidths parameters.
926eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     */
927eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static void calculateGridSizes(Paint paint, float numbersRadius, float xCenter,
928eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            float yCenter, float textSize, float[] textGridHeights, float[] textGridWidths) {
929eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        /*
930eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio         * The numbers need to be drawn in a 7x7 grid, representing the points on the Unit Circle.
931eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio         */
932eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final float offset1 = numbersRadius;
933eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // cos(30) = a / r => r * cos(30)
934eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final float offset2 = numbersRadius * COSINE_30_DEGREES;
935eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // sin(30) = o / r => r * sin(30)
936eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final float offset3 = numbersRadius * SINE_30_DEGREES;
937eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
938eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        paint.setTextSize(textSize);
939eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // We'll need yTextBase to be slightly lower to account for the text's baseline.
940eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        yCenter -= (paint.descent() + paint.ascent()) / 2;
941eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
942eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        textGridHeights[0] = yCenter - offset1;
943eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        textGridWidths[0] = xCenter - offset1;
944eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        textGridHeights[1] = yCenter - offset2;
945eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        textGridWidths[1] = xCenter - offset2;
946eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        textGridHeights[2] = yCenter - offset3;
947eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        textGridWidths[2] = xCenter - offset3;
948eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        textGridHeights[3] = yCenter;
949eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        textGridWidths[3] = xCenter;
950eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        textGridHeights[4] = yCenter + offset3;
951eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        textGridWidths[4] = xCenter + offset3;
952eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        textGridHeights[5] = yCenter + offset2;
953eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        textGridWidths[5] = xCenter + offset2;
954eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        textGridHeights[6] = yCenter + offset1;
955eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        textGridWidths[6] = xCenter + offset1;
956eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
957eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
958eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    /**
959eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * Draw the 12 text values at the positions specified by the textGrid parameters.
960eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     */
961eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void drawTextElements(Canvas canvas, float textSize, Typeface typeface, String[] texts,
962eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            float[] textGridWidths, float[] textGridHeights, Paint paint, int color, int alpha) {
963eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        paint.setTextSize(textSize);
964eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        paint.setTypeface(typeface);
965eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        paint.setColor(color);
966eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        paint.setAlpha(getMultipliedAlpha(color, alpha));
967eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        canvas.drawText(texts[0], textGridWidths[3], textGridHeights[0], paint);
968eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        canvas.drawText(texts[1], textGridWidths[4], textGridHeights[1], paint);
969eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        canvas.drawText(texts[2], textGridWidths[5], textGridHeights[2], paint);
970eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        canvas.drawText(texts[3], textGridWidths[6], textGridHeights[3], paint);
971eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        canvas.drawText(texts[4], textGridWidths[5], textGridHeights[4], paint);
972eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        canvas.drawText(texts[5], textGridWidths[4], textGridHeights[5], paint);
973eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        canvas.drawText(texts[6], textGridWidths[3], textGridHeights[6], paint);
974eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        canvas.drawText(texts[7], textGridWidths[2], textGridHeights[5], paint);
975eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        canvas.drawText(texts[8], textGridWidths[1], textGridHeights[4], paint);
976eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        canvas.drawText(texts[9], textGridWidths[0], textGridHeights[3], paint);
977eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        canvas.drawText(texts[10], textGridWidths[1], textGridHeights[2], paint);
978eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        canvas.drawText(texts[11], textGridWidths[2], textGridHeights[1], paint);
979eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
980eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
981eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    // Used for animating the hours by changing their radius
982ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    @SuppressWarnings("unused")
983eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void setAnimationRadiusMultiplierHours(float animationRadiusMultiplier) {
984eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mAnimationRadiusMultiplier[HOURS] = animationRadiusMultiplier;
985eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mAnimationRadiusMultiplier[HOURS_INNER] = animationRadiusMultiplier;
986eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
987eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
988eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    // Used for animating the minutes by changing their radius
989ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    @SuppressWarnings("unused")
990eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void setAnimationRadiusMultiplierMinutes(float animationRadiusMultiplier) {
991eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mAnimationRadiusMultiplier[MINUTES] = animationRadiusMultiplier;
992eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
993eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
994eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static ObjectAnimator getRadiusDisappearAnimator(Object target,
995eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            String radiusPropertyName, InvalidateUpdateListener updateListener,
996eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            float midRadiusMultiplier, float endRadiusMultiplier) {
997eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        Keyframe kf0, kf1, kf2;
998eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        float midwayPoint = 0.2f;
999eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int duration = 500;
1000eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1001eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        kf0 = Keyframe.ofFloat(0f, 1);
1002eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        kf1 = Keyframe.ofFloat(midwayPoint, midRadiusMultiplier);
1003eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        kf2 = Keyframe.ofFloat(1f, endRadiusMultiplier);
1004eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        PropertyValuesHolder radiusDisappear = PropertyValuesHolder.ofKeyframe(
1005eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                radiusPropertyName, kf0, kf1, kf2);
1006eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1007eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(
1008eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                target, radiusDisappear).setDuration(duration);
1009eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        animator.addUpdateListener(updateListener);
1010eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        return animator;
1011eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
1012eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1013eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static ObjectAnimator getRadiusReappearAnimator(Object target,
1014eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            String radiusPropertyName, InvalidateUpdateListener updateListener,
1015eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            float midRadiusMultiplier, float endRadiusMultiplier) {
1016eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        Keyframe kf0, kf1, kf2, kf3;
1017eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        float midwayPoint = 0.2f;
1018eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int duration = 500;
1019eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1020eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Set up animator for reappearing.
1021eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        float delayMultiplier = 0.25f;
1022eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        float transitionDurationMultiplier = 1f;
1023eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier;
1024eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int totalDuration = (int) (duration * totalDurationMultiplier);
1025eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        float delayPoint = (delayMultiplier * duration) / totalDuration;
1026eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        midwayPoint = 1 - (midwayPoint * (1 - delayPoint));
1027eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1028eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        kf0 = Keyframe.ofFloat(0f, endRadiusMultiplier);
1029eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        kf1 = Keyframe.ofFloat(delayPoint, endRadiusMultiplier);
1030eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        kf2 = Keyframe.ofFloat(midwayPoint, midRadiusMultiplier);
1031eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        kf3 = Keyframe.ofFloat(1f, 1);
1032eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        PropertyValuesHolder radiusReappear = PropertyValuesHolder.ofKeyframe(
1033eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                radiusPropertyName, kf0, kf1, kf2, kf3);
1034eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1035eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(
1036eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                target, radiusReappear).setDuration(totalDuration);
1037eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        animator.addUpdateListener(updateListener);
1038eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        return animator;
1039eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
1040eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1041eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette    private static ObjectAnimator getFadeOutAnimator(IntHolder target, int startAlpha, int endAlpha,
1042eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                InvalidateUpdateListener updateListener) {
1043eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int duration = 500;
1044eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        ObjectAnimator animator = ObjectAnimator.ofInt(target, "value", startAlpha, endAlpha);
1045eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        animator.setDuration(duration);
1046eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        animator.addUpdateListener(updateListener);
1047eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1048eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        return animator;
1049eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
1050eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1051eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette    private static ObjectAnimator getFadeInAnimator(IntHolder target, int startAlpha, int endAlpha,
1052eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                InvalidateUpdateListener updateListener) {
1053eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        Keyframe kf0, kf1, kf2;
1054eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int duration = 500;
1055eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1056eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Set up animator for reappearing.
1057eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        float delayMultiplier = 0.25f;
1058eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        float transitionDurationMultiplier = 1f;
1059eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier;
1060eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int totalDuration = (int) (duration * totalDurationMultiplier);
1061eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        float delayPoint = (delayMultiplier * duration) / totalDuration;
1062eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1063eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        kf0 = Keyframe.ofInt(0f, startAlpha);
1064eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        kf1 = Keyframe.ofInt(delayPoint, startAlpha);
1065eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        kf2 = Keyframe.ofInt(1f, endAlpha);
1066eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        PropertyValuesHolder fadeIn = PropertyValuesHolder.ofKeyframe("value", kf0, kf1, kf2);
1067eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1068eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(
1069eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                target, fadeIn).setDuration(totalDuration);
1070eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        animator.addUpdateListener(updateListener);
1071eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        return animator;
1072eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
1073eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1074eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private class InvalidateUpdateListener implements ValueAnimator.AnimatorUpdateListener {
1075eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        @Override
1076eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        public void onAnimationUpdate(ValueAnimator animation) {
1077eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            RadialTimePickerView.this.invalidate();
1078eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1079eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
1080eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1081eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void startHoursToMinutesAnimation() {
1082eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mHoursToMinutesAnims.size() == 0) {
1083eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mHoursToMinutesAnims.add(getRadiusDisappearAnimator(this,
1084eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    "animationRadiusMultiplierHours", mInvalidateUpdateListener,
1085eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier));
1086eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mHoursToMinutesAnims.add(getFadeOutAnimator(mAlpha[HOURS],
1087eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
1088eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_CIRCLE],
1089eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
1090eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_DOT],
1091eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
1092eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_LINE],
1093eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
1094eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1095eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mHoursToMinutesAnims.add(getRadiusReappearAnimator(this,
1096eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    "animationRadiusMultiplierMinutes", mInvalidateUpdateListener,
1097eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier));
1098eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mHoursToMinutesAnims.add(getFadeInAnimator(mAlpha[MINUTES],
1099eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
1100eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_CIRCLE],
1101eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener));
1102eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_DOT],
1103eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
1104eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_LINE],
1105eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener));
1106eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1107eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1108eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mTransition != null && mTransition.isRunning()) {
1109eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mTransition.end();
1110eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1111eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mTransition = new AnimatorSet();
1112eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mTransition.playTogether(mHoursToMinutesAnims);
1113eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mTransition.start();
1114eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
1115eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1116eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void startMinutesToHoursAnimation() {
1117eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mMinuteToHoursAnims.size() == 0) {
1118eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mMinuteToHoursAnims.add(getRadiusDisappearAnimator(this,
1119eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    "animationRadiusMultiplierMinutes", mInvalidateUpdateListener,
1120eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier));
1121eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mMinuteToHoursAnims.add(getFadeOutAnimator(mAlpha[MINUTES],
1122eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
1123eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_CIRCLE],
1124eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
1125eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_DOT],
1126eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
1127eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_LINE],
1128eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
1129eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1130eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mMinuteToHoursAnims.add(getRadiusReappearAnimator(this,
1131eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    "animationRadiusMultiplierHours", mInvalidateUpdateListener,
1132eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier));
1133eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mMinuteToHoursAnims.add(getFadeInAnimator(mAlpha[HOURS],
1134eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
1135eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_CIRCLE],
1136eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener));
1137eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_DOT],
1138eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
1139eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_LINE],
1140eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener));
1141eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1142eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1143eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mTransition != null && mTransition.isRunning()) {
1144eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mTransition.end();
1145eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1146eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mTransition = new AnimatorSet();
1147eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mTransition.playTogether(mMinuteToHoursAnims);
1148eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mTransition.start();
1149eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
1150eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1151eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private int getDegreesFromXY(float x, float y) {
1152eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final double hypotenuse = Math.sqrt(
1153eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                (y - mYCenter) * (y - mYCenter) + (x - mXCenter) * (x - mXCenter));
1154eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1155eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Basic check if we're outside the range of the disk
1156eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (hypotenuse > mCircleRadius[HOURS]) {
1157eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            return -1;
1158eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1159eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Check
1160eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mIs24HourMode && mShowHours) {
1161eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if (hypotenuse >= mMinHypotenuseForInnerNumber
1162eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    && hypotenuse <= mHalfwayHypotenusePoint) {
1163eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                mIsOnInnerCircle = true;
1164eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            } else if (hypotenuse <= mMaxHypotenuseForOuterNumber
1165eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    && hypotenuse >= mHalfwayHypotenusePoint) {
1166eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                mIsOnInnerCircle = false;
1167eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            } else {
1168eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                return -1;
1169eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
1170eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        } else {
1171eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            final int index =  (mShowHours) ? HOURS : MINUTES;
1172eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            final float length = (mCircleRadius[index] * mNumbersRadiusMultiplier[index]);
1173eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            final int distanceToNumber = (int) Math.abs(hypotenuse - length);
1174eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            final int maxAllowedDistance =
1175eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    (int) (mCircleRadius[index] * (1 - mNumbersRadiusMultiplier[index]));
1176eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if (distanceToNumber > maxAllowedDistance) {
1177eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                return -1;
1178eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
1179eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1180eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1181eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final float opposite = Math.abs(y - mYCenter);
1182ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        int degrees = (int) (Math.toDegrees(Math.asin(opposite / hypotenuse)) + 0.5);
1183eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1184eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Now we have to translate to the correct quadrant.
1185ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        final boolean rightSide = (x > mXCenter);
1186ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        final boolean topSide = (y < mYCenter);
1187ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (rightSide) {
1188ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (topSide) {
1189ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                degrees = 90 - degrees;
1190ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1191ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                degrees = 90 + degrees;
1192ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1193ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        } else {
1194ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (topSide) {
1195ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                degrees = 270 + degrees;
1196ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1197ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                degrees = 270 - degrees;
1198ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1199eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1200ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return degrees;
1201eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
1202eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1203002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette    boolean mChangedDuringTouch = false;
1204002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
1205eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    @Override
1206eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public boolean onTouch(View v, MotionEvent event) {
1207002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        if (!mInputEnabled) {
1208eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            return true;
1209eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1210eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1211002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        final int action = event.getActionMasked();
1212002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        if (action == MotionEvent.ACTION_MOVE
1213002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                || action == MotionEvent.ACTION_UP
1214002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                || action == MotionEvent.ACTION_DOWN) {
1215002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            boolean forceSelection = false;
1216002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            boolean autoAdvance = false;
1217002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
1218002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            if (action == MotionEvent.ACTION_DOWN) {
1219002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                // This is a new event stream, reset whether the value changed.
1220002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                mChangedDuringTouch = false;
1221002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            } else if (action == MotionEvent.ACTION_UP) {
1222002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                autoAdvance = true;
1223002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
1224002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                // If we saw a down/up pair without the value changing, assume
1225002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                // this is a single-tap selection and force a change.
1226002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                if (!mChangedDuringTouch) {
1227002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                    forceSelection = true;
1228eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                }
1229002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            }
1230eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1231002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            mChangedDuringTouch |= handleTouchInput(
1232002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                    event.getX(), event.getY(), forceSelection, autoAdvance);
1233eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1234002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
1235002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        return true;
1236002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette    }
1237002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
1238002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette    private boolean handleTouchInput(
1239002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            float x, float y, boolean forceSelection, boolean autoAdvance) {
1240002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        // Calling getDegreesFromXY has side effects, so cache
1241002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        // whether we used to be on the inner circle.
1242002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        final boolean wasOnInnerCircle = mIsOnInnerCircle;
1243002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        final int degrees = getDegreesFromXY(x, y);
1244002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        if (degrees == -1) {
1245002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            return false;
1246002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        }
1247002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
1248002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        final int[] selectionDegrees = mSelectionDegrees;
124911a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette        final int type;
125011a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette        final int newValue;
125111a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette        final boolean valueChanged;
1252002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
1253002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        if (mShowHours) {
1254002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            final int snapDegrees = snapOnly30s(degrees, 0) % 360;
125511a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            valueChanged = selectionDegrees[HOURS] != snapDegrees
1256002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                    || selectionDegrees[HOURS_INNER] != snapDegrees
125711a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette                    || wasOnInnerCircle != mIsOnInnerCircle;
1258002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
125911a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            selectionDegrees[HOURS] = snapDegrees;
126011a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            selectionDegrees[HOURS_INNER] = snapDegrees;
126111a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            type = HOURS;
126211a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            newValue = getCurrentHour();
1263002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        } else {
1264002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            final int snapDegrees = snapPrefer30s(degrees) % 360;
126511a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            valueChanged = selectionDegrees[MINUTES] != snapDegrees;
1266002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
126711a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            selectionDegrees[MINUTES] = snapDegrees;
126811a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            type = MINUTES;
126911a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            newValue = getCurrentMinute();
1270002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        }
1271002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
127211a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette        if (valueChanged || forceSelection || autoAdvance) {
127311a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            // Fire the listener even if we just need to auto-advance.
1274002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            if (mListener != null) {
1275002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                mListener.onValueSelected(type, newValue, autoAdvance);
1276002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            }
127711a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette
127811a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            // Only provide feedback if the value actually changed.
127911a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            if (valueChanged || forceSelection) {
128011a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette                performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
128111a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette                invalidate();
128211a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            }
1283002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            return true;
1284002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        }
1285002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
1286002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        return false;
1287eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
1288eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1289eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    @Override
1290ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    public boolean dispatchHoverEvent(MotionEvent event) {
1291ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        // First right-of-refusal goes the touch exploration helper.
1292ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (mTouchHelper.dispatchHoverEvent(event)) {
1293eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            return true;
1294eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1295ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return super.dispatchHoverEvent(event);
1296eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
1297eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1298ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    public void setInputEnabled(boolean inputEnabled) {
1299ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        mInputEnabled = inputEnabled;
1300ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        invalidate();
1301ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
1302ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1303ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private class RadialPickerTouchHelper extends ExploreByTouchHelper {
1304ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final Rect mTempRect = new Rect();
1305ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1306ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int TYPE_HOUR = 1;
1307ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int TYPE_MINUTE = 2;
1308ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1309ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int SHIFT_TYPE = 0;
1310ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int MASK_TYPE = 0xF;
1311ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1312ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int SHIFT_VALUE = 8;
1313ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int MASK_VALUE = 0xFF;
1314ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1315ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        /** Increment in which virtual views are exposed for minutes. */
1316ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int MINUTE_INCREMENT = 5;
1317ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1318ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        public RadialPickerTouchHelper() {
1319ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            super(RadialTimePickerView.this);
1320eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1321eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1322ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1323ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
1324ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            super.onInitializeAccessibilityNodeInfo(host, info);
1325ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1326ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
1327ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
1328eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1329ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1330ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1331ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
1332ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (super.performAccessibilityAction(host, action, arguments)) {
1333ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                return true;
1334eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
1335eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1336ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            switch (action) {
1337ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
1338ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    adjustPicker(1);
1339ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    return true;
1340ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
1341ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    adjustPicker(-1);
1342ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    return true;
1343ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1344ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1345ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return false;
1346ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1347ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1348ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private void adjustPicker(int step) {
1349ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int stepSize;
1350ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int initialValue;
135167945c11a5e9547f71be91ceb99e7b9ff15a6292Alan Viverette            final int maxValue;
1352ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int minValue;
1353eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if (mShowHours) {
1354ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                stepSize = DEGREES_FOR_ONE_HOUR;
1355ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                initialValue = getCurrentHour() % 12;
1356ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1357eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                if (mIs24HourMode) {
1358eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    maxValue = 23;
1359ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    minValue = 0;
1360eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                } else {
1361eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    maxValue = 12;
1362eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    minValue = 1;
1363eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                }
1364eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            } else {
1365ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                stepSize = DEGREES_FOR_ONE_MINUTE;
1366ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                initialValue = getCurrentMinute();
1367ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1368eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                maxValue = 55;
1369ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                minValue = 0;
1370eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
1371ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1372ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int steppedValue = snapOnly30s(initialValue * stepSize, step) / stepSize;
1373ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int clampedValue = MathUtils.constrain(steppedValue, minValue, maxValue);
1374ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (mShowHours) {
1375ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                setCurrentHour(clampedValue);
1376ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1377ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                setCurrentMinute(clampedValue);
1378ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1379ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1380ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1381ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1382ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected int getVirtualViewAt(float x, float y) {
1383ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int id;
1384ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int degrees = getDegreesFromXY(x, y);
1385ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (degrees != -1) {
1386ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int snapDegrees = snapOnly30s(degrees, 0) % 360;
1387ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                if (mShowHours) {
1388ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    final int hour = getHourForDegrees(snapDegrees, mIsOnInnerCircle);
1389ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    id = makeId(TYPE_HOUR, hour);
1390ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                } else {
1391ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    final int current = getCurrentMinute();
1392ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    final int touched = getMinuteForDegrees(degrees);
1393ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    final int snapped = getMinuteForDegrees(snapDegrees);
1394ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1395ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    // If the touched minute is closer to the current minute
1396ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    // than it is to the snapped minute, return current.
1397ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    final int minute;
1398ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    if (Math.abs(current - touched) < Math.abs(snapped - touched)) {
1399ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                        minute = current;
1400ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    } else {
1401ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                        minute = snapped;
1402ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    }
1403ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    id = makeId(TYPE_MINUTE, minute);
1404ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                }
1405ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1406ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                id = INVALID_ID;
1407eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
1408ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1409ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return id;
1410ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1411ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1412ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1413ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected void getVisibleVirtualViews(IntArray virtualViewIds) {
1414eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if (mShowHours) {
1415ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int min = mIs24HourMode ? 0 : 1;
1416ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int max = mIs24HourMode ? 23 : 12;
1417ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                for (int i = min; i <= max ; i++) {
1418ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    virtualViewIds.add(makeId(TYPE_HOUR, i));
1419eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                }
1420eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            } else {
1421ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int current = getCurrentMinute();
1422ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                for (int i = 0; i < 60; i += MINUTE_INCREMENT) {
1423ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    virtualViewIds.add(makeId(TYPE_MINUTE, i));
1424ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1425ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    // If the current minute falls between two increments,
1426ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    // insert an extra node for it.
1427ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    if (current > i && current < i + MINUTE_INCREMENT) {
1428ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                        virtualViewIds.add(makeId(TYPE_MINUTE, current));
1429ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    }
1430eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                }
1431eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
1432eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1433eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1434ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1435ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
1436ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            event.setClassName(getClass().getName());
1437eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1438ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int type = getTypeFromId(virtualViewId);
1439ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int value = getValueFromId(virtualViewId);
1440ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final CharSequence description = getVirtualViewDescription(type, value);
1441ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            event.setContentDescription(description);
1442ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1443ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1444ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1445ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) {
1446ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            node.setClassName(getClass().getName());
1447ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            node.addAction(AccessibilityAction.ACTION_CLICK);
1448ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1449ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int type = getTypeFromId(virtualViewId);
1450ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int value = getValueFromId(virtualViewId);
1451ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final CharSequence description = getVirtualViewDescription(type, value);
1452ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            node.setContentDescription(description);
1453ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1454ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            getBoundsForVirtualView(virtualViewId, mTempRect);
1455ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            node.setBoundsInParent(mTempRect);
1456ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1457ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final boolean selected = isVirtualViewSelected(type, value);
1458ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            node.setSelected(selected);
1459ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1460ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1461ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1462ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
1463ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                Bundle arguments) {
1464ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (action == AccessibilityNodeInfo.ACTION_CLICK) {
1465ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int type = getTypeFromId(virtualViewId);
1466ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int value = getValueFromId(virtualViewId);
1467ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                if (type == TYPE_HOUR) {
1468ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    final int hour = mIs24HourMode ? value : hour12To24(value, mAmOrPm);
1469ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    setCurrentHour(hour);
1470ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    return true;
1471ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                } else if (type == TYPE_MINUTE) {
1472ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    setCurrentMinute(value);
1473ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    return true;
1474ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                }
1475ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1476ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return false;
1477ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1478ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1479ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private int hour12To24(int hour12, int amOrPm) {
1480ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            int hour24 = hour12;
1481ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (hour12 == 12) {
1482ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                if (amOrPm == AM) {
1483ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    hour24 = 0;
1484ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                }
1485ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else if (amOrPm == PM) {
1486ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                hour24 += 12;
1487ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1488ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return hour24;
1489ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1490ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1491ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private void getBoundsForVirtualView(int virtualViewId, Rect bounds) {
1492ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final float radius;
1493ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int type = getTypeFromId(virtualViewId);
1494ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int value = getValueFromId(virtualViewId);
1495ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final float centerRadius;
1496ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final float degrees;
1497ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (type == TYPE_HOUR) {
1498ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final boolean innerCircle = mIs24HourMode && value > 0 && value <= 12;
1499ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                if (innerCircle) {
1500ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    centerRadius = mCircleRadius[HOURS_INNER] * mNumbersRadiusMultiplier[HOURS_INNER];
1501ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    radius = mSelectionRadius[HOURS_INNER];
1502ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                } else {
1503ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    centerRadius = mCircleRadius[HOURS] * mNumbersRadiusMultiplier[HOURS];
1504ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    radius = mSelectionRadius[HOURS];
1505ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                }
1506ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1507ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                degrees = getDegreesForHour(value);
1508ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else if (type == TYPE_MINUTE) {
1509ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                centerRadius = mCircleRadius[MINUTES] * mNumbersRadiusMultiplier[MINUTES];
1510ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                degrees = getDegreesForMinute(value);
1511ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                radius = mSelectionRadius[MINUTES];
1512ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1513ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                // This should never happen.
1514ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                centerRadius = 0;
1515ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                degrees = 0;
1516ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                radius = 0;
1517ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1518ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1519ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final double radians = Math.toRadians(degrees);
1520ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final float xCenter = mXCenter + centerRadius * (float) Math.sin(radians);
1521ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final float yCenter = mYCenter - centerRadius * (float) Math.cos(radians);
1522ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1523ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            bounds.set((int) (xCenter - radius), (int) (yCenter - radius),
1524ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    (int) (xCenter + radius), (int) (yCenter + radius));
1525ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1526ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1527ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private CharSequence getVirtualViewDescription(int type, int value) {
1528ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final CharSequence description;
1529ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (type == TYPE_HOUR || type == TYPE_MINUTE) {
1530ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                description = Integer.toString(value);
1531ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1532ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                description = null;
1533ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1534ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return description;
1535ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1536ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1537ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private boolean isVirtualViewSelected(int type, int value) {
1538ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final boolean selected;
1539ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (type == TYPE_HOUR) {
1540ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                selected = getCurrentHour() == value;
1541ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else if (type == TYPE_MINUTE) {
1542ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                selected = getCurrentMinute() == value;
1543ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1544ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                selected = false;
1545ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1546ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return selected;
1547ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1548ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1549ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private int makeId(int type, int value) {
1550ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return type << SHIFT_TYPE | value << SHIFT_VALUE;
1551ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1552ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1553ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private int getTypeFromId(int id) {
1554ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return id >>> SHIFT_TYPE & MASK_TYPE;
1555ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1556ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1557ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private int getValueFromId(int id) {
1558ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return id >>> SHIFT_VALUE & MASK_VALUE;
1559ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1560eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
1561eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
1562eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette    private static class IntHolder {
1563eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        private int mValue;
1564eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
1565eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        public IntHolder(int value) {
1566eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mValue = value;
1567eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        }
1568eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
1569eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        public void setValue(int value) {
1570eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mValue = value;
1571eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        }
1572eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
1573eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        public int getValue() {
1574eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            return mValue;
1575eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        }
1576eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette    }
1577eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio}
1578