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.ObjectAnimator;
20eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.content.Context;
21ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.content.res.ColorStateList;
22eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.content.res.Resources;
23eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.content.res.TypedArray;
24eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Canvas;
25eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Color;
26eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Paint;
27ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.graphics.Path;
28ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.graphics.Rect;
29ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.graphics.Region;
30eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Typeface;
31eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.os.Bundle;
32eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.util.AttributeSet;
332b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viveretteimport android.util.FloatProperty;
34ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.util.IntArray;
35eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.util.Log;
36ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.util.MathUtils;
37ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.util.StateSet;
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.accessibility.AccessibilityEvent;
43eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.accessibility.AccessibilityNodeInfo;
44ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
45eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
46eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport com.android.internal.R;
47ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport com.android.internal.widget.ExploreByTouchHelper;
48eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
49eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport java.util.Calendar;
50eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport java.util.Locale;
51eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
52eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio/**
53eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * View to show a clock circle picker (with one or two picking circles)
54eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio *
55eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * @hide
56eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */
57ec9fe1ad663d59829075029508673b2e913538d3Alan Viverettepublic class RadialTimePickerView extends View {
582b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
59ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private static final String TAG = "RadialTimePickerView";
60eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
61b0f5461eeca28d6c4be6ba1f826a43a8961f587eAlan Viverette    public static final int HOURS = 0;
62b0f5461eeca28d6c4be6ba1f826a43a8961f587eAlan Viverette    public static final int MINUTES = 1;
63eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int HOURS_INNER = 2;
64eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
65eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int SELECTOR_CIRCLE = 0;
66eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int SELECTOR_DOT = 1;
67eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int SELECTOR_LINE = 2;
68eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
69eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int AM = 0;
70eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int PM = 1;
71eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
72fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette    private static final int HOURS_IN_CIRCLE = 12;
73fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette    private static final int MINUTES_IN_CIRCLE = 60;
74fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette    private static final int DEGREES_FOR_ONE_HOUR = 360 / HOURS_IN_CIRCLE;
75fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette    private static final int DEGREES_FOR_ONE_MINUTE = 360 / MINUTES_IN_CIRCLE;
76eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
77eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int[] HOURS_NUMBERS = {12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
78eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int[] HOURS_NUMBERS_24 = {0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23};
79eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int[] MINUTES_NUMBERS = {0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55};
80eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
812b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private static final int ANIM_DURATION_NORMAL = 500;
822b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private static final int ANIM_DURATION_TOUCH = 60;
83ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
84ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private static final int[] SNAP_PREFER_30S_MAP = new int[361];
85ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
86ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private static final int NUM_POSITIONS = 12;
87ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private static final float[] COS_30 = new float[NUM_POSITIONS];
88ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private static final float[] SIN_30 = new float[NUM_POSITIONS];
89ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
902b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    /** "Something is wrong" color used when a color attribute is missing. */
912b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private static final int MISSING_COLOR = Color.MAGENTA;
922b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
93ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    static {
94ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        // Prepare mapping to snap touchable degrees to selectable degrees.
95ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        preparePrefer30sMap();
96ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
97ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final double increment = 2.0 * Math.PI / NUM_POSITIONS;
98ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        double angle = Math.PI / 2.0;
99ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        for (int i = 0; i < NUM_POSITIONS; i++) {
100ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            COS_30[i] = (float) Math.cos(angle);
101ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            SIN_30[i] = (float) Math.sin(angle);
102ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            angle += increment;
103ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        }
104ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    }
105eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1062b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private final FloatProperty<RadialTimePickerView> HOURS_TO_MINUTES =
1072b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            new FloatProperty<RadialTimePickerView>("hoursToMinutes") {
1082b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                @Override
1092b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                public Float get(RadialTimePickerView radialTimePickerView) {
1102b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                    return radialTimePickerView.mHoursToMinutes;
1112b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                }
1122b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
1132b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                @Override
1142b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                public void setValue(RadialTimePickerView object, float value) {
1152b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                    object.mHoursToMinutes = value;
1162b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                    object.invalidate();
1172b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                }
1182b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            };
119ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
120eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final String[] mHours12Texts = new String[12];
121eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final String[] mOuterHours24Texts = new String[12];
122eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final String[] mInnerHours24Texts = new String[12];
123eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final String[] mMinutesTexts = new String[12];
124eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
125eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final Paint[] mPaint = new Paint[2];
126eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final Paint mPaintCenter = new Paint();
1272b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private final Paint[] mPaintSelector = new Paint[3];
128eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final Paint mPaintBackground = new Paint();
129eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
130ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final Typeface mTypeface;
131ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
132adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private final ColorStateList[] mTextColor = new ColorStateList[3];
133adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private final int[] mTextSize = new int[3];
134adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private final int[] mTextInset = new int[3];
135ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
136ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private final float[][] mOuterTextX = new float[2][12];
137ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private final float[][] mOuterTextY = new float[2][12];
138ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
139ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private final float[] mInnerTextX = new float[12];
140ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private final float[] mInnerTextY = new float[12];
141ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
14288e51032320ab92057666e1230cc6548ca163c51Alan Viverette    private final int[] mSelectionDegrees = new int[2];
143ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
144ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final RadialPickerTouchHelper mTouchHelper;
145ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
146f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette    private final Path mSelectorPath = new Path();
147f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette
148eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private boolean mIs24HourMode;
149eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private boolean mShowHours;
150d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette
1512b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private ObjectAnimator mHoursToMinutesAnimator;
1522b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private float mHoursToMinutes;
1532b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
154d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette    /**
155d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * When in 24-hour mode, indicates that the current hour is between
156d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * 1 and 12 (inclusive).
157d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     */
158eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private boolean mIsOnInnerCircle;
159eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
160adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private int mSelectorRadius;
16162c79e9a64c3b2cafd5500ed3064977dff7b7da3Alan Viverette    private int mSelectorStroke;
162adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private int mSelectorDotRadius;
163adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private int mCenterDotRadius;
164adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette
1652b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private int mSelectorColor;
1662b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private int mSelectorDotColor;
1672b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
168adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private int mXCenter;
169adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private int mYCenter;
170adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private int mCircleRadius;
171eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
17288e51032320ab92057666e1230cc6548ca163c51Alan Viverette    private int mMinDistForInnerNumber;
17388e51032320ab92057666e1230cc6548ca163c51Alan Viverette    private int mMaxDistForOuterNumber;
17488e51032320ab92057666e1230cc6548ca163c51Alan Viverette    private int mHalfwayDist;
175eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
176eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private String[] mOuterTextHours;
177eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private String[] mInnerTextHours;
17888e51032320ab92057666e1230cc6548ca163c51Alan Viverette    private String[] mMinutesText;
179eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
180eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private int mAmOrPm;
181adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette
182adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private float mDisabledAlpha;
1835134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette
184eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private OnValueSelectedListener mListener;
185eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
186ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private boolean mInputEnabled = true;
187eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
188eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public interface OnValueSelectedListener {
189eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance);
190eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
191eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
192eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    /**
193eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * Split up the 360 degrees of the circle among the 60 selectable values. Assigns a larger
194eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * selectable area to each of the 12 visible values, such that the ratio of space apportioned
195eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * to a visible value : space apportioned to a non-visible value will be 14 : 4.
196eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * E.g. the output of 30 degrees should have a higher range of input associated with it than
197eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * the output of 24 degrees, because 30 degrees corresponds to a visible number on the clock
198eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * circle (5 on the minutes, 1 or 13 on the hours).
199eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     */
200eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static void preparePrefer30sMap() {
201eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // We'll split up the visible output and the non-visible output such that each visible
202eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // output will correspond to a range of 14 associated input degrees, and each non-visible
203eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // output will correspond to a range of 4 associate input degrees, so visible numbers
204eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // are more than 3 times easier to get than non-visible numbers:
205eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // {354-359,0-7}:0, {8-11}:6, {12-15}:12, {16-19}:18, {20-23}:24, {24-37}:30, etc.
206eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        //
207eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // If an output of 30 degrees should correspond to a range of 14 associated degrees, then
208eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // we'll need any input between 24 - 37 to snap to 30. Working out from there, 20-23 should
209eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // snap to 24, while 38-41 should snap to 36. This is somewhat counter-intuitive, that you
210eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // can be touching 36 degrees but have the selection snapped to 30 degrees; however, this
211eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // inconsistency isn't noticeable at such fine-grained degrees, and it affords us the
212eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // ability to aggressively prefer the visible values by a factor of more than 3:1, which
213eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // greatly contributes to the selectability of these values.
214eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
215eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // The first output is 0, and each following output will increment by 6 {0, 6, 12, ...}.
216eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int snappedOutputDegrees = 0;
217eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Count of how many inputs we've designated to the specified output.
218eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int count = 1;
219eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // How many input we expect for a specified output. This will be 14 for output divisible
220eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // by 30, and 4 for the remaining output. We'll special case the outputs of 0 and 360, so
221eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // the caller can decide which they need.
222eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int expectedCount = 8;
223eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Iterate through the input.
224eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        for (int degrees = 0; degrees < 361; degrees++) {
225eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            // Save the input-output mapping.
226ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            SNAP_PREFER_30S_MAP[degrees] = snappedOutputDegrees;
227eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            // If this is the last input for the specified output, calculate the next output and
228eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            // the next expected count.
229eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if (count == expectedCount) {
230eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                snappedOutputDegrees += 6;
231eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                if (snappedOutputDegrees == 360) {
232eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    expectedCount = 7;
233eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                } else if (snappedOutputDegrees % 30 == 0) {
234eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    expectedCount = 14;
235eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                } else {
236eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    expectedCount = 4;
237eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                }
238eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                count = 1;
239eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            } else {
240eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                count++;
241eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
242eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
243eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
244eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
245eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    /**
246eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * Returns mapping of any input degrees (0 to 360) to one of 60 selectable output degrees,
247eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * where the degrees corresponding to visible numbers (i.e. those divisible by 30) will be
248eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * weighted heavier than the degrees corresponding to non-visible numbers.
249eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * See {@link #preparePrefer30sMap()} documentation for the rationale and generation of the
250eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * mapping.
251eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     */
252eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static int snapPrefer30s(int degrees) {
253ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        if (SNAP_PREFER_30S_MAP == null) {
254eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            return -1;
255eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
256ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        return SNAP_PREFER_30S_MAP[degrees];
257eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
258eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
259eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    /**
260eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * Returns mapping of any input degrees (0 to 360) to one of 12 visible output degrees (all
261eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * multiples of 30), where the input will be "snapped" to the closest visible degrees.
262eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * @param degrees The input degrees
263eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * @param forceHigherOrLower The output may be forced to either the higher or lower step, or may
264eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * be allowed to snap to whichever is closer. Use 1 to force strictly higher, -1 to force
265eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * strictly lower, and 0 to snap to the closer one.
266eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * @return output degrees, will be a multiple of 30
267eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     */
268eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static int snapOnly30s(int degrees, int forceHigherOrLower) {
269eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int stepSize = DEGREES_FOR_ONE_HOUR;
270eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int floor = (degrees / stepSize) * stepSize;
271eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int ceiling = floor + stepSize;
272eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (forceHigherOrLower == 1) {
273eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            degrees = ceiling;
274eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        } else if (forceHigherOrLower == -1) {
275eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if (degrees == floor) {
276eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                floor -= stepSize;
277eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
278eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            degrees = floor;
279eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        } else {
280eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if ((degrees - floor) < (ceiling - degrees)) {
281eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                degrees = floor;
282eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            } else {
283eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                degrees = ceiling;
284eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
285eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
286eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        return degrees;
287eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
288eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
289ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    @SuppressWarnings("unused")
290ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    public RadialTimePickerView(Context context)  {
291ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        this(context, null);
292ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
293ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
294eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public RadialTimePickerView(Context context, AttributeSet attrs)  {
295eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        this(context, attrs, R.attr.timePickerStyle);
296eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
297eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
298ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    public RadialTimePickerView(Context context, AttributeSet attrs, int defStyleAttr)  {
299ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        this(context, attrs, defStyleAttr, 0);
300ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
301ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
302ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    public RadialTimePickerView(
303ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)  {
304eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        super(context, attrs);
305eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
3062b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        applyAttributes(attrs, defStyleAttr, defStyleRes);
3072b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
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);
311adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mDisabledAlpha = outValue.getFloat();
3125134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette
313eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mTypeface = Typeface.create("sans-serif", Typeface.NORMAL);
314eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
315eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[HOURS] = new Paint();
316eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[HOURS].setAntiAlias(true);
317eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[HOURS].setTextAlign(Paint.Align.CENTER);
318eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
319eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[MINUTES] = new Paint();
320eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[MINUTES].setAntiAlias(true);
321eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[MINUTES].setTextAlign(Paint.Align.CENTER);
322eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
323eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintCenter.setAntiAlias(true);
324ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
3252b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintSelector[SELECTOR_CIRCLE] = new Paint();
3262b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintSelector[SELECTOR_CIRCLE].setAntiAlias(true);
327eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
3282b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintSelector[SELECTOR_DOT] = new Paint();
3292b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintSelector[SELECTOR_DOT].setAntiAlias(true);
330eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
3312b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintSelector[SELECTOR_LINE] = new Paint();
3322b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintSelector[SELECTOR_LINE].setAntiAlias(true);
3332b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintSelector[SELECTOR_LINE].setStrokeWidth(2);
334eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
335eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintBackground.setAntiAlias(true);
336eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
3372b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final Resources res = getResources();
338adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mSelectorRadius = res.getDimensionPixelSize(R.dimen.timepicker_selector_radius);
33962c79e9a64c3b2cafd5500ed3064977dff7b7da3Alan Viverette        mSelectorStroke = res.getDimensionPixelSize(R.dimen.timepicker_selector_stroke);
340adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mSelectorDotRadius = res.getDimensionPixelSize(R.dimen.timepicker_selector_dot_radius);
341adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mCenterDotRadius = res.getDimensionPixelSize(R.dimen.timepicker_center_dot_radius);
342adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette
343adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mTextSize[HOURS] = res.getDimensionPixelSize(R.dimen.timepicker_text_size_normal);
344adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mTextSize[MINUTES] = res.getDimensionPixelSize(R.dimen.timepicker_text_size_normal);
345adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mTextSize[HOURS_INNER] = res.getDimensionPixelSize(R.dimen.timepicker_text_size_inner);
346adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette
347adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mTextInset[HOURS] = res.getDimensionPixelSize(R.dimen.timepicker_text_inset_normal);
348adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mTextInset[MINUTES] = res.getDimensionPixelSize(R.dimen.timepicker_text_inset_normal);
349adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mTextInset[HOURS_INNER] = res.getDimensionPixelSize(R.dimen.timepicker_text_inset_inner);
350eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
351eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mShowHours = true;
3522b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mHoursToMinutes = HOURS;
353eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mIs24HourMode = false;
354eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mAmOrPm = AM;
355eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
356ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        // Set up accessibility components.
357ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        mTouchHelper = new RadialPickerTouchHelper();
358ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setAccessibilityDelegate(mTouchHelper);
359ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
360ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
361ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
362ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
363ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
364eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        initHoursAndMinutesText();
365eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        initData();
366eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
367eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Initial values
368eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final Calendar calendar = Calendar.getInstance(Locale.getDefault());
369eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int currentHour = calendar.get(Calendar.HOUR_OF_DAY);
370eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int currentMinute = calendar.get(Calendar.MINUTE);
371eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
372ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentHourInternal(currentHour, false, false);
373ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentMinuteInternal(currentMinute, false);
374eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
375eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        setHapticFeedbackEnabled(true);
376eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
377eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
3782b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    void applyAttributes(AttributeSet attrs, int defStyleAttr, int defStyleRes) {
3792b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final Context context = getContext();
3802b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final TypedArray a = getContext().obtainStyledAttributes(attrs,
3812b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                R.styleable.TimePicker, defStyleAttr, defStyleRes);
3822b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
3832b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final ColorStateList numbersTextColor = a.getColorStateList(
3842b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                R.styleable.TimePicker_numbersTextColor);
3852b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final ColorStateList numbersInnerTextColor = a.getColorStateList(
3862b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                R.styleable.TimePicker_numbersInnerTextColor);
3872b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mTextColor[HOURS] = numbersTextColor == null ?
3882b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                ColorStateList.valueOf(MISSING_COLOR) : numbersTextColor;
3892b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mTextColor[HOURS_INNER] = numbersInnerTextColor == null ?
3902b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                ColorStateList.valueOf(MISSING_COLOR) : numbersInnerTextColor;
3912b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mTextColor[MINUTES] = mTextColor[HOURS];
3922b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
3932b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        // Set up various colors derived from the selector "activated" state.
3942b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final ColorStateList selectorColors = a.getColorStateList(
3952b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                R.styleable.TimePicker_numbersSelectorColor);
3962b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int selectorActivatedColor;
3972b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        if (selectorColors != null) {
3982b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            final int[] stateSetEnabledActivated = StateSet.get(
3992b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                    StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED);
4002b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            selectorActivatedColor = selectorColors.getColorForState(
4012b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                    stateSetEnabledActivated, 0);
4022b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        }  else {
4032b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            selectorActivatedColor = MISSING_COLOR;
4042b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        }
4052b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
4062b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintCenter.setColor(selectorActivatedColor);
4072b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
4082b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int[] stateSetActivated = StateSet.get(
4092b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED);
4102b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
4112b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mSelectorColor = selectorActivatedColor;
4122b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mSelectorDotColor = mTextColor[HOURS].getColorForState(stateSetActivated, 0);
4132b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
4142b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintBackground.setColor(a.getColor(R.styleable.TimePicker_numbersBackgroundColor,
4152b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                context.getColor(R.color.timepicker_default_numbers_background_color_material)));
4162b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
4172b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        a.recycle();
4182b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    }
4192b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
420eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void initialize(int hour, int minute, boolean is24HourMode) {
421448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette        if (mIs24HourMode != is24HourMode) {
422448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette            mIs24HourMode = is24HourMode;
423448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette            initData();
424448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette        }
425ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
426ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentHourInternal(hour, false, false);
427ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentMinuteInternal(minute, false);
428eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
429eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
430eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void setCurrentItemShowing(int item, boolean animate) {
431eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        switch (item){
432eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            case HOURS:
433eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                showHours(animate);
434eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                break;
435eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            case MINUTES:
436eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                showMinutes(animate);
437eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                break;
438eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            default:
439eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                Log.e(TAG, "ClockView does not support showing item " + item);
440eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
441eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
442eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
443eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public int getCurrentItemShowing() {
444eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        return mShowHours ? HOURS : MINUTES;
445eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
446eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
447eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void setOnValueSelectedListener(OnValueSelectedListener listener) {
448eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mListener = listener;
449eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
450eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
451d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette    /**
452d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * Sets the current hour in 24-hour time.
453d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     *
454d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * @param hour the current hour between 0 and 23 (inclusive)
455d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     */
456eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void setCurrentHour(int hour) {
457ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentHourInternal(hour, true, false);
458ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
459ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
460ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    /**
461ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     * Sets the current hour.
462ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     *
463ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     * @param hour The current hour
464ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     * @param callback Whether the value listener should be invoked
465ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     * @param autoAdvance Whether the listener should auto-advance to the next
466ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     *                    selection mode, e.g. hour to minutes
467ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     */
468ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private void setCurrentHourInternal(int hour, boolean callback, boolean autoAdvance) {
469eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int degrees = (hour % 12) * DEGREES_FOR_ONE_HOUR;
470eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mSelectionDegrees[HOURS] = degrees;
471d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette
472d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette        // 0 is 12 AM (midnight) and 12 is 12 PM (noon).
473ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        final int amOrPm = (hour == 0 || (hour % 24) < 12) ? AM : PM;
47488e51032320ab92057666e1230cc6548ca163c51Alan Viverette        final boolean isOnInnerCircle = getInnerCircleForHour(hour);
475ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (mAmOrPm != amOrPm || mIsOnInnerCircle != isOnInnerCircle) {
476ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            mAmOrPm = amOrPm;
477ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            mIsOnInnerCircle = isOnInnerCircle;
478ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
479ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            initData();
480ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            mTouchHelper.invalidateRoot();
481ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
482d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette
483eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        invalidate();
484ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
485ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (callback && mListener != null) {
486ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            mListener.onValueSelected(HOURS, hour, autoAdvance);
487ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
488eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
489eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
490d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette    /**
491d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * Returns the current hour in 24-hour time.
492d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     *
493d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * @return the current hour between 0 and 23 (inclusive)
494d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     */
495eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public int getCurrentHour() {
49688e51032320ab92057666e1230cc6548ca163c51Alan Viverette        return getHourForDegrees(mSelectionDegrees[HOURS], mIsOnInnerCircle);
497ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
498ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
499ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private int getHourForDegrees(int degrees, boolean innerCircle) {
500ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        int hour = (degrees / DEGREES_FOR_ONE_HOUR) % 12;
501eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mIs24HourMode) {
502d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette            // Convert the 12-hour value into 24-hour time based on where the
503d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette            // selector is positioned.
50488e51032320ab92057666e1230cc6548ca163c51Alan Viverette            if (!innerCircle && hour == 0) {
50588e51032320ab92057666e1230cc6548ca163c51Alan Viverette                // Outer circle is 1 through 12.
506d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette                hour = 12;
50788e51032320ab92057666e1230cc6548ca163c51Alan Viverette            } else if (innerCircle && hour != 0) {
50888e51032320ab92057666e1230cc6548ca163c51Alan Viverette                // Inner circle is 13 through 23 and 0.
509d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette                hour += 12;
510eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
511d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette        } else if (mAmOrPm == PM) {
512d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette            hour += 12;
513eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
514d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette        return hour;
515eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
516eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
51788e51032320ab92057666e1230cc6548ca163c51Alan Viverette    /**
51888e51032320ab92057666e1230cc6548ca163c51Alan Viverette     * @param hour the hour in 24-hour time or 12-hour time
51988e51032320ab92057666e1230cc6548ca163c51Alan Viverette     */
520ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private int getDegreesForHour(int hour) {
521ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        // Convert to be 0-11.
522ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (mIs24HourMode) {
523ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (hour >= 12) {
524ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                hour -= 12;
525ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
526ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        } else if (hour == 12) {
527ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            hour = 0;
528ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
529ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return hour * DEGREES_FOR_ONE_HOUR;
530ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
531ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
53288e51032320ab92057666e1230cc6548ca163c51Alan Viverette    /**
53388e51032320ab92057666e1230cc6548ca163c51Alan Viverette     * @param hour the hour in 24-hour time or 12-hour time
53488e51032320ab92057666e1230cc6548ca163c51Alan Viverette     */
53588e51032320ab92057666e1230cc6548ca163c51Alan Viverette    private boolean getInnerCircleForHour(int hour) {
53688e51032320ab92057666e1230cc6548ca163c51Alan Viverette        return mIs24HourMode && (hour == 0 || hour > 12);
53788e51032320ab92057666e1230cc6548ca163c51Alan Viverette    }
53888e51032320ab92057666e1230cc6548ca163c51Alan Viverette
539eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void setCurrentMinute(int minute) {
540ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentMinuteInternal(minute, true);
541ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
542ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
543ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private void setCurrentMinuteInternal(int minute, boolean callback) {
544fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette        mSelectionDegrees[MINUTES] = (minute % MINUTES_IN_CIRCLE) * DEGREES_FOR_ONE_MINUTE;
545ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
546eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        invalidate();
547ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
548ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (callback && mListener != null) {
549ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            mListener.onValueSelected(MINUTES, minute, false);
550ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
551eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
552eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
553eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    // Returns minutes in 0-59 range
554eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public int getCurrentMinute() {
555ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return getMinuteForDegrees(mSelectionDegrees[MINUTES]);
556ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
557ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
558ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private int getMinuteForDegrees(int degrees) {
559ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return degrees / DEGREES_FOR_ONE_MINUTE;
560ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
561ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
562ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private int getDegreesForMinute(int minute) {
563ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return minute * DEGREES_FOR_ONE_MINUTE;
564eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
565eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
56630b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette    /**
56730b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette     * Sets whether the picker is showing AM or PM hours. Has no effect when
56830b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette     * in 24-hour mode.
56930b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette     *
57030b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette     * @param amOrPm {@link #AM} or {@link #PM}
57130b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette     * @return {@code true} if the value changed from what was previously set,
57230b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette     *         or {@code false} otherwise
57330b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette     */
57430b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette    public boolean setAmOrPm(int amOrPm) {
57530b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette        if (mAmOrPm == amOrPm || mIs24HourMode) {
57630b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette            return false;
57730b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette        }
57830b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette
57930b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette        mAmOrPm = amOrPm;
580eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        invalidate();
581ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        mTouchHelper.invalidateRoot();
58230b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette        return true;
583eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
584eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
585eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public int getAmOrPm() {
586eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        return mAmOrPm;
587eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
588eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
589eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void showHours(boolean animate) {
5902b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        showPicker(true, animate);
591eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
592eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
593eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void showMinutes(boolean animate) {
5942b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        showPicker(false, animate);
595eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
596eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
597eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void initHoursAndMinutesText() {
598eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Initialize the hours and minutes numbers.
599eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        for (int i = 0; i < 12; i++) {
600eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mHours12Texts[i] = String.format("%d", HOURS_NUMBERS[i]);
601f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette            mInnerHours24Texts[i] = String.format("%02d", HOURS_NUMBERS_24[i]);
602f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette            mOuterHours24Texts[i] = String.format("%d", HOURS_NUMBERS[i]);
603eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mMinutesTexts[i] = String.format("%02d", MINUTES_NUMBERS[i]);
604eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
605eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
606eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
607eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void initData() {
608eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mIs24HourMode) {
609eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mOuterTextHours = mOuterHours24Texts;
610eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mInnerTextHours = mInnerHours24Texts;
611eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        } else {
612eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mOuterTextHours = mHours12Texts;
613f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette            mInnerTextHours = mHours12Texts;
614eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
615eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
61688e51032320ab92057666e1230cc6548ca163c51Alan Viverette        mMinutesText = mMinutesTexts;
617eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
618eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
619eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    @Override
620eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
621ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        if (!changed) {
622ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            return;
623ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        }
624eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
625eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mXCenter = getWidth() / 2;
626eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mYCenter = getHeight() / 2;
627ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mCircleRadius = Math.min(mXCenter, mYCenter);
628eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
62988e51032320ab92057666e1230cc6548ca163c51Alan Viverette        mMinDistForInnerNumber = mCircleRadius - mTextInset[HOURS_INNER] - mSelectorRadius;
63088e51032320ab92057666e1230cc6548ca163c51Alan Viverette        mMaxDistForOuterNumber = mCircleRadius - mTextInset[HOURS] + mSelectorRadius;
63188e51032320ab92057666e1230cc6548ca163c51Alan Viverette        mHalfwayDist = mCircleRadius - (mTextInset[HOURS] + mTextInset[HOURS_INNER]) / 2;
632eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
633ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        calculatePositionsHours();
634ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        calculatePositionsMinutes();
635eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
636ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        mTouchHelper.invalidateRoot();
637eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
638eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
639eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    @Override
640eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void onDraw(Canvas canvas) {
641adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        final float alphaMod = mInputEnabled ? 1 : mDisabledAlpha;
642eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
643eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        drawCircleBackground(canvas);
6442b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
6452b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final Path selectorPath = mSelectorPath;
6462b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        drawSelector(canvas, selectorPath);
6472b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        drawHours(canvas, selectorPath, alphaMod);
6482b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        drawMinutes(canvas, selectorPath, alphaMod);
649adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        drawCenter(canvas, alphaMod);
650adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    }
651eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
6522b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private void showPicker(boolean hours, boolean animate) {
6532b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        if (mShowHours == hours) {
6542b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            return;
6552b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        }
6562b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
6572b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mShowHours = hours;
6582b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
6592b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        if (animate) {
6602b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            animatePicker(hours, ANIM_DURATION_NORMAL);
66132f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette        } else {
66232f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette            // If we have a pending or running animator, cancel it.
66332f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette            if (mHoursToMinutesAnimator != null && mHoursToMinutesAnimator.isStarted()) {
66432f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette                mHoursToMinutesAnimator.cancel();
66532f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette                mHoursToMinutesAnimator = null;
66632f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette            }
66732f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette            mHoursToMinutes = hours ? 0.0f : 1.0f;
6682b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        }
6692b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
6702b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        initData();
6712b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        invalidate();
6722b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mTouchHelper.invalidateRoot();
6732b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    }
6742b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
6752b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private void animatePicker(boolean hoursToMinutes, long duration) {
6762b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final float target = hoursToMinutes ? HOURS : MINUTES;
6772b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        if (mHoursToMinutes == target) {
6782b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            // If we have a pending or running animator, cancel it.
6792b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            if (mHoursToMinutesAnimator != null && mHoursToMinutesAnimator.isStarted()) {
6802b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                mHoursToMinutesAnimator.cancel();
6812b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                mHoursToMinutesAnimator = null;
6822b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            }
6832b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
6842b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            // We're already showing the correct picker.
6852b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            return;
6862b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        }
6872b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
6882b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mHoursToMinutesAnimator = ObjectAnimator.ofFloat(this, HOURS_TO_MINUTES, target);
6892b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mHoursToMinutesAnimator.setAutoCancel(true);
6902b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mHoursToMinutesAnimator.setDuration(duration);
6912b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mHoursToMinutesAnimator.start();
6922b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    }
6932b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
694adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private void drawCircleBackground(Canvas canvas) {
695adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        canvas.drawCircle(mXCenter, mYCenter, mCircleRadius, mPaintBackground);
696adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    }
697adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette
6982b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private void drawHours(Canvas canvas, Path selectorPath, float alphaMod) {
6992b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int hoursAlpha = (int) (255f * (1f - mHoursToMinutes) * alphaMod + 0.5f);
700ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        if (hoursAlpha > 0) {
7012b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            // Exclude the selector region, then draw inner/outer hours with no
7022b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            // activated states.
7032b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.save(Canvas.CLIP_SAVE_FLAG);
7042b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.clipPath(selectorPath, Region.Op.DIFFERENCE);
7052b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            drawHoursClipped(canvas, hoursAlpha, false);
7062b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.restore();
7072b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
7082b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            // Intersect the selector region, then draw minutes with only
7092b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            // activated states.
7102b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.save(Canvas.CLIP_SAVE_FLAG);
7112b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.clipPath(selectorPath, Region.Op.INTERSECT);
7122b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            drawHoursClipped(canvas, hoursAlpha, true);
7132b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.restore();
714eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
715adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    }
716eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
7172b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private void drawHoursClipped(Canvas canvas, int hoursAlpha, boolean showActivated) {
7182b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        // Draw outer hours.
7192b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        drawTextElements(canvas, mTextSize[HOURS], mTypeface, mTextColor[HOURS], mOuterTextHours,
7202b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                mOuterTextX[HOURS], mOuterTextY[HOURS], mPaint[HOURS], hoursAlpha,
7212b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                showActivated && !mIsOnInnerCircle, mSelectionDegrees[HOURS], showActivated);
722ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
7232b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        // Draw inner hours (13-00) for 24-hour time.
7242b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        if (mIs24HourMode && mInnerTextHours != null) {
7252b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            drawTextElements(canvas, mTextSize[HOURS_INNER], mTypeface, mTextColor[HOURS_INNER],
7262b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                    mInnerTextHours, mInnerTextX, mInnerTextY, mPaint[HOURS], hoursAlpha,
7272b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                    showActivated && mIsOnInnerCircle, mSelectionDegrees[HOURS], showActivated);
7282b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        }
7292b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    }
7302b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
7312b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private void drawMinutes(Canvas canvas, Path selectorPath, float alphaMod) {
7322b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int minutesAlpha = (int) (255f * mHoursToMinutes * alphaMod + 0.5f);
7332b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        if (minutesAlpha > 0) {
734ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            // Exclude the selector region, then draw minutes with no
735ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            // activated states.
736ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            canvas.save(Canvas.CLIP_SAVE_FLAG);
7372b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.clipPath(selectorPath, Region.Op.DIFFERENCE);
7382b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            drawMinutesClipped(canvas, minutesAlpha, false);
739ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            canvas.restore();
740ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
741ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            // Intersect the selector region, then draw minutes with only
742ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            // activated states.
743ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            canvas.save(Canvas.CLIP_SAVE_FLAG);
7442b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.clipPath(selectorPath, Region.Op.INTERSECT);
7452b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            drawMinutesClipped(canvas, minutesAlpha, true);
746ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            canvas.restore();
747ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        }
748eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
749eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
7502b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private void drawMinutesClipped(Canvas canvas, int minutesAlpha, boolean showActivated) {
7512b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mTextColor[MINUTES], mMinutesText,
7522b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES], minutesAlpha,
7532b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                showActivated, mSelectionDegrees[MINUTES], showActivated);
7542b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    }
7552b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
756adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private void drawCenter(Canvas canvas, float alphaMod) {
757adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mPaintCenter.setAlpha((int) (255 * alphaMod + 0.5f));
758adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        canvas.drawCircle(mXCenter, mYCenter, mCenterDotRadius, mPaintCenter);
759eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
760eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
761eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette    private int getMultipliedAlpha(int argb, int alpha) {
762eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        return (int) (Color.alpha(argb) * (alpha / 255.0) + 0.5);
763eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette    }
764eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
7652b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private void drawSelector(Canvas canvas, Path selectorPath) {
7662b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        // Determine the current length, angle, and dot scaling factor.
7672b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int hoursIndex = mIsOnInnerCircle ? HOURS_INNER : HOURS;
7682b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int hoursInset = mTextInset[hoursIndex];
7692b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int hoursAngleDeg = mSelectionDegrees[hoursIndex % 2];
7702b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final float hoursDotScale = mSelectionDegrees[hoursIndex % 2] % 30 != 0 ? 1 : 0;
7712b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
7722b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int minutesIndex = MINUTES;
7732b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int minutesInset = mTextInset[minutesIndex];
7742b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int minutesAngleDeg = mSelectionDegrees[minutesIndex];
7752b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final float minutesDotScale = mSelectionDegrees[minutesIndex] % 30 != 0 ? 1 : 0;
776eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
777f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        // Calculate the current radius at which to place the selection circle.
778f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        final int selRadius = mSelectorRadius;
7792b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final float selLength =
7802b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                mCircleRadius - MathUtils.lerp(hoursInset, minutesInset, mHoursToMinutes);
7812b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final double selAngleRad =
7822b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                Math.toRadians(MathUtils.lerpDeg(hoursAngleDeg, minutesAngleDeg, mHoursToMinutes));
783f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        final float selCenterX = mXCenter + selLength * (float) Math.sin(selAngleRad);
784f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        final float selCenterY = mYCenter - selLength * (float) Math.cos(selAngleRad);
785f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette
786f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        // Draw the selection circle.
7872b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final Paint paint = mPaintSelector[SELECTOR_CIRCLE];
7882b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        paint.setColor(mSelectorColor);
789f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        canvas.drawCircle(selCenterX, selCenterY, selRadius, paint);
790eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
791ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        // If needed, set up the clip path for later.
792ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        if (selectorPath != null) {
793f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette            selectorPath.reset();
794f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette            selectorPath.addCircle(selCenterX, selCenterY, selRadius, Path.Direction.CCW);
795ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        }
796ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
797f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        // Draw the dot if we're between two items.
7982b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final float dotScale = MathUtils.lerp(hoursDotScale, minutesDotScale, mHoursToMinutes);
7992b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        if (dotScale > 0) {
8002b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            final Paint dotPaint = mPaintSelector[SELECTOR_DOT];
80188e51032320ab92057666e1230cc6548ca163c51Alan Viverette            dotPaint.setColor(mSelectorDotColor);
8022b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.drawCircle(selCenterX, selCenterY, mSelectorDotRadius * dotScale, dotPaint);
803eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
804eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
805adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        // Shorten the line to only go from the edge of the center dot to the
806adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        // edge of the selection circle.
807f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        final double sin = Math.sin(selAngleRad);
808f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        final double cos = Math.cos(selAngleRad);
8092b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final float lineLength = selLength - selRadius;
810adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        final int centerX = mXCenter + (int) (mCenterDotRadius * sin);
811adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        final int centerY = mYCenter - (int) (mCenterDotRadius * cos);
812f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        final float linePointX = centerX + (int) (lineLength * sin);
813f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        final float linePointY = centerY - (int) (lineLength * cos);
814ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
815f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        // Draw the line.
8162b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final Paint linePaint = mPaintSelector[SELECTOR_LINE];
8172b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        linePaint.setColor(mSelectorColor);
818f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        linePaint.setStrokeWidth(mSelectorStroke);
819f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        canvas.drawLine(mXCenter, mYCenter, linePointX, linePointY, linePaint);
820eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
821eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
822ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private void calculatePositionsHours() {
823eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Calculate the text positions
824adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        final float numbersRadius = mCircleRadius - mTextInset[HOURS];
825eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
826eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Calculate the positions for the 12 numbers in the main circle.
827ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        calculatePositions(mPaint[HOURS], numbersRadius, mXCenter, mYCenter,
828ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                mTextSize[HOURS], mOuterTextX[HOURS], mOuterTextY[HOURS]);
829eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
830eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // If we have an inner circle, calculate those positions too.
831eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mIs24HourMode) {
832adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette            final int innerNumbersRadius = mCircleRadius - mTextInset[HOURS_INNER];
833ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            calculatePositions(mPaint[HOURS], innerNumbersRadius, mXCenter, mYCenter,
834ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    mTextSize[HOURS_INNER], mInnerTextX, mInnerTextY);
835eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
836eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
837eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
838ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private void calculatePositionsMinutes() {
839eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Calculate the text positions
840adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        final float numbersRadius = mCircleRadius - mTextInset[MINUTES];
841eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
842eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Calculate the positions for the 12 numbers in the main circle.
843ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        calculatePositions(mPaint[MINUTES], numbersRadius, mXCenter, mYCenter,
844ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                mTextSize[MINUTES], mOuterTextX[MINUTES], mOuterTextY[MINUTES]);
845eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
846eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
847eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    /**
848eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * Using the trigonometric Unit Circle, calculate the positions that the text will need to be
849eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * drawn at based on the specified circle radius. Place the values in the textGridHeights and
850eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * textGridWidths parameters.
851eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     */
852ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private static void calculatePositions(Paint paint, float radius, float xCenter, float yCenter,
853ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            float textSize, float[] x, float[] y) {
854ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        // Adjust yCenter to account for the text's baseline.
855eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        paint.setTextSize(textSize);
856eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        yCenter -= (paint.descent() + paint.ascent()) / 2;
857eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
858ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        for (int i = 0; i < NUM_POSITIONS; i++) {
859ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            x[i] = xCenter - radius * COS_30[i];
860ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            y[i] = yCenter - radius * SIN_30[i];
861ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        }
862eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
863eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
864eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    /**
865eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * Draw the 12 text values at the positions specified by the textGrid parameters.
866eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     */
867adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private void drawTextElements(Canvas canvas, float textSize, Typeface typeface,
868adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette            ColorStateList textColor, String[] texts, float[] textX, float[] textY, Paint paint,
869adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette            int alpha, boolean showActivated, int activatedDegrees, boolean activatedOnly) {
870eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        paint.setTextSize(textSize);
871eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        paint.setTypeface(typeface);
872eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
873ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        // The activated index can touch a range of elements.
874ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final float activatedIndex = activatedDegrees / (360.0f / NUM_POSITIONS);
875ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final int activatedFloor = (int) activatedIndex;
876ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final int activatedCeil = ((int) Math.ceil(activatedIndex)) % NUM_POSITIONS;
877eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
878ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        for (int i = 0; i < 12; i++) {
879ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            final boolean activated = (activatedFloor == i || activatedCeil == i);
880ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            if (activatedOnly && !activated) {
881ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                continue;
882ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            }
883eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
884ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            final int stateMask = StateSet.VIEW_STATE_ENABLED
885ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    | (showActivated && activated ? StateSet.VIEW_STATE_ACTIVATED : 0);
886adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette            final int color = textColor.getColorForState(StateSet.get(stateMask), 0);
887ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            paint.setColor(color);
888ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            paint.setAlpha(getMultipliedAlpha(color, alpha));
889eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
890ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            canvas.drawText(texts[i], textX[i], textY[i], paint);
891ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        }
892eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
893eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
894ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private int getDegreesFromXY(float x, float y, boolean constrainOutside) {
89588e51032320ab92057666e1230cc6548ca163c51Alan Viverette        // Ensure the point is inside the touchable area.
89688e51032320ab92057666e1230cc6548ca163c51Alan Viverette        final int innerBound;
89788e51032320ab92057666e1230cc6548ca163c51Alan Viverette        final int outerBound;
89888e51032320ab92057666e1230cc6548ca163c51Alan Viverette        if (mIs24HourMode && mShowHours) {
89988e51032320ab92057666e1230cc6548ca163c51Alan Viverette            innerBound = mMinDistForInnerNumber;
90088e51032320ab92057666e1230cc6548ca163c51Alan Viverette            outerBound = mMaxDistForOuterNumber;
90188e51032320ab92057666e1230cc6548ca163c51Alan Viverette        } else {
90288e51032320ab92057666e1230cc6548ca163c51Alan Viverette            final int index = mShowHours ? HOURS : MINUTES;
90388e51032320ab92057666e1230cc6548ca163c51Alan Viverette            final int center = mCircleRadius - mTextInset[index];
90488e51032320ab92057666e1230cc6548ca163c51Alan Viverette            innerBound = center - mSelectorRadius;
90588e51032320ab92057666e1230cc6548ca163c51Alan Viverette            outerBound = center + mSelectorRadius;
90688e51032320ab92057666e1230cc6548ca163c51Alan Viverette        }
907eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
90888e51032320ab92057666e1230cc6548ca163c51Alan Viverette        final double dX = x - mXCenter;
90988e51032320ab92057666e1230cc6548ca163c51Alan Viverette        final double dY = y - mYCenter;
91088e51032320ab92057666e1230cc6548ca163c51Alan Viverette        final double distFromCenter = Math.sqrt(dX * dX + dY * dY);
91188e51032320ab92057666e1230cc6548ca163c51Alan Viverette        if (distFromCenter < innerBound || constrainOutside && distFromCenter > outerBound) {
912eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            return -1;
913eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
914ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
91588e51032320ab92057666e1230cc6548ca163c51Alan Viverette        // Convert to degrees.
91688e51032320ab92057666e1230cc6548ca163c51Alan Viverette        final int degrees = (int) (Math.toDegrees(Math.atan2(dY, dX) + Math.PI / 2) + 0.5);
91788e51032320ab92057666e1230cc6548ca163c51Alan Viverette        if (degrees < 0) {
91888e51032320ab92057666e1230cc6548ca163c51Alan Viverette            return degrees + 360;
919eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        } else {
92088e51032320ab92057666e1230cc6548ca163c51Alan Viverette            return degrees;
921eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
92288e51032320ab92057666e1230cc6548ca163c51Alan Viverette    }
923eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
92488e51032320ab92057666e1230cc6548ca163c51Alan Viverette    private boolean getInnerCircleFromXY(float x, float y) {
92588e51032320ab92057666e1230cc6548ca163c51Alan Viverette        if (mIs24HourMode && mShowHours) {
92688e51032320ab92057666e1230cc6548ca163c51Alan Viverette            final double dX = x - mXCenter;
92788e51032320ab92057666e1230cc6548ca163c51Alan Viverette            final double dY = y - mYCenter;
92888e51032320ab92057666e1230cc6548ca163c51Alan Viverette            final double distFromCenter = Math.sqrt(dX * dX + dY * dY);
92988e51032320ab92057666e1230cc6548ca163c51Alan Viverette            return distFromCenter <= mHalfwayDist;
930eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
93188e51032320ab92057666e1230cc6548ca163c51Alan Viverette        return false;
932eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
933eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
934002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette    boolean mChangedDuringTouch = false;
935002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
936eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    @Override
937ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    public boolean onTouchEvent(MotionEvent event) {
938002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        if (!mInputEnabled) {
939eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            return true;
940eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
941eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
942002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        final int action = event.getActionMasked();
943002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        if (action == MotionEvent.ACTION_MOVE
944002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                || action == MotionEvent.ACTION_UP
945002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                || action == MotionEvent.ACTION_DOWN) {
946002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            boolean forceSelection = false;
947002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            boolean autoAdvance = false;
948002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
949002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            if (action == MotionEvent.ACTION_DOWN) {
950002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                // This is a new event stream, reset whether the value changed.
951002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                mChangedDuringTouch = false;
952002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            } else if (action == MotionEvent.ACTION_UP) {
953002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                autoAdvance = true;
954002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
955002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                // If we saw a down/up pair without the value changing, assume
956002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                // this is a single-tap selection and force a change.
957002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                if (!mChangedDuringTouch) {
958002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                    forceSelection = true;
959eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                }
960002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            }
961eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
962002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            mChangedDuringTouch |= handleTouchInput(
963002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                    event.getX(), event.getY(), forceSelection, autoAdvance);
964eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
965002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
966002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        return true;
967002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette    }
968002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
969002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette    private boolean handleTouchInput(
970002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            float x, float y, boolean forceSelection, boolean autoAdvance) {
97188e51032320ab92057666e1230cc6548ca163c51Alan Viverette        final boolean isOnInnerCircle = getInnerCircleFromXY(x, y);
972ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final int degrees = getDegreesFromXY(x, y, false);
973002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        if (degrees == -1) {
974002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            return false;
975002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        }
976002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
9772b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        // Ensure we're showing the correct picker.
9782b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        animatePicker(mShowHours, ANIM_DURATION_TOUCH);
9792b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
98011a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette        final int type;
98111a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette        final int newValue;
98211a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette        final boolean valueChanged;
983002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
984002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        if (mShowHours) {
985002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            final int snapDegrees = snapOnly30s(degrees, 0) % 360;
98688e51032320ab92057666e1230cc6548ca163c51Alan Viverette            valueChanged = mIsOnInnerCircle != isOnInnerCircle
98788e51032320ab92057666e1230cc6548ca163c51Alan Viverette                    || mSelectionDegrees[HOURS] != snapDegrees;
98888e51032320ab92057666e1230cc6548ca163c51Alan Viverette            mIsOnInnerCircle = isOnInnerCircle;
98988e51032320ab92057666e1230cc6548ca163c51Alan Viverette            mSelectionDegrees[HOURS] = snapDegrees;
99011a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            type = HOURS;
99111a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            newValue = getCurrentHour();
992002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        } else {
993002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            final int snapDegrees = snapPrefer30s(degrees) % 360;
99488e51032320ab92057666e1230cc6548ca163c51Alan Viverette            valueChanged = mSelectionDegrees[MINUTES] != snapDegrees;
99588e51032320ab92057666e1230cc6548ca163c51Alan Viverette            mSelectionDegrees[MINUTES] = snapDegrees;
99611a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            type = MINUTES;
99711a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            newValue = getCurrentMinute();
998002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        }
999002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
100011a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette        if (valueChanged || forceSelection || autoAdvance) {
100111a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            // Fire the listener even if we just need to auto-advance.
1002002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            if (mListener != null) {
1003002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                mListener.onValueSelected(type, newValue, autoAdvance);
1004002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            }
100511a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette
100611a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            // Only provide feedback if the value actually changed.
100711a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            if (valueChanged || forceSelection) {
100811a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette                performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
100911a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette                invalidate();
101011a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            }
1011002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            return true;
1012002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        }
1013002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
1014002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        return false;
1015eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
1016eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1017eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    @Override
1018ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    public boolean dispatchHoverEvent(MotionEvent event) {
1019ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        // First right-of-refusal goes the touch exploration helper.
1020ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (mTouchHelper.dispatchHoverEvent(event)) {
1021eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            return true;
1022eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1023ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return super.dispatchHoverEvent(event);
1024eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
1025eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1026ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    public void setInputEnabled(boolean inputEnabled) {
1027ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        mInputEnabled = inputEnabled;
1028ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        invalidate();
1029ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
1030ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1031ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private class RadialPickerTouchHelper extends ExploreByTouchHelper {
1032ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final Rect mTempRect = new Rect();
1033ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1034ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int TYPE_HOUR = 1;
1035ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int TYPE_MINUTE = 2;
1036ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1037ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int SHIFT_TYPE = 0;
1038ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int MASK_TYPE = 0xF;
1039ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1040ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int SHIFT_VALUE = 8;
1041ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int MASK_VALUE = 0xFF;
1042ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1043ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        /** Increment in which virtual views are exposed for minutes. */
1044ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int MINUTE_INCREMENT = 5;
1045ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1046ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        public RadialPickerTouchHelper() {
1047ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            super(RadialTimePickerView.this);
1048eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1049eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1050ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1051ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
1052ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            super.onInitializeAccessibilityNodeInfo(host, info);
1053ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1054ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
1055ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
1056eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1057ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1058ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1059ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
1060ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (super.performAccessibilityAction(host, action, arguments)) {
1061ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                return true;
1062eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
1063eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1064ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            switch (action) {
1065ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
1066ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    adjustPicker(1);
1067ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    return true;
1068ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
1069ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    adjustPicker(-1);
1070ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    return true;
1071ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1072ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1073ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return false;
1074ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1075ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1076ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private void adjustPicker(int step) {
1077ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int stepSize;
10784a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette            final int initialStep;
107967945c11a5e9547f71be91ceb99e7b9ff15a6292Alan Viverette            final int maxValue;
1080ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int minValue;
1081eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if (mShowHours) {
10824a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                stepSize = 1;
1083ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
10844a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                final int currentHour24 = getCurrentHour();
1085eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                if (mIs24HourMode) {
10864a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                    initialStep = currentHour24;
1087ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    minValue = 0;
10884a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                    maxValue = 23;
1089eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                } else {
10904a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                    initialStep = hour24To12(currentHour24);
1091eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    minValue = 1;
10924a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                    maxValue = 12;
1093eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                }
1094eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            } else {
10954a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                stepSize = 5;
10964a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                initialStep = getCurrentMinute() / stepSize;
1097ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                minValue = 0;
10984a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                maxValue = 55;
1099eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
1100ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
11014a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette            final int nextValue = (initialStep + step) * stepSize;
11024a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette            final int clampedValue = MathUtils.constrain(nextValue, minValue, maxValue);
1103ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (mShowHours) {
1104ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                setCurrentHour(clampedValue);
1105ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1106ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                setCurrentMinute(clampedValue);
1107ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1108ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1109ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1110ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1111ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected int getVirtualViewAt(float x, float y) {
1112ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int id;
1113d8c2af55420d869c140278e6643d16cd90cfeb51Alan Viverette            final int degrees = getDegreesFromXY(x, y, true);
1114ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (degrees != -1) {
1115ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int snapDegrees = snapOnly30s(degrees, 0) % 360;
1116ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                if (mShowHours) {
111788e51032320ab92057666e1230cc6548ca163c51Alan Viverette                    final boolean isOnInnerCircle = getInnerCircleFromXY(x, y);
11185efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette                    final int hour24 = getHourForDegrees(snapDegrees, isOnInnerCircle);
11195efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette                    final int hour = mIs24HourMode ? hour24 : hour24To12(hour24);
1120ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    id = makeId(TYPE_HOUR, hour);
1121ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                } else {
1122ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    final int current = getCurrentMinute();
1123ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    final int touched = getMinuteForDegrees(degrees);
1124ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    final int snapped = getMinuteForDegrees(snapDegrees);
1125ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1126ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    // If the touched minute is closer to the current minute
1127ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    // than it is to the snapped minute, return current.
1128fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette                    final int currentOffset = getCircularDiff(current, touched, MINUTES_IN_CIRCLE);
1129fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette                    final int snappedOffset = getCircularDiff(snapped, touched, MINUTES_IN_CIRCLE);
1130ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    final int minute;
113188e51032320ab92057666e1230cc6548ca163c51Alan Viverette                    if (currentOffset < snappedOffset) {
1132ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                        minute = current;
1133ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    } else {
1134ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                        minute = snapped;
1135ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    }
1136ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    id = makeId(TYPE_MINUTE, minute);
1137ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                }
1138ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1139ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                id = INVALID_ID;
1140eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
1141ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1142ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return id;
1143ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1144ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
114588e51032320ab92057666e1230cc6548ca163c51Alan Viverette        /**
114688e51032320ab92057666e1230cc6548ca163c51Alan Viverette         * Returns the difference in degrees between two values along a circle.
114788e51032320ab92057666e1230cc6548ca163c51Alan Viverette         *
114888e51032320ab92057666e1230cc6548ca163c51Alan Viverette         * @param first value in the range [0,max]
114988e51032320ab92057666e1230cc6548ca163c51Alan Viverette         * @param second value in the range [0,max]
115088e51032320ab92057666e1230cc6548ca163c51Alan Viverette         * @param max the maximum value along the circle
115188e51032320ab92057666e1230cc6548ca163c51Alan Viverette         * @return the difference in between the two values
115288e51032320ab92057666e1230cc6548ca163c51Alan Viverette         */
115388e51032320ab92057666e1230cc6548ca163c51Alan Viverette        private int getCircularDiff(int first, int second, int max) {
115488e51032320ab92057666e1230cc6548ca163c51Alan Viverette            final int diff = Math.abs(first - second);
115588e51032320ab92057666e1230cc6548ca163c51Alan Viverette            final int midpoint = max / 2;
115688e51032320ab92057666e1230cc6548ca163c51Alan Viverette            return (diff > midpoint) ? (max - diff) : diff;
115788e51032320ab92057666e1230cc6548ca163c51Alan Viverette        }
115888e51032320ab92057666e1230cc6548ca163c51Alan Viverette
1159ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1160ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected void getVisibleVirtualViews(IntArray virtualViewIds) {
1161eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if (mShowHours) {
1162ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int min = mIs24HourMode ? 0 : 1;
1163ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int max = mIs24HourMode ? 23 : 12;
1164ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                for (int i = min; i <= max ; i++) {
1165ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    virtualViewIds.add(makeId(TYPE_HOUR, i));
1166eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                }
1167eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            } else {
1168ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int current = getCurrentMinute();
1169fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette                for (int i = 0; i < MINUTES_IN_CIRCLE; i += MINUTE_INCREMENT) {
1170ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    virtualViewIds.add(makeId(TYPE_MINUTE, i));
1171ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1172ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    // If the current minute falls between two increments,
1173ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    // insert an extra node for it.
1174ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    if (current > i && current < i + MINUTE_INCREMENT) {
1175ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                        virtualViewIds.add(makeId(TYPE_MINUTE, current));
1176ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    }
1177eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                }
1178eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
1179eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1180eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1181ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1182ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
1183ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            event.setClassName(getClass().getName());
1184eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1185ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int type = getTypeFromId(virtualViewId);
1186ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int value = getValueFromId(virtualViewId);
1187ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final CharSequence description = getVirtualViewDescription(type, value);
1188ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            event.setContentDescription(description);
1189ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1190ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1191ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1192ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) {
1193ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            node.setClassName(getClass().getName());
1194ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            node.addAction(AccessibilityAction.ACTION_CLICK);
1195ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1196ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int type = getTypeFromId(virtualViewId);
1197ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int value = getValueFromId(virtualViewId);
1198ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final CharSequence description = getVirtualViewDescription(type, value);
1199ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            node.setContentDescription(description);
1200ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1201ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            getBoundsForVirtualView(virtualViewId, mTempRect);
1202ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            node.setBoundsInParent(mTempRect);
1203ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1204ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final boolean selected = isVirtualViewSelected(type, value);
1205ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            node.setSelected(selected);
12063fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette
12073fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            final int nextId = getVirtualViewIdAfter(type, value);
12083fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            if (nextId != INVALID_ID) {
12093fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                node.setTraversalBefore(RadialTimePickerView.this, nextId);
12103fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            }
12113fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette        }
12123fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette
12133fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette        private int getVirtualViewIdAfter(int type, int value) {
12143fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            if (type == TYPE_HOUR) {
12153fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                final int nextValue = value + 1;
12163fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                final int max = mIs24HourMode ? 23 : 12;
12173fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                if (nextValue <= max) {
12183fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                    return makeId(type, nextValue);
12193fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                }
12203fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            } else if (type == TYPE_MINUTE) {
12213fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                final int current = getCurrentMinute();
12223fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                final int snapValue = value - (value % MINUTE_INCREMENT);
12233fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                final int nextValue = snapValue + MINUTE_INCREMENT;
12243fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                if (value < current && nextValue > current) {
12253fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                    // The current value is between two snap values.
12263fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                    return makeId(type, current);
1227fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette                } else if (nextValue < MINUTES_IN_CIRCLE) {
12283fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                    return makeId(type, nextValue);
12293fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                }
12303fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            }
12313fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            return INVALID_ID;
1232ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1233ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1234ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1235ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
1236ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                Bundle arguments) {
1237ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (action == AccessibilityNodeInfo.ACTION_CLICK) {
1238ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int type = getTypeFromId(virtualViewId);
1239ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int value = getValueFromId(virtualViewId);
1240ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                if (type == TYPE_HOUR) {
1241ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    final int hour = mIs24HourMode ? value : hour12To24(value, mAmOrPm);
1242ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    setCurrentHour(hour);
1243ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    return true;
1244ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                } else if (type == TYPE_MINUTE) {
1245ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    setCurrentMinute(value);
1246ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    return true;
1247ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                }
1248ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1249ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return false;
1250ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1251ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1252ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private int hour12To24(int hour12, int amOrPm) {
1253ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            int hour24 = hour12;
1254ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (hour12 == 12) {
1255ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                if (amOrPm == AM) {
1256ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    hour24 = 0;
1257ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                }
1258ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else if (amOrPm == PM) {
1259ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                hour24 += 12;
1260ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1261ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return hour24;
1262ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1263ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
12645efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette        private int hour24To12(int hour24) {
12655efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette            if (hour24 == 0) {
12665efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette                return 12;
12675efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette            } else if (hour24 > 12) {
12685efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette                return hour24 - 12;
12695efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette            } else {
12705efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette                return hour24;
12715efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette            }
12725efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette        }
12735efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette
1274ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private void getBoundsForVirtualView(int virtualViewId, Rect bounds) {
1275ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final float radius;
1276ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int type = getTypeFromId(virtualViewId);
1277ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int value = getValueFromId(virtualViewId);
1278ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final float centerRadius;
1279ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final float degrees;
1280ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (type == TYPE_HOUR) {
128188e51032320ab92057666e1230cc6548ca163c51Alan Viverette                final boolean innerCircle = getInnerCircleForHour(value);
1282ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                if (innerCircle) {
1283adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette                    centerRadius = mCircleRadius - mTextInset[HOURS_INNER];
1284adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette                    radius = mSelectorRadius;
1285ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                } else {
1286adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette                    centerRadius = mCircleRadius - mTextInset[HOURS];
1287adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette                    radius = mSelectorRadius;
1288ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                }
1289ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1290ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                degrees = getDegreesForHour(value);
1291ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else if (type == TYPE_MINUTE) {
1292adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette                centerRadius = mCircleRadius - mTextInset[MINUTES];
1293ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                degrees = getDegreesForMinute(value);
1294adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette                radius = mSelectorRadius;
1295ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1296ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                // This should never happen.
1297ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                centerRadius = 0;
1298ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                degrees = 0;
1299ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                radius = 0;
1300ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1301ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1302ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final double radians = Math.toRadians(degrees);
1303ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final float xCenter = mXCenter + centerRadius * (float) Math.sin(radians);
1304ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final float yCenter = mYCenter - centerRadius * (float) Math.cos(radians);
1305ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1306ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            bounds.set((int) (xCenter - radius), (int) (yCenter - radius),
1307ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    (int) (xCenter + radius), (int) (yCenter + radius));
1308ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1309ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1310ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private CharSequence getVirtualViewDescription(int type, int value) {
1311ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final CharSequence description;
1312ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (type == TYPE_HOUR || type == TYPE_MINUTE) {
1313ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                description = Integer.toString(value);
1314ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1315ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                description = null;
1316ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1317ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return description;
1318ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1319ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1320ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private boolean isVirtualViewSelected(int type, int value) {
1321ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final boolean selected;
1322ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (type == TYPE_HOUR) {
1323ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                selected = getCurrentHour() == value;
1324ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else if (type == TYPE_MINUTE) {
1325ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                selected = getCurrentMinute() == value;
1326ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1327ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                selected = false;
1328ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1329ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return selected;
1330ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1331ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1332ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private int makeId(int type, int value) {
1333ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return type << SHIFT_TYPE | value << SHIFT_VALUE;
1334ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1335ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1336ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private int getTypeFromId(int id) {
1337ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return id >>> SHIFT_TYPE & MASK_TYPE;
1338ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1339ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1340ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private int getValueFromId(int id) {
1341ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return id >>> SHIFT_VALUE & MASK_VALUE;
1342ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1343eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
1344eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio}
1345