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;
2066a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viveretteimport android.annotation.IntDef;
21eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.content.Context;
22ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.content.res.ColorStateList;
23eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.content.res.Resources;
24eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.content.res.TypedArray;
25eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Canvas;
26eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Color;
27eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Paint;
28ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.graphics.Path;
29ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.graphics.Rect;
30ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.graphics.Region;
31eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Typeface;
32eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.os.Bundle;
33eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.util.AttributeSet;
342b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viveretteimport android.util.FloatProperty;
35ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.util.IntArray;
36eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.util.Log;
37ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.util.MathUtils;
38ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.util.StateSet;
395134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viveretteimport android.util.TypedValue;
40eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.HapticFeedbackConstants;
41eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.MotionEvent;
4299441c5d7da45c10b729185852be97cbb0bdc8d5Aurimas Liutikasimport android.view.PointerIcon;
43eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.View;
44eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.accessibility.AccessibilityEvent;
45eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.accessibility.AccessibilityNodeInfo;
46ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
47eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
4899441c5d7da45c10b729185852be97cbb0bdc8d5Aurimas Liutikasimport com.android.internal.R;
4999441c5d7da45c10b729185852be97cbb0bdc8d5Aurimas Liutikasimport com.android.internal.widget.ExploreByTouchHelper;
5099441c5d7da45c10b729185852be97cbb0bdc8d5Aurimas Liutikas
5166a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viveretteimport java.lang.annotation.Retention;
5266a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viveretteimport java.lang.annotation.RetentionPolicy;
53eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport java.util.Calendar;
54eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport java.util.Locale;
55eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
56eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio/**
57eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * View to show a clock circle picker (with one or two picking circles)
58eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio *
59eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * @hide
60eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */
61ec9fe1ad663d59829075029508673b2e913538d3Alan Viverettepublic class RadialTimePickerView extends View {
62ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private static final String TAG = "RadialTimePickerView";
63eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
64b0f5461eeca28d6c4be6ba1f826a43a8961f587eAlan Viverette    public static final int HOURS = 0;
65b0f5461eeca28d6c4be6ba1f826a43a8961f587eAlan Viverette    public static final int MINUTES = 1;
6666a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette
6766a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette    /** @hide */
6866a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette    @IntDef({HOURS, MINUTES})
6966a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette    @Retention(RetentionPolicy.SOURCE)
7066a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette    @interface PickerType {}
7166a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette
72eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int HOURS_INNER = 2;
73eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
74eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int SELECTOR_CIRCLE = 0;
75eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int SELECTOR_DOT = 1;
76eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int SELECTOR_LINE = 2;
77eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
78eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int AM = 0;
79eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int PM = 1;
80eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
81fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette    private static final int HOURS_IN_CIRCLE = 12;
82fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette    private static final int MINUTES_IN_CIRCLE = 60;
83fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette    private static final int DEGREES_FOR_ONE_HOUR = 360 / HOURS_IN_CIRCLE;
84fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette    private static final int DEGREES_FOR_ONE_MINUTE = 360 / MINUTES_IN_CIRCLE;
85eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
86eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int[] HOURS_NUMBERS = {12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
87eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int[] HOURS_NUMBERS_24 = {0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23};
88eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int[] MINUTES_NUMBERS = {0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55};
89eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
902b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private static final int ANIM_DURATION_NORMAL = 500;
912b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private static final int ANIM_DURATION_TOUCH = 60;
92ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
93ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private static final int[] SNAP_PREFER_30S_MAP = new int[361];
94ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
95ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private static final int NUM_POSITIONS = 12;
96ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private static final float[] COS_30 = new float[NUM_POSITIONS];
97ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private static final float[] SIN_30 = new float[NUM_POSITIONS];
98ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
992b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    /** "Something is wrong" color used when a color attribute is missing. */
1002b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private static final int MISSING_COLOR = Color.MAGENTA;
1012b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
102ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    static {
103ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        // Prepare mapping to snap touchable degrees to selectable degrees.
104ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        preparePrefer30sMap();
105ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
106ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final double increment = 2.0 * Math.PI / NUM_POSITIONS;
107ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        double angle = Math.PI / 2.0;
108ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        for (int i = 0; i < NUM_POSITIONS; i++) {
109ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            COS_30[i] = (float) Math.cos(angle);
110ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            SIN_30[i] = (float) Math.sin(angle);
111ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            angle += increment;
112ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        }
113ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    }
114eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1152b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private final FloatProperty<RadialTimePickerView> HOURS_TO_MINUTES =
1162b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            new FloatProperty<RadialTimePickerView>("hoursToMinutes") {
1172b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                @Override
1182b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                public Float get(RadialTimePickerView radialTimePickerView) {
1192b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                    return radialTimePickerView.mHoursToMinutes;
1202b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                }
1212b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
1222b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                @Override
1232b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                public void setValue(RadialTimePickerView object, float value) {
1242b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                    object.mHoursToMinutes = value;
1252b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                    object.invalidate();
1262b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                }
1272b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            };
128ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
129eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final String[] mHours12Texts = new String[12];
130eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final String[] mOuterHours24Texts = new String[12];
131eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final String[] mInnerHours24Texts = new String[12];
132eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final String[] mMinutesTexts = new String[12];
133eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
134eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final Paint[] mPaint = new Paint[2];
135eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final Paint mPaintCenter = new Paint();
1362b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private final Paint[] mPaintSelector = new Paint[3];
137eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final Paint mPaintBackground = new Paint();
138eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
139ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final Typeface mTypeface;
140ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
141adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private final ColorStateList[] mTextColor = new ColorStateList[3];
142adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private final int[] mTextSize = new int[3];
143adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private final int[] mTextInset = new int[3];
144ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
145ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private final float[][] mOuterTextX = new float[2][12];
146ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private final float[][] mOuterTextY = new float[2][12];
147ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
148ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private final float[] mInnerTextX = new float[12];
149ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private final float[] mInnerTextY = new float[12];
150ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
15188e51032320ab92057666e1230cc6548ca163c51Alan Viverette    private final int[] mSelectionDegrees = new int[2];
152ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
153ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final RadialPickerTouchHelper mTouchHelper;
154ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
155f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette    private final Path mSelectorPath = new Path();
156f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette
157eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private boolean mIs24HourMode;
158eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private boolean mShowHours;
159d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette
1602b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private ObjectAnimator mHoursToMinutesAnimator;
1612b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private float mHoursToMinutes;
1622b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
163d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette    /**
164d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * When in 24-hour mode, indicates that the current hour is between
165d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * 1 and 12 (inclusive).
166d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     */
167eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private boolean mIsOnInnerCircle;
168eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
169adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private int mSelectorRadius;
17062c79e9a64c3b2cafd5500ed3064977dff7b7da3Alan Viverette    private int mSelectorStroke;
171adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private int mSelectorDotRadius;
172adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private int mCenterDotRadius;
173adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette
1742b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private int mSelectorColor;
1752b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private int mSelectorDotColor;
1762b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
177adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private int mXCenter;
178adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private int mYCenter;
179adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private int mCircleRadius;
180eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
18188e51032320ab92057666e1230cc6548ca163c51Alan Viverette    private int mMinDistForInnerNumber;
18288e51032320ab92057666e1230cc6548ca163c51Alan Viverette    private int mMaxDistForOuterNumber;
18388e51032320ab92057666e1230cc6548ca163c51Alan Viverette    private int mHalfwayDist;
184eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
185eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private String[] mOuterTextHours;
186eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private String[] mInnerTextHours;
18788e51032320ab92057666e1230cc6548ca163c51Alan Viverette    private String[] mMinutesText;
188eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
189eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private int mAmOrPm;
190adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette
191adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private float mDisabledAlpha;
1925134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette
193eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private OnValueSelectedListener mListener;
194eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
195ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private boolean mInputEnabled = true;
196eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
19766a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette    interface OnValueSelectedListener {
19866a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette        /**
19966a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         * Called when the selected value at a given picker index has changed.
20066a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         *
20166a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         * @param pickerType the type of value that has changed, one of:
20266a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         *                   <ul>
20366a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         *                       <li>{@link #MINUTES}
20466a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         *                       <li>{@link #HOURS}
20566a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         *                   </ul>
20666a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         * @param newValue the new value as minute in hour (0-59) or hour in
20766a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         *                 day (0-23)
20866a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         * @param autoAdvance when the picker type is {@link #HOURS},
20966a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         *                    {@code true} to switch to the {@link #MINUTES}
21066a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         *                    picker or {@code false} to stay on the current
21166a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         *                    picker. No effect when picker type is
21266a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         *                    {@link #MINUTES}.
21366a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         */
21466a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette        void onValueSelected(@PickerType int pickerType, int newValue, boolean autoAdvance);
215eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
216eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
217eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    /**
218eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * Split up the 360 degrees of the circle among the 60 selectable values. Assigns a larger
219eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * selectable area to each of the 12 visible values, such that the ratio of space apportioned
220eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * to a visible value : space apportioned to a non-visible value will be 14 : 4.
221eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * E.g. the output of 30 degrees should have a higher range of input associated with it than
222eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * the output of 24 degrees, because 30 degrees corresponds to a visible number on the clock
223eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * circle (5 on the minutes, 1 or 13 on the hours).
224eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     */
225eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static void preparePrefer30sMap() {
226eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // We'll split up the visible output and the non-visible output such that each visible
227eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // output will correspond to a range of 14 associated input degrees, and each non-visible
228eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // output will correspond to a range of 4 associate input degrees, so visible numbers
229eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // are more than 3 times easier to get than non-visible numbers:
230eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // {354-359,0-7}:0, {8-11}:6, {12-15}:12, {16-19}:18, {20-23}:24, {24-37}:30, etc.
231eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        //
232eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // If an output of 30 degrees should correspond to a range of 14 associated degrees, then
233eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // we'll need any input between 24 - 37 to snap to 30. Working out from there, 20-23 should
234eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // snap to 24, while 38-41 should snap to 36. This is somewhat counter-intuitive, that you
235eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // can be touching 36 degrees but have the selection snapped to 30 degrees; however, this
236eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // inconsistency isn't noticeable at such fine-grained degrees, and it affords us the
237eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // ability to aggressively prefer the visible values by a factor of more than 3:1, which
238eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // greatly contributes to the selectability of these values.
239eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
240eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // The first output is 0, and each following output will increment by 6 {0, 6, 12, ...}.
241eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int snappedOutputDegrees = 0;
242eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Count of how many inputs we've designated to the specified output.
243eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int count = 1;
244eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // How many input we expect for a specified output. This will be 14 for output divisible
245eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // by 30, and 4 for the remaining output. We'll special case the outputs of 0 and 360, so
246eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // the caller can decide which they need.
247eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int expectedCount = 8;
248eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Iterate through the input.
249eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        for (int degrees = 0; degrees < 361; degrees++) {
250eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            // Save the input-output mapping.
251ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            SNAP_PREFER_30S_MAP[degrees] = snappedOutputDegrees;
252eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            // If this is the last input for the specified output, calculate the next output and
253eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            // the next expected count.
254eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if (count == expectedCount) {
255eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                snappedOutputDegrees += 6;
256eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                if (snappedOutputDegrees == 360) {
257eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    expectedCount = 7;
258eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                } else if (snappedOutputDegrees % 30 == 0) {
259eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    expectedCount = 14;
260eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                } else {
261eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    expectedCount = 4;
262eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                }
263eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                count = 1;
264eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            } else {
265eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                count++;
266eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
267eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
268eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
269eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
270eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    /**
271eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * Returns mapping of any input degrees (0 to 360) to one of 60 selectable output degrees,
272eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * where the degrees corresponding to visible numbers (i.e. those divisible by 30) will be
273eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * weighted heavier than the degrees corresponding to non-visible numbers.
274eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * See {@link #preparePrefer30sMap()} documentation for the rationale and generation of the
275eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * mapping.
276eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     */
277eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static int snapPrefer30s(int degrees) {
278ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        if (SNAP_PREFER_30S_MAP == null) {
279eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            return -1;
280eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
281ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        return SNAP_PREFER_30S_MAP[degrees];
282eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
283eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
284eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    /**
285eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * Returns mapping of any input degrees (0 to 360) to one of 12 visible output degrees (all
286eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * multiples of 30), where the input will be "snapped" to the closest visible degrees.
287eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * @param degrees The input degrees
288eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * @param forceHigherOrLower The output may be forced to either the higher or lower step, or may
289eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * be allowed to snap to whichever is closer. Use 1 to force strictly higher, -1 to force
290eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * strictly lower, and 0 to snap to the closer one.
291eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * @return output degrees, will be a multiple of 30
292eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     */
293eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static int snapOnly30s(int degrees, int forceHigherOrLower) {
294eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int stepSize = DEGREES_FOR_ONE_HOUR;
295eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int floor = (degrees / stepSize) * stepSize;
296eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int ceiling = floor + stepSize;
297eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (forceHigherOrLower == 1) {
298eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            degrees = ceiling;
299eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        } else if (forceHigherOrLower == -1) {
300eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if (degrees == floor) {
301eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                floor -= stepSize;
302eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
303eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            degrees = floor;
304eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        } else {
305eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if ((degrees - floor) < (ceiling - degrees)) {
306eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                degrees = floor;
307eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            } else {
308eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                degrees = ceiling;
309eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
310eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
311eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        return degrees;
312eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
313eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
314ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    @SuppressWarnings("unused")
315ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    public RadialTimePickerView(Context context)  {
316ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        this(context, null);
317ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
318ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
319eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public RadialTimePickerView(Context context, AttributeSet attrs)  {
320eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        this(context, attrs, R.attr.timePickerStyle);
321eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
322eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
323ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    public RadialTimePickerView(Context context, AttributeSet attrs, int defStyleAttr)  {
324ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        this(context, attrs, defStyleAttr, 0);
325ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
326ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
327ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    public RadialTimePickerView(
328ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)  {
329eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        super(context, attrs);
330eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
3312b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        applyAttributes(attrs, defStyleAttr, defStyleRes);
3322b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
3335134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette        // Pull disabled alpha from theme.
3345134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette        final TypedValue outValue = new TypedValue();
3355134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette        context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true);
336adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mDisabledAlpha = outValue.getFloat();
3375134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette
338eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mTypeface = Typeface.create("sans-serif", Typeface.NORMAL);
339eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
340eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[HOURS] = new Paint();
341eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[HOURS].setAntiAlias(true);
342eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[HOURS].setTextAlign(Paint.Align.CENTER);
343eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
344eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[MINUTES] = new Paint();
345eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[MINUTES].setAntiAlias(true);
346eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[MINUTES].setTextAlign(Paint.Align.CENTER);
347eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
348eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintCenter.setAntiAlias(true);
349ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
3502b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintSelector[SELECTOR_CIRCLE] = new Paint();
3512b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintSelector[SELECTOR_CIRCLE].setAntiAlias(true);
352eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
3532b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintSelector[SELECTOR_DOT] = new Paint();
3542b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintSelector[SELECTOR_DOT].setAntiAlias(true);
355eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
3562b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintSelector[SELECTOR_LINE] = new Paint();
3572b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintSelector[SELECTOR_LINE].setAntiAlias(true);
3582b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintSelector[SELECTOR_LINE].setStrokeWidth(2);
359eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
360eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintBackground.setAntiAlias(true);
361eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
3622b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final Resources res = getResources();
363adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mSelectorRadius = res.getDimensionPixelSize(R.dimen.timepicker_selector_radius);
36462c79e9a64c3b2cafd5500ed3064977dff7b7da3Alan Viverette        mSelectorStroke = res.getDimensionPixelSize(R.dimen.timepicker_selector_stroke);
365adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mSelectorDotRadius = res.getDimensionPixelSize(R.dimen.timepicker_selector_dot_radius);
366adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mCenterDotRadius = res.getDimensionPixelSize(R.dimen.timepicker_center_dot_radius);
367adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette
368adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mTextSize[HOURS] = res.getDimensionPixelSize(R.dimen.timepicker_text_size_normal);
369adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mTextSize[MINUTES] = res.getDimensionPixelSize(R.dimen.timepicker_text_size_normal);
370adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mTextSize[HOURS_INNER] = res.getDimensionPixelSize(R.dimen.timepicker_text_size_inner);
371adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette
372adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mTextInset[HOURS] = res.getDimensionPixelSize(R.dimen.timepicker_text_inset_normal);
373adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mTextInset[MINUTES] = res.getDimensionPixelSize(R.dimen.timepicker_text_inset_normal);
374adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mTextInset[HOURS_INNER] = res.getDimensionPixelSize(R.dimen.timepicker_text_inset_inner);
375eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
376eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mShowHours = true;
3772b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mHoursToMinutes = HOURS;
378eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mIs24HourMode = false;
379eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mAmOrPm = AM;
380eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
381ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        // Set up accessibility components.
382ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        mTouchHelper = new RadialPickerTouchHelper();
383ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setAccessibilityDelegate(mTouchHelper);
384ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
385ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
386ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
387ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
388ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
389eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        initHoursAndMinutesText();
390eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        initData();
391eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
392eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Initial values
393eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final Calendar calendar = Calendar.getInstance(Locale.getDefault());
394eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int currentHour = calendar.get(Calendar.HOUR_OF_DAY);
395eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int currentMinute = calendar.get(Calendar.MINUTE);
396eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
397ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentHourInternal(currentHour, false, false);
398ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentMinuteInternal(currentMinute, false);
399eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
400eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        setHapticFeedbackEnabled(true);
401eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
402eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
4032b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    void applyAttributes(AttributeSet attrs, int defStyleAttr, int defStyleRes) {
4042b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final Context context = getContext();
4052b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final TypedArray a = getContext().obtainStyledAttributes(attrs,
4062b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                R.styleable.TimePicker, defStyleAttr, defStyleRes);
4072b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
4082b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final ColorStateList numbersTextColor = a.getColorStateList(
4092b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                R.styleable.TimePicker_numbersTextColor);
4102b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final ColorStateList numbersInnerTextColor = a.getColorStateList(
4112b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                R.styleable.TimePicker_numbersInnerTextColor);
4122b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mTextColor[HOURS] = numbersTextColor == null ?
4132b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                ColorStateList.valueOf(MISSING_COLOR) : numbersTextColor;
4142b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mTextColor[HOURS_INNER] = numbersInnerTextColor == null ?
4152b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                ColorStateList.valueOf(MISSING_COLOR) : numbersInnerTextColor;
4162b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mTextColor[MINUTES] = mTextColor[HOURS];
4172b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
4182b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        // Set up various colors derived from the selector "activated" state.
4192b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final ColorStateList selectorColors = a.getColorStateList(
4202b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                R.styleable.TimePicker_numbersSelectorColor);
4212b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int selectorActivatedColor;
4222b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        if (selectorColors != null) {
4232b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            final int[] stateSetEnabledActivated = StateSet.get(
4242b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                    StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED);
4252b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            selectorActivatedColor = selectorColors.getColorForState(
4262b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                    stateSetEnabledActivated, 0);
4272b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        }  else {
4282b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            selectorActivatedColor = MISSING_COLOR;
4292b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        }
4302b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
4312b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintCenter.setColor(selectorActivatedColor);
4322b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
4332b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int[] stateSetActivated = StateSet.get(
4342b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED);
4352b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
4362b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mSelectorColor = selectorActivatedColor;
4372b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mSelectorDotColor = mTextColor[HOURS].getColorForState(stateSetActivated, 0);
4382b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
4392b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintBackground.setColor(a.getColor(R.styleable.TimePicker_numbersBackgroundColor,
4402b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                context.getColor(R.color.timepicker_default_numbers_background_color_material)));
4412b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
4422b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        a.recycle();
4432b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    }
4442b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
445eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void initialize(int hour, int minute, boolean is24HourMode) {
446448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette        if (mIs24HourMode != is24HourMode) {
447448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette            mIs24HourMode = is24HourMode;
448448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette            initData();
449448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette        }
450ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
451ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentHourInternal(hour, false, false);
452ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentMinuteInternal(minute, false);
453eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
454eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
455eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void setCurrentItemShowing(int item, boolean animate) {
456eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        switch (item){
457eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            case HOURS:
458eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                showHours(animate);
459eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                break;
460eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            case MINUTES:
461eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                showMinutes(animate);
462eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                break;
463eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            default:
464eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                Log.e(TAG, "ClockView does not support showing item " + item);
465eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
466eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
467eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
468eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public int getCurrentItemShowing() {
469eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        return mShowHours ? HOURS : MINUTES;
470eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
471eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
472eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void setOnValueSelectedListener(OnValueSelectedListener listener) {
473eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mListener = listener;
474eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
475eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
476d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette    /**
477d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * Sets the current hour in 24-hour time.
478d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     *
479d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * @param hour the current hour between 0 and 23 (inclusive)
480d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     */
481eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void setCurrentHour(int hour) {
482ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentHourInternal(hour, true, false);
483ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
484ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
485ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    /**
486ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     * Sets the current hour.
487ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     *
488ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     * @param hour The current hour
489ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     * @param callback Whether the value listener should be invoked
490ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     * @param autoAdvance Whether the listener should auto-advance to the next
491ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     *                    selection mode, e.g. hour to minutes
492ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     */
493ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private void setCurrentHourInternal(int hour, boolean callback, boolean autoAdvance) {
494eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int degrees = (hour % 12) * DEGREES_FOR_ONE_HOUR;
495eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mSelectionDegrees[HOURS] = degrees;
496d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette
497d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette        // 0 is 12 AM (midnight) and 12 is 12 PM (noon).
498ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        final int amOrPm = (hour == 0 || (hour % 24) < 12) ? AM : PM;
49988e51032320ab92057666e1230cc6548ca163c51Alan Viverette        final boolean isOnInnerCircle = getInnerCircleForHour(hour);
500ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (mAmOrPm != amOrPm || mIsOnInnerCircle != isOnInnerCircle) {
501ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            mAmOrPm = amOrPm;
502ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            mIsOnInnerCircle = isOnInnerCircle;
503ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
504ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            initData();
505ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            mTouchHelper.invalidateRoot();
506ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
507d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette
508eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        invalidate();
509ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
510ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (callback && mListener != null) {
511ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            mListener.onValueSelected(HOURS, hour, autoAdvance);
512ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
513eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
514eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
515d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette    /**
516d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * Returns the current hour in 24-hour time.
517d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     *
518d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * @return the current hour between 0 and 23 (inclusive)
519d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     */
520eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public int getCurrentHour() {
52188e51032320ab92057666e1230cc6548ca163c51Alan Viverette        return getHourForDegrees(mSelectionDegrees[HOURS], mIsOnInnerCircle);
522ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
523ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
524ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private int getHourForDegrees(int degrees, boolean innerCircle) {
525ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        int hour = (degrees / DEGREES_FOR_ONE_HOUR) % 12;
526eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mIs24HourMode) {
527d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette            // Convert the 12-hour value into 24-hour time based on where the
528d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette            // selector is positioned.
52988e51032320ab92057666e1230cc6548ca163c51Alan Viverette            if (!innerCircle && hour == 0) {
53088e51032320ab92057666e1230cc6548ca163c51Alan Viverette                // Outer circle is 1 through 12.
531d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette                hour = 12;
53288e51032320ab92057666e1230cc6548ca163c51Alan Viverette            } else if (innerCircle && hour != 0) {
53388e51032320ab92057666e1230cc6548ca163c51Alan Viverette                // Inner circle is 13 through 23 and 0.
534d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette                hour += 12;
535eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
536d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette        } else if (mAmOrPm == PM) {
537d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette            hour += 12;
538eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
539d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette        return hour;
540eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
541eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
54288e51032320ab92057666e1230cc6548ca163c51Alan Viverette    /**
54388e51032320ab92057666e1230cc6548ca163c51Alan Viverette     * @param hour the hour in 24-hour time or 12-hour time
54488e51032320ab92057666e1230cc6548ca163c51Alan Viverette     */
545ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private int getDegreesForHour(int hour) {
546ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        // Convert to be 0-11.
547ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (mIs24HourMode) {
548ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (hour >= 12) {
549ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                hour -= 12;
550ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
551ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        } else if (hour == 12) {
552ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            hour = 0;
553ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
554ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return hour * DEGREES_FOR_ONE_HOUR;
555ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
556ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
55788e51032320ab92057666e1230cc6548ca163c51Alan Viverette    /**
55888e51032320ab92057666e1230cc6548ca163c51Alan Viverette     * @param hour the hour in 24-hour time or 12-hour time
55988e51032320ab92057666e1230cc6548ca163c51Alan Viverette     */
56088e51032320ab92057666e1230cc6548ca163c51Alan Viverette    private boolean getInnerCircleForHour(int hour) {
56188e51032320ab92057666e1230cc6548ca163c51Alan Viverette        return mIs24HourMode && (hour == 0 || hour > 12);
56288e51032320ab92057666e1230cc6548ca163c51Alan Viverette    }
56388e51032320ab92057666e1230cc6548ca163c51Alan Viverette
564eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void setCurrentMinute(int minute) {
565ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentMinuteInternal(minute, true);
566ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
567ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
568ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private void setCurrentMinuteInternal(int minute, boolean callback) {
569fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette        mSelectionDegrees[MINUTES] = (minute % MINUTES_IN_CIRCLE) * DEGREES_FOR_ONE_MINUTE;
570ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
571eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        invalidate();
572ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
573ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (callback && mListener != null) {
574ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            mListener.onValueSelected(MINUTES, minute, false);
575ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
576eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
577eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
578eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    // Returns minutes in 0-59 range
579eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public int getCurrentMinute() {
580ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return getMinuteForDegrees(mSelectionDegrees[MINUTES]);
581ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
582ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
583ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private int getMinuteForDegrees(int degrees) {
584ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return degrees / DEGREES_FOR_ONE_MINUTE;
585ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
586ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
587ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private int getDegreesForMinute(int minute) {
588ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return minute * DEGREES_FOR_ONE_MINUTE;
589eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
590eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
59130b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette    /**
59230b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette     * Sets whether the picker is showing AM or PM hours. Has no effect when
59330b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette     * in 24-hour mode.
59430b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette     *
59530b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette     * @param amOrPm {@link #AM} or {@link #PM}
59630b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette     * @return {@code true} if the value changed from what was previously set,
59730b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette     *         or {@code false} otherwise
59830b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette     */
59930b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette    public boolean setAmOrPm(int amOrPm) {
60030b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette        if (mAmOrPm == amOrPm || mIs24HourMode) {
60130b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette            return false;
60230b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette        }
60330b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette
60430b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette        mAmOrPm = amOrPm;
605eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        invalidate();
606ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        mTouchHelper.invalidateRoot();
60730b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette        return true;
608eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
609eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
610eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public int getAmOrPm() {
611eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        return mAmOrPm;
612eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
613eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
614eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void showHours(boolean animate) {
6152b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        showPicker(true, animate);
616eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
617eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
618eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void showMinutes(boolean animate) {
6192b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        showPicker(false, animate);
620eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
621eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
622eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void initHoursAndMinutesText() {
623eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Initialize the hours and minutes numbers.
624eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        for (int i = 0; i < 12; i++) {
625eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mHours12Texts[i] = String.format("%d", HOURS_NUMBERS[i]);
626f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette            mInnerHours24Texts[i] = String.format("%02d", HOURS_NUMBERS_24[i]);
627f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette            mOuterHours24Texts[i] = String.format("%d", HOURS_NUMBERS[i]);
628eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mMinutesTexts[i] = String.format("%02d", MINUTES_NUMBERS[i]);
629eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
630eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
631eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
632eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void initData() {
633eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mIs24HourMode) {
634eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mOuterTextHours = mOuterHours24Texts;
635eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mInnerTextHours = mInnerHours24Texts;
636eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        } else {
637eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mOuterTextHours = mHours12Texts;
638f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette            mInnerTextHours = mHours12Texts;
639eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
640eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
64188e51032320ab92057666e1230cc6548ca163c51Alan Viverette        mMinutesText = mMinutesTexts;
642eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
643eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
644eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    @Override
645eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
646ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        if (!changed) {
647ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            return;
648ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        }
649eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
650eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mXCenter = getWidth() / 2;
651eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mYCenter = getHeight() / 2;
652ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mCircleRadius = Math.min(mXCenter, mYCenter);
653eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
65488e51032320ab92057666e1230cc6548ca163c51Alan Viverette        mMinDistForInnerNumber = mCircleRadius - mTextInset[HOURS_INNER] - mSelectorRadius;
65588e51032320ab92057666e1230cc6548ca163c51Alan Viverette        mMaxDistForOuterNumber = mCircleRadius - mTextInset[HOURS] + mSelectorRadius;
65688e51032320ab92057666e1230cc6548ca163c51Alan Viverette        mHalfwayDist = mCircleRadius - (mTextInset[HOURS] + mTextInset[HOURS_INNER]) / 2;
657eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
658ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        calculatePositionsHours();
659ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        calculatePositionsMinutes();
660eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
661ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        mTouchHelper.invalidateRoot();
662eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
663eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
664eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    @Override
665eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void onDraw(Canvas canvas) {
666adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        final float alphaMod = mInputEnabled ? 1 : mDisabledAlpha;
667eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
668eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        drawCircleBackground(canvas);
6692b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
6702b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final Path selectorPath = mSelectorPath;
6712b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        drawSelector(canvas, selectorPath);
6722b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        drawHours(canvas, selectorPath, alphaMod);
6732b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        drawMinutes(canvas, selectorPath, alphaMod);
674adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        drawCenter(canvas, alphaMod);
675adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    }
676eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
6772b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private void showPicker(boolean hours, boolean animate) {
6782b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        if (mShowHours == hours) {
6792b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            return;
6802b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        }
6812b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
6822b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mShowHours = hours;
6832b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
6842b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        if (animate) {
6852b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            animatePicker(hours, ANIM_DURATION_NORMAL);
68632f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette        } else {
68732f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette            // If we have a pending or running animator, cancel it.
68832f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette            if (mHoursToMinutesAnimator != null && mHoursToMinutesAnimator.isStarted()) {
68932f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette                mHoursToMinutesAnimator.cancel();
69032f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette                mHoursToMinutesAnimator = null;
69132f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette            }
69232f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette            mHoursToMinutes = hours ? 0.0f : 1.0f;
6932b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        }
6942b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
6952b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        initData();
6962b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        invalidate();
6972b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mTouchHelper.invalidateRoot();
6982b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    }
6992b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
7002b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private void animatePicker(boolean hoursToMinutes, long duration) {
7012b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final float target = hoursToMinutes ? HOURS : MINUTES;
7022b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        if (mHoursToMinutes == target) {
7032b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            // If we have a pending or running animator, cancel it.
7042b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            if (mHoursToMinutesAnimator != null && mHoursToMinutesAnimator.isStarted()) {
7052b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                mHoursToMinutesAnimator.cancel();
7062b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                mHoursToMinutesAnimator = null;
7072b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            }
7082b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
7092b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            // We're already showing the correct picker.
7102b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            return;
7112b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        }
7122b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
7132b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mHoursToMinutesAnimator = ObjectAnimator.ofFloat(this, HOURS_TO_MINUTES, target);
7142b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mHoursToMinutesAnimator.setAutoCancel(true);
7152b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mHoursToMinutesAnimator.setDuration(duration);
7162b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mHoursToMinutesAnimator.start();
7172b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    }
7182b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
719adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private void drawCircleBackground(Canvas canvas) {
720adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        canvas.drawCircle(mXCenter, mYCenter, mCircleRadius, mPaintBackground);
721adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    }
722adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette
7232b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private void drawHours(Canvas canvas, Path selectorPath, float alphaMod) {
7242b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int hoursAlpha = (int) (255f * (1f - mHoursToMinutes) * alphaMod + 0.5f);
725ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        if (hoursAlpha > 0) {
7262b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            // Exclude the selector region, then draw inner/outer hours with no
7272b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            // activated states.
7282b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.save(Canvas.CLIP_SAVE_FLAG);
7292b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.clipPath(selectorPath, Region.Op.DIFFERENCE);
7302b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            drawHoursClipped(canvas, hoursAlpha, false);
7312b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.restore();
7322b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
7332b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            // Intersect the selector region, then draw minutes with only
7342b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            // activated states.
7352b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.save(Canvas.CLIP_SAVE_FLAG);
7362b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.clipPath(selectorPath, Region.Op.INTERSECT);
7372b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            drawHoursClipped(canvas, hoursAlpha, true);
7382b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.restore();
739eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
740adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    }
741eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
7422b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private void drawHoursClipped(Canvas canvas, int hoursAlpha, boolean showActivated) {
7432b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        // Draw outer hours.
7442b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        drawTextElements(canvas, mTextSize[HOURS], mTypeface, mTextColor[HOURS], mOuterTextHours,
7452b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                mOuterTextX[HOURS], mOuterTextY[HOURS], mPaint[HOURS], hoursAlpha,
7462b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                showActivated && !mIsOnInnerCircle, mSelectionDegrees[HOURS], showActivated);
747ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
7482b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        // Draw inner hours (13-00) for 24-hour time.
7492b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        if (mIs24HourMode && mInnerTextHours != null) {
7502b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            drawTextElements(canvas, mTextSize[HOURS_INNER], mTypeface, mTextColor[HOURS_INNER],
7512b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                    mInnerTextHours, mInnerTextX, mInnerTextY, mPaint[HOURS], hoursAlpha,
7522b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                    showActivated && mIsOnInnerCircle, mSelectionDegrees[HOURS], showActivated);
7532b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        }
7542b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    }
7552b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
7562b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private void drawMinutes(Canvas canvas, Path selectorPath, float alphaMod) {
7572b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int minutesAlpha = (int) (255f * mHoursToMinutes * alphaMod + 0.5f);
7582b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        if (minutesAlpha > 0) {
759ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            // Exclude the selector region, then draw minutes with no
760ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            // activated states.
761ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            canvas.save(Canvas.CLIP_SAVE_FLAG);
7622b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.clipPath(selectorPath, Region.Op.DIFFERENCE);
7632b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            drawMinutesClipped(canvas, minutesAlpha, false);
764ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            canvas.restore();
765ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
766ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            // Intersect the selector region, then draw minutes with only
767ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            // activated states.
768ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            canvas.save(Canvas.CLIP_SAVE_FLAG);
7692b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.clipPath(selectorPath, Region.Op.INTERSECT);
7702b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            drawMinutesClipped(canvas, minutesAlpha, true);
771ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            canvas.restore();
772ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        }
773eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
774eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
7752b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private void drawMinutesClipped(Canvas canvas, int minutesAlpha, boolean showActivated) {
7762b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mTextColor[MINUTES], mMinutesText,
7772b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES], minutesAlpha,
7782b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                showActivated, mSelectionDegrees[MINUTES], showActivated);
7792b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    }
7802b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
781adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private void drawCenter(Canvas canvas, float alphaMod) {
782adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mPaintCenter.setAlpha((int) (255 * alphaMod + 0.5f));
783adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        canvas.drawCircle(mXCenter, mYCenter, mCenterDotRadius, mPaintCenter);
784eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
785eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
786eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette    private int getMultipliedAlpha(int argb, int alpha) {
787eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        return (int) (Color.alpha(argb) * (alpha / 255.0) + 0.5);
788eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette    }
789eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
7902b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private void drawSelector(Canvas canvas, Path selectorPath) {
7912b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        // Determine the current length, angle, and dot scaling factor.
7922b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int hoursIndex = mIsOnInnerCircle ? HOURS_INNER : HOURS;
7932b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int hoursInset = mTextInset[hoursIndex];
7942b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int hoursAngleDeg = mSelectionDegrees[hoursIndex % 2];
7952b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final float hoursDotScale = mSelectionDegrees[hoursIndex % 2] % 30 != 0 ? 1 : 0;
7962b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
7972b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int minutesIndex = MINUTES;
7982b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int minutesInset = mTextInset[minutesIndex];
7992b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int minutesAngleDeg = mSelectionDegrees[minutesIndex];
8002b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final float minutesDotScale = mSelectionDegrees[minutesIndex] % 30 != 0 ? 1 : 0;
801eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
802f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        // Calculate the current radius at which to place the selection circle.
803f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        final int selRadius = mSelectorRadius;
8042b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final float selLength =
8052b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                mCircleRadius - MathUtils.lerp(hoursInset, minutesInset, mHoursToMinutes);
8062b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final double selAngleRad =
8072b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                Math.toRadians(MathUtils.lerpDeg(hoursAngleDeg, minutesAngleDeg, mHoursToMinutes));
808f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        final float selCenterX = mXCenter + selLength * (float) Math.sin(selAngleRad);
809f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        final float selCenterY = mYCenter - selLength * (float) Math.cos(selAngleRad);
810f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette
811f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        // Draw the selection circle.
8122b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final Paint paint = mPaintSelector[SELECTOR_CIRCLE];
8132b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        paint.setColor(mSelectorColor);
814f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        canvas.drawCircle(selCenterX, selCenterY, selRadius, paint);
815eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
816ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        // If needed, set up the clip path for later.
817ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        if (selectorPath != null) {
818f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette            selectorPath.reset();
819f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette            selectorPath.addCircle(selCenterX, selCenterY, selRadius, Path.Direction.CCW);
820ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        }
821ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
822f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        // Draw the dot if we're between two items.
8232b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final float dotScale = MathUtils.lerp(hoursDotScale, minutesDotScale, mHoursToMinutes);
8242b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        if (dotScale > 0) {
8252b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            final Paint dotPaint = mPaintSelector[SELECTOR_DOT];
82688e51032320ab92057666e1230cc6548ca163c51Alan Viverette            dotPaint.setColor(mSelectorDotColor);
8272b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.drawCircle(selCenterX, selCenterY, mSelectorDotRadius * dotScale, dotPaint);
828eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
829eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
830adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        // Shorten the line to only go from the edge of the center dot to the
831adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        // edge of the selection circle.
832f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        final double sin = Math.sin(selAngleRad);
833f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        final double cos = Math.cos(selAngleRad);
8342b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final float lineLength = selLength - selRadius;
835adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        final int centerX = mXCenter + (int) (mCenterDotRadius * sin);
836adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        final int centerY = mYCenter - (int) (mCenterDotRadius * cos);
837f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        final float linePointX = centerX + (int) (lineLength * sin);
838f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        final float linePointY = centerY - (int) (lineLength * cos);
839ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
840f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        // Draw the line.
8412b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final Paint linePaint = mPaintSelector[SELECTOR_LINE];
8422b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        linePaint.setColor(mSelectorColor);
843f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        linePaint.setStrokeWidth(mSelectorStroke);
844f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        canvas.drawLine(mXCenter, mYCenter, linePointX, linePointY, linePaint);
845eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
846eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
847ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private void calculatePositionsHours() {
848eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Calculate the text positions
849adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        final float numbersRadius = mCircleRadius - mTextInset[HOURS];
850eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
851eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Calculate the positions for the 12 numbers in the main circle.
852ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        calculatePositions(mPaint[HOURS], numbersRadius, mXCenter, mYCenter,
853ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                mTextSize[HOURS], mOuterTextX[HOURS], mOuterTextY[HOURS]);
854eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
855eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // If we have an inner circle, calculate those positions too.
856eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mIs24HourMode) {
857adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette            final int innerNumbersRadius = mCircleRadius - mTextInset[HOURS_INNER];
858ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            calculatePositions(mPaint[HOURS], innerNumbersRadius, mXCenter, mYCenter,
859ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    mTextSize[HOURS_INNER], mInnerTextX, mInnerTextY);
860eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
861eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
862eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
863ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private void calculatePositionsMinutes() {
864eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Calculate the text positions
865adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        final float numbersRadius = mCircleRadius - mTextInset[MINUTES];
866eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
867eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Calculate the positions for the 12 numbers in the main circle.
868ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        calculatePositions(mPaint[MINUTES], numbersRadius, mXCenter, mYCenter,
869ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                mTextSize[MINUTES], mOuterTextX[MINUTES], mOuterTextY[MINUTES]);
870eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
871eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
872eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    /**
873eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * Using the trigonometric Unit Circle, calculate the positions that the text will need to be
874eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * drawn at based on the specified circle radius. Place the values in the textGridHeights and
875eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * textGridWidths parameters.
876eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     */
877ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private static void calculatePositions(Paint paint, float radius, float xCenter, float yCenter,
878ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            float textSize, float[] x, float[] y) {
879ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        // Adjust yCenter to account for the text's baseline.
880eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        paint.setTextSize(textSize);
881eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        yCenter -= (paint.descent() + paint.ascent()) / 2;
882eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
883ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        for (int i = 0; i < NUM_POSITIONS; i++) {
884ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            x[i] = xCenter - radius * COS_30[i];
885ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            y[i] = yCenter - radius * SIN_30[i];
886ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        }
887eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
888eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
889eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    /**
890eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * Draw the 12 text values at the positions specified by the textGrid parameters.
891eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     */
892adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private void drawTextElements(Canvas canvas, float textSize, Typeface typeface,
893adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette            ColorStateList textColor, String[] texts, float[] textX, float[] textY, Paint paint,
894adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette            int alpha, boolean showActivated, int activatedDegrees, boolean activatedOnly) {
895eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        paint.setTextSize(textSize);
896eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        paint.setTypeface(typeface);
897eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
898ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        // The activated index can touch a range of elements.
899ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final float activatedIndex = activatedDegrees / (360.0f / NUM_POSITIONS);
900ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final int activatedFloor = (int) activatedIndex;
901ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final int activatedCeil = ((int) Math.ceil(activatedIndex)) % NUM_POSITIONS;
902eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
903ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        for (int i = 0; i < 12; i++) {
904ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            final boolean activated = (activatedFloor == i || activatedCeil == i);
905ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            if (activatedOnly && !activated) {
906ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                continue;
907ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            }
908eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
909ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            final int stateMask = StateSet.VIEW_STATE_ENABLED
910ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    | (showActivated && activated ? StateSet.VIEW_STATE_ACTIVATED : 0);
911adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette            final int color = textColor.getColorForState(StateSet.get(stateMask), 0);
912ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            paint.setColor(color);
913ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            paint.setAlpha(getMultipliedAlpha(color, alpha));
914eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
915ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            canvas.drawText(texts[i], textX[i], textY[i], paint);
916ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        }
917eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
918eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
919ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private int getDegreesFromXY(float x, float y, boolean constrainOutside) {
92088e51032320ab92057666e1230cc6548ca163c51Alan Viverette        // Ensure the point is inside the touchable area.
92188e51032320ab92057666e1230cc6548ca163c51Alan Viverette        final int innerBound;
92288e51032320ab92057666e1230cc6548ca163c51Alan Viverette        final int outerBound;
92388e51032320ab92057666e1230cc6548ca163c51Alan Viverette        if (mIs24HourMode && mShowHours) {
92488e51032320ab92057666e1230cc6548ca163c51Alan Viverette            innerBound = mMinDistForInnerNumber;
92588e51032320ab92057666e1230cc6548ca163c51Alan Viverette            outerBound = mMaxDistForOuterNumber;
92688e51032320ab92057666e1230cc6548ca163c51Alan Viverette        } else {
92788e51032320ab92057666e1230cc6548ca163c51Alan Viverette            final int index = mShowHours ? HOURS : MINUTES;
92888e51032320ab92057666e1230cc6548ca163c51Alan Viverette            final int center = mCircleRadius - mTextInset[index];
92988e51032320ab92057666e1230cc6548ca163c51Alan Viverette            innerBound = center - mSelectorRadius;
93088e51032320ab92057666e1230cc6548ca163c51Alan Viverette            outerBound = center + mSelectorRadius;
93188e51032320ab92057666e1230cc6548ca163c51Alan Viverette        }
932eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
93388e51032320ab92057666e1230cc6548ca163c51Alan Viverette        final double dX = x - mXCenter;
93488e51032320ab92057666e1230cc6548ca163c51Alan Viverette        final double dY = y - mYCenter;
93588e51032320ab92057666e1230cc6548ca163c51Alan Viverette        final double distFromCenter = Math.sqrt(dX * dX + dY * dY);
93688e51032320ab92057666e1230cc6548ca163c51Alan Viverette        if (distFromCenter < innerBound || constrainOutside && distFromCenter > outerBound) {
937eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            return -1;
938eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
939ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
94088e51032320ab92057666e1230cc6548ca163c51Alan Viverette        // Convert to degrees.
94188e51032320ab92057666e1230cc6548ca163c51Alan Viverette        final int degrees = (int) (Math.toDegrees(Math.atan2(dY, dX) + Math.PI / 2) + 0.5);
94288e51032320ab92057666e1230cc6548ca163c51Alan Viverette        if (degrees < 0) {
94388e51032320ab92057666e1230cc6548ca163c51Alan Viverette            return degrees + 360;
944eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        } else {
94588e51032320ab92057666e1230cc6548ca163c51Alan Viverette            return degrees;
946eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
94788e51032320ab92057666e1230cc6548ca163c51Alan Viverette    }
948eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
94988e51032320ab92057666e1230cc6548ca163c51Alan Viverette    private boolean getInnerCircleFromXY(float x, float y) {
95088e51032320ab92057666e1230cc6548ca163c51Alan Viverette        if (mIs24HourMode && mShowHours) {
95188e51032320ab92057666e1230cc6548ca163c51Alan Viverette            final double dX = x - mXCenter;
95288e51032320ab92057666e1230cc6548ca163c51Alan Viverette            final double dY = y - mYCenter;
95388e51032320ab92057666e1230cc6548ca163c51Alan Viverette            final double distFromCenter = Math.sqrt(dX * dX + dY * dY);
95488e51032320ab92057666e1230cc6548ca163c51Alan Viverette            return distFromCenter <= mHalfwayDist;
955eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
95688e51032320ab92057666e1230cc6548ca163c51Alan Viverette        return false;
957eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
958eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
959002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette    boolean mChangedDuringTouch = false;
960002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
961eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    @Override
962ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    public boolean onTouchEvent(MotionEvent event) {
963002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        if (!mInputEnabled) {
964eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            return true;
965eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
966eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
967002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        final int action = event.getActionMasked();
968002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        if (action == MotionEvent.ACTION_MOVE
969002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                || action == MotionEvent.ACTION_UP
970002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                || action == MotionEvent.ACTION_DOWN) {
971002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            boolean forceSelection = false;
972002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            boolean autoAdvance = false;
973002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
974002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            if (action == MotionEvent.ACTION_DOWN) {
975002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                // This is a new event stream, reset whether the value changed.
976002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                mChangedDuringTouch = false;
977002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            } else if (action == MotionEvent.ACTION_UP) {
978002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                autoAdvance = true;
979002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
980002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                // If we saw a down/up pair without the value changing, assume
981002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                // this is a single-tap selection and force a change.
982002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                if (!mChangedDuringTouch) {
983002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                    forceSelection = true;
984eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                }
985002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            }
986eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
987002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            mChangedDuringTouch |= handleTouchInput(
988002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                    event.getX(), event.getY(), forceSelection, autoAdvance);
989eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
990002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
991002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        return true;
992002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette    }
993002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
994002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette    private boolean handleTouchInput(
995002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            float x, float y, boolean forceSelection, boolean autoAdvance) {
99688e51032320ab92057666e1230cc6548ca163c51Alan Viverette        final boolean isOnInnerCircle = getInnerCircleFromXY(x, y);
997ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final int degrees = getDegreesFromXY(x, y, false);
998002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        if (degrees == -1) {
999002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            return false;
1000002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        }
1001002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
10022b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        // Ensure we're showing the correct picker.
10032b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        animatePicker(mShowHours, ANIM_DURATION_TOUCH);
10042b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
100566a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette        final @PickerType int type;
100611a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette        final int newValue;
100711a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette        final boolean valueChanged;
1008002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
1009002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        if (mShowHours) {
1010002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            final int snapDegrees = snapOnly30s(degrees, 0) % 360;
101188e51032320ab92057666e1230cc6548ca163c51Alan Viverette            valueChanged = mIsOnInnerCircle != isOnInnerCircle
101288e51032320ab92057666e1230cc6548ca163c51Alan Viverette                    || mSelectionDegrees[HOURS] != snapDegrees;
101388e51032320ab92057666e1230cc6548ca163c51Alan Viverette            mIsOnInnerCircle = isOnInnerCircle;
101488e51032320ab92057666e1230cc6548ca163c51Alan Viverette            mSelectionDegrees[HOURS] = snapDegrees;
101511a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            type = HOURS;
101611a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            newValue = getCurrentHour();
1017002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        } else {
1018002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            final int snapDegrees = snapPrefer30s(degrees) % 360;
101988e51032320ab92057666e1230cc6548ca163c51Alan Viverette            valueChanged = mSelectionDegrees[MINUTES] != snapDegrees;
102088e51032320ab92057666e1230cc6548ca163c51Alan Viverette            mSelectionDegrees[MINUTES] = snapDegrees;
102111a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            type = MINUTES;
102211a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            newValue = getCurrentMinute();
1023002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        }
1024002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
102511a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette        if (valueChanged || forceSelection || autoAdvance) {
102611a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            // Fire the listener even if we just need to auto-advance.
1027002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            if (mListener != null) {
1028002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                mListener.onValueSelected(type, newValue, autoAdvance);
1029002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            }
103011a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette
103111a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            // Only provide feedback if the value actually changed.
103211a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            if (valueChanged || forceSelection) {
103311a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette                performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
103411a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette                invalidate();
103511a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            }
1036002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            return true;
1037002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        }
1038002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
1039002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        return false;
1040eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
1041eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1042eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    @Override
1043ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    public boolean dispatchHoverEvent(MotionEvent event) {
1044ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        // First right-of-refusal goes the touch exploration helper.
1045ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (mTouchHelper.dispatchHoverEvent(event)) {
1046eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            return true;
1047eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1048ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return super.dispatchHoverEvent(event);
1049eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
1050eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1051ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    public void setInputEnabled(boolean inputEnabled) {
1052ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        mInputEnabled = inputEnabled;
1053ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        invalidate();
1054ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
1055ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
105647f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev    @Override
105747f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev    public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
105847f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev        if (!isEnabled()) {
105947f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev            return null;
106047f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev        }
106147f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev        final int degrees = getDegreesFromXY(event.getX(), event.getY(), false);
106247f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev        if (degrees != -1) {
106347f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev            return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND);
106447f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev        }
106547f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev        return super.onResolvePointerIcon(event, pointerIndex);
106647f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev    }
106747f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev
1068ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private class RadialPickerTouchHelper extends ExploreByTouchHelper {
1069ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final Rect mTempRect = new Rect();
1070ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1071ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int TYPE_HOUR = 1;
1072ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int TYPE_MINUTE = 2;
1073ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1074ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int SHIFT_TYPE = 0;
1075ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int MASK_TYPE = 0xF;
1076ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1077ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int SHIFT_VALUE = 8;
1078ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int MASK_VALUE = 0xFF;
1079ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1080ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        /** Increment in which virtual views are exposed for minutes. */
1081ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int MINUTE_INCREMENT = 5;
1082ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1083ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        public RadialPickerTouchHelper() {
1084ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            super(RadialTimePickerView.this);
1085eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1086eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1087ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1088ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
1089ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            super.onInitializeAccessibilityNodeInfo(host, info);
1090ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1091ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
1092ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
1093eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1094ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1095ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1096ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
1097ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (super.performAccessibilityAction(host, action, arguments)) {
1098ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                return true;
1099eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
1100eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1101ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            switch (action) {
1102ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
1103ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    adjustPicker(1);
1104ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    return true;
1105ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
1106ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    adjustPicker(-1);
1107ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    return true;
1108ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1109ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1110ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return false;
1111ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1112ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1113ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private void adjustPicker(int step) {
1114ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int stepSize;
11154a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette            final int initialStep;
111667945c11a5e9547f71be91ceb99e7b9ff15a6292Alan Viverette            final int maxValue;
1117ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int minValue;
1118eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if (mShowHours) {
11194a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                stepSize = 1;
1120ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
11214a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                final int currentHour24 = getCurrentHour();
1122eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                if (mIs24HourMode) {
11234a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                    initialStep = currentHour24;
1124ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    minValue = 0;
11254a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                    maxValue = 23;
1126eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                } else {
11274a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                    initialStep = hour24To12(currentHour24);
1128eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    minValue = 1;
11294a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                    maxValue = 12;
1130eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                }
1131eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            } else {
11324a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                stepSize = 5;
11334a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                initialStep = getCurrentMinute() / stepSize;
1134ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                minValue = 0;
11354a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                maxValue = 55;
1136eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
1137ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
11384a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette            final int nextValue = (initialStep + step) * stepSize;
11394a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette            final int clampedValue = MathUtils.constrain(nextValue, minValue, maxValue);
1140ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (mShowHours) {
1141ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                setCurrentHour(clampedValue);
1142ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1143ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                setCurrentMinute(clampedValue);
1144ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1145ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1146ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1147ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1148ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected int getVirtualViewAt(float x, float y) {
1149ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int id;
1150d8c2af55420d869c140278e6643d16cd90cfeb51Alan Viverette            final int degrees = getDegreesFromXY(x, y, true);
1151ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (degrees != -1) {
1152ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int snapDegrees = snapOnly30s(degrees, 0) % 360;
1153ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                if (mShowHours) {
115488e51032320ab92057666e1230cc6548ca163c51Alan Viverette                    final boolean isOnInnerCircle = getInnerCircleFromXY(x, y);
11555efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette                    final int hour24 = getHourForDegrees(snapDegrees, isOnInnerCircle);
11565efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette                    final int hour = mIs24HourMode ? hour24 : hour24To12(hour24);
1157ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    id = makeId(TYPE_HOUR, hour);
1158ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                } else {
1159ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    final int current = getCurrentMinute();
1160ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    final int touched = getMinuteForDegrees(degrees);
1161ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    final int snapped = getMinuteForDegrees(snapDegrees);
1162ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1163ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    // If the touched minute is closer to the current minute
1164ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    // than it is to the snapped minute, return current.
1165fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette                    final int currentOffset = getCircularDiff(current, touched, MINUTES_IN_CIRCLE);
1166fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette                    final int snappedOffset = getCircularDiff(snapped, touched, MINUTES_IN_CIRCLE);
1167ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    final int minute;
116888e51032320ab92057666e1230cc6548ca163c51Alan Viverette                    if (currentOffset < snappedOffset) {
1169ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                        minute = current;
1170ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    } else {
1171ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                        minute = snapped;
1172ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    }
1173ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    id = makeId(TYPE_MINUTE, minute);
1174ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                }
1175ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1176ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                id = INVALID_ID;
1177eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
1178ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1179ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return id;
1180ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1181ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
118288e51032320ab92057666e1230cc6548ca163c51Alan Viverette        /**
118388e51032320ab92057666e1230cc6548ca163c51Alan Viverette         * Returns the difference in degrees between two values along a circle.
118488e51032320ab92057666e1230cc6548ca163c51Alan Viverette         *
118588e51032320ab92057666e1230cc6548ca163c51Alan Viverette         * @param first value in the range [0,max]
118688e51032320ab92057666e1230cc6548ca163c51Alan Viverette         * @param second value in the range [0,max]
118788e51032320ab92057666e1230cc6548ca163c51Alan Viverette         * @param max the maximum value along the circle
118888e51032320ab92057666e1230cc6548ca163c51Alan Viverette         * @return the difference in between the two values
118988e51032320ab92057666e1230cc6548ca163c51Alan Viverette         */
119088e51032320ab92057666e1230cc6548ca163c51Alan Viverette        private int getCircularDiff(int first, int second, int max) {
119188e51032320ab92057666e1230cc6548ca163c51Alan Viverette            final int diff = Math.abs(first - second);
119288e51032320ab92057666e1230cc6548ca163c51Alan Viverette            final int midpoint = max / 2;
119388e51032320ab92057666e1230cc6548ca163c51Alan Viverette            return (diff > midpoint) ? (max - diff) : diff;
119488e51032320ab92057666e1230cc6548ca163c51Alan Viverette        }
119588e51032320ab92057666e1230cc6548ca163c51Alan Viverette
1196ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1197ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected void getVisibleVirtualViews(IntArray virtualViewIds) {
1198eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if (mShowHours) {
1199ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int min = mIs24HourMode ? 0 : 1;
1200ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int max = mIs24HourMode ? 23 : 12;
1201ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                for (int i = min; i <= max ; i++) {
1202ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    virtualViewIds.add(makeId(TYPE_HOUR, i));
1203eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                }
1204eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            } else {
1205ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int current = getCurrentMinute();
1206fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette                for (int i = 0; i < MINUTES_IN_CIRCLE; i += MINUTE_INCREMENT) {
1207ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    virtualViewIds.add(makeId(TYPE_MINUTE, i));
1208ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1209ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    // If the current minute falls between two increments,
1210ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    // insert an extra node for it.
1211ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    if (current > i && current < i + MINUTE_INCREMENT) {
1212ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                        virtualViewIds.add(makeId(TYPE_MINUTE, current));
1213ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    }
1214eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                }
1215eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
1216eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1217eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1218ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1219ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
1220ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            event.setClassName(getClass().getName());
1221eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1222ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int type = getTypeFromId(virtualViewId);
1223ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int value = getValueFromId(virtualViewId);
1224ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final CharSequence description = getVirtualViewDescription(type, value);
1225ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            event.setContentDescription(description);
1226ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1227ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1228ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1229ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) {
1230ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            node.setClassName(getClass().getName());
1231ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            node.addAction(AccessibilityAction.ACTION_CLICK);
1232ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1233ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int type = getTypeFromId(virtualViewId);
1234ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int value = getValueFromId(virtualViewId);
1235ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final CharSequence description = getVirtualViewDescription(type, value);
1236ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            node.setContentDescription(description);
1237ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1238ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            getBoundsForVirtualView(virtualViewId, mTempRect);
1239ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            node.setBoundsInParent(mTempRect);
1240ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1241ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final boolean selected = isVirtualViewSelected(type, value);
1242ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            node.setSelected(selected);
12433fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette
12443fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            final int nextId = getVirtualViewIdAfter(type, value);
12453fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            if (nextId != INVALID_ID) {
12463fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                node.setTraversalBefore(RadialTimePickerView.this, nextId);
12473fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            }
12483fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette        }
12493fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette
12503fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette        private int getVirtualViewIdAfter(int type, int value) {
12513fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            if (type == TYPE_HOUR) {
12523fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                final int nextValue = value + 1;
12533fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                final int max = mIs24HourMode ? 23 : 12;
12543fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                if (nextValue <= max) {
12553fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                    return makeId(type, nextValue);
12563fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                }
12573fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            } else if (type == TYPE_MINUTE) {
12583fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                final int current = getCurrentMinute();
12593fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                final int snapValue = value - (value % MINUTE_INCREMENT);
12603fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                final int nextValue = snapValue + MINUTE_INCREMENT;
12613fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                if (value < current && nextValue > current) {
12623fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                    // The current value is between two snap values.
12633fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                    return makeId(type, current);
1264fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette                } else if (nextValue < MINUTES_IN_CIRCLE) {
12653fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                    return makeId(type, nextValue);
12663fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                }
12673fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            }
12683fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            return INVALID_ID;
1269ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1270ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1271ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1272ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
1273ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                Bundle arguments) {
1274ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (action == AccessibilityNodeInfo.ACTION_CLICK) {
1275ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int type = getTypeFromId(virtualViewId);
1276ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int value = getValueFromId(virtualViewId);
1277ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                if (type == TYPE_HOUR) {
1278ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    final int hour = mIs24HourMode ? value : hour12To24(value, mAmOrPm);
1279ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    setCurrentHour(hour);
1280ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    return true;
1281ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                } else if (type == TYPE_MINUTE) {
1282ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    setCurrentMinute(value);
1283ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    return true;
1284ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                }
1285ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1286ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return false;
1287ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1288ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1289ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private int hour12To24(int hour12, int amOrPm) {
1290ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            int hour24 = hour12;
1291ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (hour12 == 12) {
1292ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                if (amOrPm == AM) {
1293ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    hour24 = 0;
1294ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                }
1295ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else if (amOrPm == PM) {
1296ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                hour24 += 12;
1297ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1298ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return hour24;
1299ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1300ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
13015efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette        private int hour24To12(int hour24) {
13025efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette            if (hour24 == 0) {
13035efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette                return 12;
13045efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette            } else if (hour24 > 12) {
13055efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette                return hour24 - 12;
13065efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette            } else {
13075efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette                return hour24;
13085efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette            }
13095efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette        }
13105efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette
1311ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private void getBoundsForVirtualView(int virtualViewId, Rect bounds) {
1312ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final float radius;
1313ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int type = getTypeFromId(virtualViewId);
1314ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int value = getValueFromId(virtualViewId);
1315ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final float centerRadius;
1316ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final float degrees;
1317ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (type == TYPE_HOUR) {
131888e51032320ab92057666e1230cc6548ca163c51Alan Viverette                final boolean innerCircle = getInnerCircleForHour(value);
1319ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                if (innerCircle) {
1320adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette                    centerRadius = mCircleRadius - mTextInset[HOURS_INNER];
1321adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette                    radius = mSelectorRadius;
1322ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                } else {
1323adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette                    centerRadius = mCircleRadius - mTextInset[HOURS];
1324adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette                    radius = mSelectorRadius;
1325ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                }
1326ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1327ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                degrees = getDegreesForHour(value);
1328ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else if (type == TYPE_MINUTE) {
1329adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette                centerRadius = mCircleRadius - mTextInset[MINUTES];
1330ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                degrees = getDegreesForMinute(value);
1331adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette                radius = mSelectorRadius;
1332ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1333ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                // This should never happen.
1334ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                centerRadius = 0;
1335ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                degrees = 0;
1336ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                radius = 0;
1337ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1338ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1339ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final double radians = Math.toRadians(degrees);
1340ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final float xCenter = mXCenter + centerRadius * (float) Math.sin(radians);
1341ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final float yCenter = mYCenter - centerRadius * (float) Math.cos(radians);
1342ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1343ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            bounds.set((int) (xCenter - radius), (int) (yCenter - radius),
1344ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    (int) (xCenter + radius), (int) (yCenter + radius));
1345ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1346ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1347ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private CharSequence getVirtualViewDescription(int type, int value) {
1348ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final CharSequence description;
1349ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (type == TYPE_HOUR || type == TYPE_MINUTE) {
1350ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                description = Integer.toString(value);
1351ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1352ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                description = null;
1353ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1354ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return description;
1355ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1356ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1357ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private boolean isVirtualViewSelected(int type, int value) {
1358ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final boolean selected;
1359ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (type == TYPE_HOUR) {
1360ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                selected = getCurrentHour() == value;
1361ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else if (type == TYPE_MINUTE) {
1362ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                selected = getCurrentMinute() == value;
1363ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1364ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                selected = false;
1365ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1366ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return selected;
1367ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1368ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1369ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private int makeId(int type, int value) {
1370ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return type << SHIFT_TYPE | value << SHIFT_VALUE;
1371ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1372ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1373ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private int getTypeFromId(int id) {
1374ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return id >>> SHIFT_TYPE & MASK_TYPE;
1375ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1376ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1377ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private int getValueFromId(int id) {
1378ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return id >>> SHIFT_VALUE & MASK_VALUE;
1379ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1380eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
1381eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio}
1382