RadialTimePickerView.java revision 66a8562c58507c52d0b9924aa0c7c246b1c723e8
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
1966a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viveretteimport com.android.internal.R;
2066a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viveretteimport com.android.internal.widget.ExploreByTouchHelper;
2166a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette
22eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.animation.ObjectAnimator;
2366a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viveretteimport android.annotation.IntDef;
24eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.content.Context;
25ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.content.res.ColorStateList;
26eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.content.res.Resources;
27eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.content.res.TypedArray;
28eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Canvas;
29eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Color;
30eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Paint;
31ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.graphics.Path;
32ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.graphics.Rect;
33ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.graphics.Region;
34eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Typeface;
35eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.os.Bundle;
36eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.util.AttributeSet;
372b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viveretteimport android.util.FloatProperty;
38ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.util.IntArray;
39eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.util.Log;
40ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.util.MathUtils;
41ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.util.StateSet;
425134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viveretteimport android.util.TypedValue;
43eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.HapticFeedbackConstants;
44eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.MotionEvent;
45eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.View;
46eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.accessibility.AccessibilityEvent;
47eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.accessibility.AccessibilityNodeInfo;
48ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
49eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
5066a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viveretteimport java.lang.annotation.Retention;
5166a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viveretteimport java.lang.annotation.RetentionPolicy;
52eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport java.util.Calendar;
53eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport java.util.Locale;
54eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
55eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio/**
56eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * View to show a clock circle picker (with one or two picking circles)
57eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio *
58eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * @hide
59eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */
60ec9fe1ad663d59829075029508673b2e913538d3Alan Viverettepublic class RadialTimePickerView extends View {
61ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private static final String TAG = "RadialTimePickerView";
62eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
63b0f5461eeca28d6c4be6ba1f826a43a8961f587eAlan Viverette    public static final int HOURS = 0;
64b0f5461eeca28d6c4be6ba1f826a43a8961f587eAlan Viverette    public static final int MINUTES = 1;
6566a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette
6666a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette    /** @hide */
6766a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette    @IntDef({HOURS, MINUTES})
6866a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette    @Retention(RetentionPolicy.SOURCE)
6966a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette    @interface PickerType {}
7066a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette
71eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int HOURS_INNER = 2;
72eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
73eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int SELECTOR_CIRCLE = 0;
74eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int SELECTOR_DOT = 1;
75eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int SELECTOR_LINE = 2;
76eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
77eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int AM = 0;
78eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int PM = 1;
79eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
80fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette    private static final int HOURS_IN_CIRCLE = 12;
81fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette    private static final int MINUTES_IN_CIRCLE = 60;
82fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette    private static final int DEGREES_FOR_ONE_HOUR = 360 / HOURS_IN_CIRCLE;
83fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette    private static final int DEGREES_FOR_ONE_MINUTE = 360 / MINUTES_IN_CIRCLE;
84eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
85eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int[] HOURS_NUMBERS = {12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
86eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int[] HOURS_NUMBERS_24 = {0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23};
87eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int[] MINUTES_NUMBERS = {0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55};
88eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
892b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private static final int ANIM_DURATION_NORMAL = 500;
902b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private static final int ANIM_DURATION_TOUCH = 60;
91ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
92ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private static final int[] SNAP_PREFER_30S_MAP = new int[361];
93ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
94ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private static final int NUM_POSITIONS = 12;
95ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private static final float[] COS_30 = new float[NUM_POSITIONS];
96ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private static final float[] SIN_30 = new float[NUM_POSITIONS];
97ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
982b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    /** "Something is wrong" color used when a color attribute is missing. */
992b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private static final int MISSING_COLOR = Color.MAGENTA;
1002b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
101ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    static {
102ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        // Prepare mapping to snap touchable degrees to selectable degrees.
103ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        preparePrefer30sMap();
104ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
105ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final double increment = 2.0 * Math.PI / NUM_POSITIONS;
106ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        double angle = Math.PI / 2.0;
107ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        for (int i = 0; i < NUM_POSITIONS; i++) {
108ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            COS_30[i] = (float) Math.cos(angle);
109ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            SIN_30[i] = (float) Math.sin(angle);
110ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            angle += increment;
111ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        }
112ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    }
113eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1142b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private final FloatProperty<RadialTimePickerView> HOURS_TO_MINUTES =
1152b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            new FloatProperty<RadialTimePickerView>("hoursToMinutes") {
1162b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                @Override
1172b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                public Float get(RadialTimePickerView radialTimePickerView) {
1182b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                    return radialTimePickerView.mHoursToMinutes;
1192b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                }
1202b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
1212b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                @Override
1222b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                public void setValue(RadialTimePickerView object, float value) {
1232b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                    object.mHoursToMinutes = value;
1242b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                    object.invalidate();
1252b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                }
1262b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            };
127ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
128eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final String[] mHours12Texts = new String[12];
129eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final String[] mOuterHours24Texts = new String[12];
130eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final String[] mInnerHours24Texts = new String[12];
131eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final String[] mMinutesTexts = new String[12];
132eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
133eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final Paint[] mPaint = new Paint[2];
134eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final Paint mPaintCenter = new Paint();
1352b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private final Paint[] mPaintSelector = new Paint[3];
136eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final Paint mPaintBackground = new Paint();
137eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
138ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final Typeface mTypeface;
139ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
140adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private final ColorStateList[] mTextColor = new ColorStateList[3];
141adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private final int[] mTextSize = new int[3];
142adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private final int[] mTextInset = new int[3];
143ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
144ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private final float[][] mOuterTextX = new float[2][12];
145ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private final float[][] mOuterTextY = new float[2][12];
146ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
147ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private final float[] mInnerTextX = new float[12];
148ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private final float[] mInnerTextY = new float[12];
149ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
15088e51032320ab92057666e1230cc6548ca163c51Alan Viverette    private final int[] mSelectionDegrees = new int[2];
151ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
152ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final RadialPickerTouchHelper mTouchHelper;
153ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
154f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette    private final Path mSelectorPath = new Path();
155f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette
156eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private boolean mIs24HourMode;
157eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private boolean mShowHours;
158d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette
1592b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private ObjectAnimator mHoursToMinutesAnimator;
1602b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private float mHoursToMinutes;
1612b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
162d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette    /**
163d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * When in 24-hour mode, indicates that the current hour is between
164d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * 1 and 12 (inclusive).
165d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     */
166eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private boolean mIsOnInnerCircle;
167eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
168adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private int mSelectorRadius;
16962c79e9a64c3b2cafd5500ed3064977dff7b7da3Alan Viverette    private int mSelectorStroke;
170adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private int mSelectorDotRadius;
171adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private int mCenterDotRadius;
172adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette
1732b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private int mSelectorColor;
1742b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private int mSelectorDotColor;
1752b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
176adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private int mXCenter;
177adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private int mYCenter;
178adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private int mCircleRadius;
179eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
18088e51032320ab92057666e1230cc6548ca163c51Alan Viverette    private int mMinDistForInnerNumber;
18188e51032320ab92057666e1230cc6548ca163c51Alan Viverette    private int mMaxDistForOuterNumber;
18288e51032320ab92057666e1230cc6548ca163c51Alan Viverette    private int mHalfwayDist;
183eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
184eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private String[] mOuterTextHours;
185eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private String[] mInnerTextHours;
18688e51032320ab92057666e1230cc6548ca163c51Alan Viverette    private String[] mMinutesText;
187eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
188eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private int mAmOrPm;
189adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette
190adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private float mDisabledAlpha;
1915134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette
192eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private OnValueSelectedListener mListener;
193eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
194ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private boolean mInputEnabled = true;
195eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
19666a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette    interface OnValueSelectedListener {
19766a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette        /**
19866a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         * Called when the selected value at a given picker index has changed.
19966a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         *
20066a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         * @param pickerType the type of value that has changed, one of:
20166a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         *                   <ul>
20266a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         *                       <li>{@link #MINUTES}
20366a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         *                       <li>{@link #HOURS}
20466a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         *                   </ul>
20566a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         * @param newValue the new value as minute in hour (0-59) or hour in
20666a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         *                 day (0-23)
20766a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         * @param autoAdvance when the picker type is {@link #HOURS},
20866a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         *                    {@code true} to switch to the {@link #MINUTES}
20966a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         *                    picker or {@code false} to stay on the current
21066a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         *                    picker. No effect when picker type is
21166a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         *                    {@link #MINUTES}.
21266a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette         */
21366a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette        void onValueSelected(@PickerType int pickerType, int newValue, boolean autoAdvance);
214eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
215eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
216eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    /**
217eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * Split up the 360 degrees of the circle among the 60 selectable values. Assigns a larger
218eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * selectable area to each of the 12 visible values, such that the ratio of space apportioned
219eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * to a visible value : space apportioned to a non-visible value will be 14 : 4.
220eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * E.g. the output of 30 degrees should have a higher range of input associated with it than
221eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * the output of 24 degrees, because 30 degrees corresponds to a visible number on the clock
222eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * circle (5 on the minutes, 1 or 13 on the hours).
223eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     */
224eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static void preparePrefer30sMap() {
225eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // We'll split up the visible output and the non-visible output such that each visible
226eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // output will correspond to a range of 14 associated input degrees, and each non-visible
227eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // output will correspond to a range of 4 associate input degrees, so visible numbers
228eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // are more than 3 times easier to get than non-visible numbers:
229eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // {354-359,0-7}:0, {8-11}:6, {12-15}:12, {16-19}:18, {20-23}:24, {24-37}:30, etc.
230eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        //
231eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // If an output of 30 degrees should correspond to a range of 14 associated degrees, then
232eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // we'll need any input between 24 - 37 to snap to 30. Working out from there, 20-23 should
233eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // snap to 24, while 38-41 should snap to 36. This is somewhat counter-intuitive, that you
234eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // can be touching 36 degrees but have the selection snapped to 30 degrees; however, this
235eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // inconsistency isn't noticeable at such fine-grained degrees, and it affords us the
236eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // ability to aggressively prefer the visible values by a factor of more than 3:1, which
237eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // greatly contributes to the selectability of these values.
238eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
239eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // The first output is 0, and each following output will increment by 6 {0, 6, 12, ...}.
240eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int snappedOutputDegrees = 0;
241eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Count of how many inputs we've designated to the specified output.
242eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int count = 1;
243eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // How many input we expect for a specified output. This will be 14 for output divisible
244eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // by 30, and 4 for the remaining output. We'll special case the outputs of 0 and 360, so
245eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // the caller can decide which they need.
246eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int expectedCount = 8;
247eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Iterate through the input.
248eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        for (int degrees = 0; degrees < 361; degrees++) {
249eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            // Save the input-output mapping.
250ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            SNAP_PREFER_30S_MAP[degrees] = snappedOutputDegrees;
251eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            // If this is the last input for the specified output, calculate the next output and
252eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            // the next expected count.
253eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if (count == expectedCount) {
254eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                snappedOutputDegrees += 6;
255eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                if (snappedOutputDegrees == 360) {
256eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    expectedCount = 7;
257eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                } else if (snappedOutputDegrees % 30 == 0) {
258eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    expectedCount = 14;
259eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                } else {
260eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    expectedCount = 4;
261eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                }
262eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                count = 1;
263eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            } else {
264eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                count++;
265eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
266eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
267eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
268eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
269eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    /**
270eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * Returns mapping of any input degrees (0 to 360) to one of 60 selectable output degrees,
271eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * where the degrees corresponding to visible numbers (i.e. those divisible by 30) will be
272eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * weighted heavier than the degrees corresponding to non-visible numbers.
273eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * See {@link #preparePrefer30sMap()} documentation for the rationale and generation of the
274eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * mapping.
275eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     */
276eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static int snapPrefer30s(int degrees) {
277ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        if (SNAP_PREFER_30S_MAP == null) {
278eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            return -1;
279eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
280ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        return SNAP_PREFER_30S_MAP[degrees];
281eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
282eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
283eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    /**
284eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * Returns mapping of any input degrees (0 to 360) to one of 12 visible output degrees (all
285eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * multiples of 30), where the input will be "snapped" to the closest visible degrees.
286eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * @param degrees The input degrees
287eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * @param forceHigherOrLower The output may be forced to either the higher or lower step, or may
288eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * be allowed to snap to whichever is closer. Use 1 to force strictly higher, -1 to force
289eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * strictly lower, and 0 to snap to the closer one.
290eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * @return output degrees, will be a multiple of 30
291eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     */
292eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static int snapOnly30s(int degrees, int forceHigherOrLower) {
293eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int stepSize = DEGREES_FOR_ONE_HOUR;
294eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int floor = (degrees / stepSize) * stepSize;
295eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int ceiling = floor + stepSize;
296eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (forceHigherOrLower == 1) {
297eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            degrees = ceiling;
298eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        } else if (forceHigherOrLower == -1) {
299eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if (degrees == floor) {
300eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                floor -= stepSize;
301eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
302eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            degrees = floor;
303eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        } else {
304eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if ((degrees - floor) < (ceiling - degrees)) {
305eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                degrees = floor;
306eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            } else {
307eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                degrees = ceiling;
308eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
309eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
310eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        return degrees;
311eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
312eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
313ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    @SuppressWarnings("unused")
314ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    public RadialTimePickerView(Context context)  {
315ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        this(context, null);
316ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
317ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
318eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public RadialTimePickerView(Context context, AttributeSet attrs)  {
319eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        this(context, attrs, R.attr.timePickerStyle);
320eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
321eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
322ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    public RadialTimePickerView(Context context, AttributeSet attrs, int defStyleAttr)  {
323ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        this(context, attrs, defStyleAttr, 0);
324ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
325ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
326ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    public RadialTimePickerView(
327ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)  {
328eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        super(context, attrs);
329eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
3302b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        applyAttributes(attrs, defStyleAttr, defStyleRes);
3312b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
3325134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette        // Pull disabled alpha from theme.
3335134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette        final TypedValue outValue = new TypedValue();
3345134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette        context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true);
335adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mDisabledAlpha = outValue.getFloat();
3365134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette
337eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mTypeface = Typeface.create("sans-serif", Typeface.NORMAL);
338eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
339eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[HOURS] = new Paint();
340eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[HOURS].setAntiAlias(true);
341eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[HOURS].setTextAlign(Paint.Align.CENTER);
342eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
343eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[MINUTES] = new Paint();
344eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[MINUTES].setAntiAlias(true);
345eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[MINUTES].setTextAlign(Paint.Align.CENTER);
346eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
347eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintCenter.setAntiAlias(true);
348ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
3492b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintSelector[SELECTOR_CIRCLE] = new Paint();
3502b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintSelector[SELECTOR_CIRCLE].setAntiAlias(true);
351eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
3522b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintSelector[SELECTOR_DOT] = new Paint();
3532b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintSelector[SELECTOR_DOT].setAntiAlias(true);
354eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
3552b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintSelector[SELECTOR_LINE] = new Paint();
3562b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintSelector[SELECTOR_LINE].setAntiAlias(true);
3572b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintSelector[SELECTOR_LINE].setStrokeWidth(2);
358eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
359eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintBackground.setAntiAlias(true);
360eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
3612b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final Resources res = getResources();
362adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mSelectorRadius = res.getDimensionPixelSize(R.dimen.timepicker_selector_radius);
36362c79e9a64c3b2cafd5500ed3064977dff7b7da3Alan Viverette        mSelectorStroke = res.getDimensionPixelSize(R.dimen.timepicker_selector_stroke);
364adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mSelectorDotRadius = res.getDimensionPixelSize(R.dimen.timepicker_selector_dot_radius);
365adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mCenterDotRadius = res.getDimensionPixelSize(R.dimen.timepicker_center_dot_radius);
366adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette
367adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mTextSize[HOURS] = res.getDimensionPixelSize(R.dimen.timepicker_text_size_normal);
368adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mTextSize[MINUTES] = res.getDimensionPixelSize(R.dimen.timepicker_text_size_normal);
369adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mTextSize[HOURS_INNER] = res.getDimensionPixelSize(R.dimen.timepicker_text_size_inner);
370adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette
371adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mTextInset[HOURS] = res.getDimensionPixelSize(R.dimen.timepicker_text_inset_normal);
372adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mTextInset[MINUTES] = res.getDimensionPixelSize(R.dimen.timepicker_text_inset_normal);
373adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mTextInset[HOURS_INNER] = res.getDimensionPixelSize(R.dimen.timepicker_text_inset_inner);
374eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
375eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mShowHours = true;
3762b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mHoursToMinutes = HOURS;
377eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mIs24HourMode = false;
378eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mAmOrPm = AM;
379eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
380ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        // Set up accessibility components.
381ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        mTouchHelper = new RadialPickerTouchHelper();
382ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setAccessibilityDelegate(mTouchHelper);
383ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
384ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
385ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
386ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
387ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
388eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        initHoursAndMinutesText();
389eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        initData();
390eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
391eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Initial values
392eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final Calendar calendar = Calendar.getInstance(Locale.getDefault());
393eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int currentHour = calendar.get(Calendar.HOUR_OF_DAY);
394eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int currentMinute = calendar.get(Calendar.MINUTE);
395eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
396ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentHourInternal(currentHour, false, false);
397ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentMinuteInternal(currentMinute, false);
398eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
399eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        setHapticFeedbackEnabled(true);
400eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
401eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
4022b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    void applyAttributes(AttributeSet attrs, int defStyleAttr, int defStyleRes) {
4032b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final Context context = getContext();
4042b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final TypedArray a = getContext().obtainStyledAttributes(attrs,
4052b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                R.styleable.TimePicker, defStyleAttr, defStyleRes);
4062b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
4072b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final ColorStateList numbersTextColor = a.getColorStateList(
4082b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                R.styleable.TimePicker_numbersTextColor);
4092b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final ColorStateList numbersInnerTextColor = a.getColorStateList(
4102b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                R.styleable.TimePicker_numbersInnerTextColor);
4112b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mTextColor[HOURS] = numbersTextColor == null ?
4122b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                ColorStateList.valueOf(MISSING_COLOR) : numbersTextColor;
4132b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mTextColor[HOURS_INNER] = numbersInnerTextColor == null ?
4142b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                ColorStateList.valueOf(MISSING_COLOR) : numbersInnerTextColor;
4152b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mTextColor[MINUTES] = mTextColor[HOURS];
4162b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
4172b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        // Set up various colors derived from the selector "activated" state.
4182b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final ColorStateList selectorColors = a.getColorStateList(
4192b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                R.styleable.TimePicker_numbersSelectorColor);
4202b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int selectorActivatedColor;
4212b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        if (selectorColors != null) {
4222b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            final int[] stateSetEnabledActivated = StateSet.get(
4232b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                    StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED);
4242b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            selectorActivatedColor = selectorColors.getColorForState(
4252b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                    stateSetEnabledActivated, 0);
4262b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        }  else {
4272b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            selectorActivatedColor = MISSING_COLOR;
4282b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        }
4292b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
4302b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintCenter.setColor(selectorActivatedColor);
4312b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
4322b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int[] stateSetActivated = StateSet.get(
4332b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED);
4342b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
4352b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mSelectorColor = selectorActivatedColor;
4362b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mSelectorDotColor = mTextColor[HOURS].getColorForState(stateSetActivated, 0);
4372b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
4382b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mPaintBackground.setColor(a.getColor(R.styleable.TimePicker_numbersBackgroundColor,
4392b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                context.getColor(R.color.timepicker_default_numbers_background_color_material)));
4402b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
4412b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        a.recycle();
4422b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    }
4432b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
444eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void initialize(int hour, int minute, boolean is24HourMode) {
445448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette        if (mIs24HourMode != is24HourMode) {
446448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette            mIs24HourMode = is24HourMode;
447448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette            initData();
448448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette        }
449ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
450ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentHourInternal(hour, false, false);
451ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentMinuteInternal(minute, false);
452eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
453eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
454eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void setCurrentItemShowing(int item, boolean animate) {
455eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        switch (item){
456eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            case HOURS:
457eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                showHours(animate);
458eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                break;
459eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            case MINUTES:
460eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                showMinutes(animate);
461eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                break;
462eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            default:
463eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                Log.e(TAG, "ClockView does not support showing item " + item);
464eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
465eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
466eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
467eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public int getCurrentItemShowing() {
468eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        return mShowHours ? HOURS : MINUTES;
469eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
470eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
471eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void setOnValueSelectedListener(OnValueSelectedListener listener) {
472eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mListener = listener;
473eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
474eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
475d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette    /**
476d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * Sets the current hour in 24-hour time.
477d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     *
478d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * @param hour the current hour between 0 and 23 (inclusive)
479d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     */
480eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void setCurrentHour(int hour) {
481ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentHourInternal(hour, true, false);
482ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
483ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
484ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    /**
485ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     * Sets the current hour.
486ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     *
487ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     * @param hour The current hour
488ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     * @param callback Whether the value listener should be invoked
489ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     * @param autoAdvance Whether the listener should auto-advance to the next
490ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     *                    selection mode, e.g. hour to minutes
491ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     */
492ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private void setCurrentHourInternal(int hour, boolean callback, boolean autoAdvance) {
493eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int degrees = (hour % 12) * DEGREES_FOR_ONE_HOUR;
494eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mSelectionDegrees[HOURS] = degrees;
495d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette
496d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette        // 0 is 12 AM (midnight) and 12 is 12 PM (noon).
497ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        final int amOrPm = (hour == 0 || (hour % 24) < 12) ? AM : PM;
49888e51032320ab92057666e1230cc6548ca163c51Alan Viverette        final boolean isOnInnerCircle = getInnerCircleForHour(hour);
499ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (mAmOrPm != amOrPm || mIsOnInnerCircle != isOnInnerCircle) {
500ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            mAmOrPm = amOrPm;
501ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            mIsOnInnerCircle = isOnInnerCircle;
502ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
503ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            initData();
504ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            mTouchHelper.invalidateRoot();
505ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
506d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette
507eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        invalidate();
508ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
509ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (callback && mListener != null) {
510ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            mListener.onValueSelected(HOURS, hour, autoAdvance);
511ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
512eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
513eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
514d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette    /**
515d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * Returns the current hour in 24-hour time.
516d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     *
517d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * @return the current hour between 0 and 23 (inclusive)
518d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     */
519eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public int getCurrentHour() {
52088e51032320ab92057666e1230cc6548ca163c51Alan Viverette        return getHourForDegrees(mSelectionDegrees[HOURS], mIsOnInnerCircle);
521ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
522ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
523ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private int getHourForDegrees(int degrees, boolean innerCircle) {
524ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        int hour = (degrees / DEGREES_FOR_ONE_HOUR) % 12;
525eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mIs24HourMode) {
526d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette            // Convert the 12-hour value into 24-hour time based on where the
527d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette            // selector is positioned.
52888e51032320ab92057666e1230cc6548ca163c51Alan Viverette            if (!innerCircle && hour == 0) {
52988e51032320ab92057666e1230cc6548ca163c51Alan Viverette                // Outer circle is 1 through 12.
530d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette                hour = 12;
53188e51032320ab92057666e1230cc6548ca163c51Alan Viverette            } else if (innerCircle && hour != 0) {
53288e51032320ab92057666e1230cc6548ca163c51Alan Viverette                // Inner circle is 13 through 23 and 0.
533d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette                hour += 12;
534eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
535d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette        } else if (mAmOrPm == PM) {
536d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette            hour += 12;
537eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
538d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette        return hour;
539eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
540eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
54188e51032320ab92057666e1230cc6548ca163c51Alan Viverette    /**
54288e51032320ab92057666e1230cc6548ca163c51Alan Viverette     * @param hour the hour in 24-hour time or 12-hour time
54388e51032320ab92057666e1230cc6548ca163c51Alan Viverette     */
544ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private int getDegreesForHour(int hour) {
545ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        // Convert to be 0-11.
546ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (mIs24HourMode) {
547ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (hour >= 12) {
548ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                hour -= 12;
549ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
550ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        } else if (hour == 12) {
551ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            hour = 0;
552ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
553ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return hour * DEGREES_FOR_ONE_HOUR;
554ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
555ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
55688e51032320ab92057666e1230cc6548ca163c51Alan Viverette    /**
55788e51032320ab92057666e1230cc6548ca163c51Alan Viverette     * @param hour the hour in 24-hour time or 12-hour time
55888e51032320ab92057666e1230cc6548ca163c51Alan Viverette     */
55988e51032320ab92057666e1230cc6548ca163c51Alan Viverette    private boolean getInnerCircleForHour(int hour) {
56088e51032320ab92057666e1230cc6548ca163c51Alan Viverette        return mIs24HourMode && (hour == 0 || hour > 12);
56188e51032320ab92057666e1230cc6548ca163c51Alan Viverette    }
56288e51032320ab92057666e1230cc6548ca163c51Alan Viverette
563eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void setCurrentMinute(int minute) {
564ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentMinuteInternal(minute, true);
565ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
566ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
567ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private void setCurrentMinuteInternal(int minute, boolean callback) {
568fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette        mSelectionDegrees[MINUTES] = (minute % MINUTES_IN_CIRCLE) * DEGREES_FOR_ONE_MINUTE;
569ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
570eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        invalidate();
571ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
572ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (callback && mListener != null) {
573ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            mListener.onValueSelected(MINUTES, minute, false);
574ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
575eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
576eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
577eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    // Returns minutes in 0-59 range
578eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public int getCurrentMinute() {
579ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return getMinuteForDegrees(mSelectionDegrees[MINUTES]);
580ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
581ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
582ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private int getMinuteForDegrees(int degrees) {
583ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return degrees / DEGREES_FOR_ONE_MINUTE;
584ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
585ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
586ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private int getDegreesForMinute(int minute) {
587ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return minute * DEGREES_FOR_ONE_MINUTE;
588eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
589eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
59030b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette    /**
59130b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette     * Sets whether the picker is showing AM or PM hours. Has no effect when
59230b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette     * in 24-hour mode.
59330b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette     *
59430b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette     * @param amOrPm {@link #AM} or {@link #PM}
59530b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette     * @return {@code true} if the value changed from what was previously set,
59630b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette     *         or {@code false} otherwise
59730b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette     */
59830b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette    public boolean setAmOrPm(int amOrPm) {
59930b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette        if (mAmOrPm == amOrPm || mIs24HourMode) {
60030b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette            return false;
60130b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette        }
60230b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette
60330b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette        mAmOrPm = amOrPm;
604eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        invalidate();
605ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        mTouchHelper.invalidateRoot();
60630b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette        return true;
607eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
608eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
609eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public int getAmOrPm() {
610eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        return mAmOrPm;
611eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
612eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
613eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void showHours(boolean animate) {
6142b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        showPicker(true, animate);
615eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
616eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
617eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void showMinutes(boolean animate) {
6182b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        showPicker(false, animate);
619eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
620eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
621eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void initHoursAndMinutesText() {
622eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Initialize the hours and minutes numbers.
623eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        for (int i = 0; i < 12; i++) {
624eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mHours12Texts[i] = String.format("%d", HOURS_NUMBERS[i]);
625f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette            mInnerHours24Texts[i] = String.format("%02d", HOURS_NUMBERS_24[i]);
626f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette            mOuterHours24Texts[i] = String.format("%d", HOURS_NUMBERS[i]);
627eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mMinutesTexts[i] = String.format("%02d", MINUTES_NUMBERS[i]);
628eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
629eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
630eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
631eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void initData() {
632eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mIs24HourMode) {
633eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mOuterTextHours = mOuterHours24Texts;
634eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mInnerTextHours = mInnerHours24Texts;
635eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        } else {
636eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mOuterTextHours = mHours12Texts;
637f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette            mInnerTextHours = mHours12Texts;
638eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
639eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
64088e51032320ab92057666e1230cc6548ca163c51Alan Viverette        mMinutesText = mMinutesTexts;
641eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
642eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
643eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    @Override
644eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
645ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        if (!changed) {
646ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            return;
647ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        }
648eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
649eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mXCenter = getWidth() / 2;
650eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mYCenter = getHeight() / 2;
651ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mCircleRadius = Math.min(mXCenter, mYCenter);
652eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
65388e51032320ab92057666e1230cc6548ca163c51Alan Viverette        mMinDistForInnerNumber = mCircleRadius - mTextInset[HOURS_INNER] - mSelectorRadius;
65488e51032320ab92057666e1230cc6548ca163c51Alan Viverette        mMaxDistForOuterNumber = mCircleRadius - mTextInset[HOURS] + mSelectorRadius;
65588e51032320ab92057666e1230cc6548ca163c51Alan Viverette        mHalfwayDist = mCircleRadius - (mTextInset[HOURS] + mTextInset[HOURS_INNER]) / 2;
656eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
657ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        calculatePositionsHours();
658ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        calculatePositionsMinutes();
659eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
660ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        mTouchHelper.invalidateRoot();
661eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
662eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
663eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    @Override
664eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void onDraw(Canvas canvas) {
665adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        final float alphaMod = mInputEnabled ? 1 : mDisabledAlpha;
666eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
667eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        drawCircleBackground(canvas);
6682b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
6692b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final Path selectorPath = mSelectorPath;
6702b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        drawSelector(canvas, selectorPath);
6712b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        drawHours(canvas, selectorPath, alphaMod);
6722b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        drawMinutes(canvas, selectorPath, alphaMod);
673adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        drawCenter(canvas, alphaMod);
674adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    }
675eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
6762b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private void showPicker(boolean hours, boolean animate) {
6772b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        if (mShowHours == hours) {
6782b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            return;
6792b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        }
6802b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
6812b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mShowHours = hours;
6822b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
6832b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        if (animate) {
6842b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            animatePicker(hours, ANIM_DURATION_NORMAL);
68532f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette        } else {
68632f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette            // If we have a pending or running animator, cancel it.
68732f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette            if (mHoursToMinutesAnimator != null && mHoursToMinutesAnimator.isStarted()) {
68832f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette                mHoursToMinutesAnimator.cancel();
68932f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette                mHoursToMinutesAnimator = null;
69032f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette            }
69132f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette            mHoursToMinutes = hours ? 0.0f : 1.0f;
6922b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        }
6932b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
6942b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        initData();
6952b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        invalidate();
6962b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mTouchHelper.invalidateRoot();
6972b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    }
6982b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
6992b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private void animatePicker(boolean hoursToMinutes, long duration) {
7002b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final float target = hoursToMinutes ? HOURS : MINUTES;
7012b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        if (mHoursToMinutes == target) {
7022b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            // If we have a pending or running animator, cancel it.
7032b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            if (mHoursToMinutesAnimator != null && mHoursToMinutesAnimator.isStarted()) {
7042b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                mHoursToMinutesAnimator.cancel();
7052b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                mHoursToMinutesAnimator = null;
7062b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            }
7072b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
7082b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            // We're already showing the correct picker.
7092b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            return;
7102b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        }
7112b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
7122b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mHoursToMinutesAnimator = ObjectAnimator.ofFloat(this, HOURS_TO_MINUTES, target);
7132b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mHoursToMinutesAnimator.setAutoCancel(true);
7142b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mHoursToMinutesAnimator.setDuration(duration);
7152b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        mHoursToMinutesAnimator.start();
7162b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    }
7172b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
718adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private void drawCircleBackground(Canvas canvas) {
719adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        canvas.drawCircle(mXCenter, mYCenter, mCircleRadius, mPaintBackground);
720adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    }
721adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette
7222b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private void drawHours(Canvas canvas, Path selectorPath, float alphaMod) {
7232b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int hoursAlpha = (int) (255f * (1f - mHoursToMinutes) * alphaMod + 0.5f);
724ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        if (hoursAlpha > 0) {
7252b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            // Exclude the selector region, then draw inner/outer hours with no
7262b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            // activated states.
7272b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.save(Canvas.CLIP_SAVE_FLAG);
7282b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.clipPath(selectorPath, Region.Op.DIFFERENCE);
7292b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            drawHoursClipped(canvas, hoursAlpha, false);
7302b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.restore();
7312b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
7322b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            // Intersect the selector region, then draw minutes with only
7332b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            // activated states.
7342b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.save(Canvas.CLIP_SAVE_FLAG);
7352b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.clipPath(selectorPath, Region.Op.INTERSECT);
7362b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            drawHoursClipped(canvas, hoursAlpha, true);
7372b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.restore();
738eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
739adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    }
740eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
7412b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private void drawHoursClipped(Canvas canvas, int hoursAlpha, boolean showActivated) {
7422b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        // Draw outer hours.
7432b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        drawTextElements(canvas, mTextSize[HOURS], mTypeface, mTextColor[HOURS], mOuterTextHours,
7442b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                mOuterTextX[HOURS], mOuterTextY[HOURS], mPaint[HOURS], hoursAlpha,
7452b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                showActivated && !mIsOnInnerCircle, mSelectionDegrees[HOURS], showActivated);
746ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
7472b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        // Draw inner hours (13-00) for 24-hour time.
7482b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        if (mIs24HourMode && mInnerTextHours != null) {
7492b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            drawTextElements(canvas, mTextSize[HOURS_INNER], mTypeface, mTextColor[HOURS_INNER],
7502b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                    mInnerTextHours, mInnerTextX, mInnerTextY, mPaint[HOURS], hoursAlpha,
7512b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                    showActivated && mIsOnInnerCircle, mSelectionDegrees[HOURS], showActivated);
7522b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        }
7532b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    }
7542b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
7552b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private void drawMinutes(Canvas canvas, Path selectorPath, float alphaMod) {
7562b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int minutesAlpha = (int) (255f * mHoursToMinutes * alphaMod + 0.5f);
7572b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        if (minutesAlpha > 0) {
758ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            // Exclude the selector region, then draw minutes with no
759ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            // activated states.
760ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            canvas.save(Canvas.CLIP_SAVE_FLAG);
7612b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.clipPath(selectorPath, Region.Op.DIFFERENCE);
7622b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            drawMinutesClipped(canvas, minutesAlpha, false);
763ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            canvas.restore();
764ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
765ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            // Intersect the selector region, then draw minutes with only
766ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            // activated states.
767ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            canvas.save(Canvas.CLIP_SAVE_FLAG);
7682b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.clipPath(selectorPath, Region.Op.INTERSECT);
7692b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            drawMinutesClipped(canvas, minutesAlpha, true);
770ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            canvas.restore();
771ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        }
772eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
773eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
7742b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private void drawMinutesClipped(Canvas canvas, int minutesAlpha, boolean showActivated) {
7752b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mTextColor[MINUTES], mMinutesText,
7762b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES], minutesAlpha,
7772b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                showActivated, mSelectionDegrees[MINUTES], showActivated);
7782b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    }
7792b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
780adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private void drawCenter(Canvas canvas, float alphaMod) {
781adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        mPaintCenter.setAlpha((int) (255 * alphaMod + 0.5f));
782adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        canvas.drawCircle(mXCenter, mYCenter, mCenterDotRadius, mPaintCenter);
783eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
784eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
785eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette    private int getMultipliedAlpha(int argb, int alpha) {
786eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        return (int) (Color.alpha(argb) * (alpha / 255.0) + 0.5);
787eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette    }
788eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
7892b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette    private void drawSelector(Canvas canvas, Path selectorPath) {
7902b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        // Determine the current length, angle, and dot scaling factor.
7912b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int hoursIndex = mIsOnInnerCircle ? HOURS_INNER : HOURS;
7922b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int hoursInset = mTextInset[hoursIndex];
7932b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int hoursAngleDeg = mSelectionDegrees[hoursIndex % 2];
7942b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final float hoursDotScale = mSelectionDegrees[hoursIndex % 2] % 30 != 0 ? 1 : 0;
7952b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
7962b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int minutesIndex = MINUTES;
7972b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int minutesInset = mTextInset[minutesIndex];
7982b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final int minutesAngleDeg = mSelectionDegrees[minutesIndex];
7992b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final float minutesDotScale = mSelectionDegrees[minutesIndex] % 30 != 0 ? 1 : 0;
800eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
801f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        // Calculate the current radius at which to place the selection circle.
802f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        final int selRadius = mSelectorRadius;
8032b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final float selLength =
8042b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                mCircleRadius - MathUtils.lerp(hoursInset, minutesInset, mHoursToMinutes);
8052b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final double selAngleRad =
8062b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette                Math.toRadians(MathUtils.lerpDeg(hoursAngleDeg, minutesAngleDeg, mHoursToMinutes));
807f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        final float selCenterX = mXCenter + selLength * (float) Math.sin(selAngleRad);
808f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        final float selCenterY = mYCenter - selLength * (float) Math.cos(selAngleRad);
809f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette
810f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        // Draw the selection circle.
8112b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final Paint paint = mPaintSelector[SELECTOR_CIRCLE];
8122b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        paint.setColor(mSelectorColor);
813f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        canvas.drawCircle(selCenterX, selCenterY, selRadius, paint);
814eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
815ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        // If needed, set up the clip path for later.
816ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        if (selectorPath != null) {
817f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette            selectorPath.reset();
818f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette            selectorPath.addCircle(selCenterX, selCenterY, selRadius, Path.Direction.CCW);
819ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        }
820ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
821f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        // Draw the dot if we're between two items.
8222b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final float dotScale = MathUtils.lerp(hoursDotScale, minutesDotScale, mHoursToMinutes);
8232b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        if (dotScale > 0) {
8242b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            final Paint dotPaint = mPaintSelector[SELECTOR_DOT];
82588e51032320ab92057666e1230cc6548ca163c51Alan Viverette            dotPaint.setColor(mSelectorDotColor);
8262b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette            canvas.drawCircle(selCenterX, selCenterY, mSelectorDotRadius * dotScale, dotPaint);
827eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
828eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
829adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        // Shorten the line to only go from the edge of the center dot to the
830adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        // edge of the selection circle.
831f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        final double sin = Math.sin(selAngleRad);
832f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        final double cos = Math.cos(selAngleRad);
8332b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final float lineLength = selLength - selRadius;
834adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        final int centerX = mXCenter + (int) (mCenterDotRadius * sin);
835adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        final int centerY = mYCenter - (int) (mCenterDotRadius * cos);
836f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        final float linePointX = centerX + (int) (lineLength * sin);
837f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        final float linePointY = centerY - (int) (lineLength * cos);
838ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
839f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        // Draw the line.
8402b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        final Paint linePaint = mPaintSelector[SELECTOR_LINE];
8412b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        linePaint.setColor(mSelectorColor);
842f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        linePaint.setStrokeWidth(mSelectorStroke);
843f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette        canvas.drawLine(mXCenter, mYCenter, linePointX, linePointY, linePaint);
844eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
845eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
846ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private void calculatePositionsHours() {
847eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Calculate the text positions
848adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        final float numbersRadius = mCircleRadius - mTextInset[HOURS];
849eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
850eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Calculate the positions for the 12 numbers in the main circle.
851ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        calculatePositions(mPaint[HOURS], numbersRadius, mXCenter, mYCenter,
852ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                mTextSize[HOURS], mOuterTextX[HOURS], mOuterTextY[HOURS]);
853eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
854eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // If we have an inner circle, calculate those positions too.
855eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mIs24HourMode) {
856adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette            final int innerNumbersRadius = mCircleRadius - mTextInset[HOURS_INNER];
857ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            calculatePositions(mPaint[HOURS], innerNumbersRadius, mXCenter, mYCenter,
858ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    mTextSize[HOURS_INNER], mInnerTextX, mInnerTextY);
859eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
860eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
861eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
862ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private void calculatePositionsMinutes() {
863eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Calculate the text positions
864adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette        final float numbersRadius = mCircleRadius - mTextInset[MINUTES];
865eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
866eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Calculate the positions for the 12 numbers in the main circle.
867ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        calculatePositions(mPaint[MINUTES], numbersRadius, mXCenter, mYCenter,
868ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                mTextSize[MINUTES], mOuterTextX[MINUTES], mOuterTextY[MINUTES]);
869eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
870eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
871eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    /**
872eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * Using the trigonometric Unit Circle, calculate the positions that the text will need to be
873eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * drawn at based on the specified circle radius. Place the values in the textGridHeights and
874eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * textGridWidths parameters.
875eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     */
876ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private static void calculatePositions(Paint paint, float radius, float xCenter, float yCenter,
877ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            float textSize, float[] x, float[] y) {
878ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        // Adjust yCenter to account for the text's baseline.
879eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        paint.setTextSize(textSize);
880eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        yCenter -= (paint.descent() + paint.ascent()) / 2;
881eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
882ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        for (int i = 0; i < NUM_POSITIONS; i++) {
883ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            x[i] = xCenter - radius * COS_30[i];
884ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            y[i] = yCenter - radius * SIN_30[i];
885ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        }
886eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
887eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
888eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    /**
889eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * Draw the 12 text values at the positions specified by the textGrid parameters.
890eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     */
891adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette    private void drawTextElements(Canvas canvas, float textSize, Typeface typeface,
892adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette            ColorStateList textColor, String[] texts, float[] textX, float[] textY, Paint paint,
893adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette            int alpha, boolean showActivated, int activatedDegrees, boolean activatedOnly) {
894eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        paint.setTextSize(textSize);
895eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        paint.setTypeface(typeface);
896eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
897ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        // The activated index can touch a range of elements.
898ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final float activatedIndex = activatedDegrees / (360.0f / NUM_POSITIONS);
899ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final int activatedFloor = (int) activatedIndex;
900ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final int activatedCeil = ((int) Math.ceil(activatedIndex)) % NUM_POSITIONS;
901eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
902ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        for (int i = 0; i < 12; i++) {
903ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            final boolean activated = (activatedFloor == i || activatedCeil == i);
904ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            if (activatedOnly && !activated) {
905ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                continue;
906ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            }
907eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
908ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            final int stateMask = StateSet.VIEW_STATE_ENABLED
909ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    | (showActivated && activated ? StateSet.VIEW_STATE_ACTIVATED : 0);
910adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette            final int color = textColor.getColorForState(StateSet.get(stateMask), 0);
911ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            paint.setColor(color);
912ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            paint.setAlpha(getMultipliedAlpha(color, alpha));
913eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
914ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            canvas.drawText(texts[i], textX[i], textY[i], paint);
915ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        }
916eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
917eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
918ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private int getDegreesFromXY(float x, float y, boolean constrainOutside) {
91988e51032320ab92057666e1230cc6548ca163c51Alan Viverette        // Ensure the point is inside the touchable area.
92088e51032320ab92057666e1230cc6548ca163c51Alan Viverette        final int innerBound;
92188e51032320ab92057666e1230cc6548ca163c51Alan Viverette        final int outerBound;
92288e51032320ab92057666e1230cc6548ca163c51Alan Viverette        if (mIs24HourMode && mShowHours) {
92388e51032320ab92057666e1230cc6548ca163c51Alan Viverette            innerBound = mMinDistForInnerNumber;
92488e51032320ab92057666e1230cc6548ca163c51Alan Viverette            outerBound = mMaxDistForOuterNumber;
92588e51032320ab92057666e1230cc6548ca163c51Alan Viverette        } else {
92688e51032320ab92057666e1230cc6548ca163c51Alan Viverette            final int index = mShowHours ? HOURS : MINUTES;
92788e51032320ab92057666e1230cc6548ca163c51Alan Viverette            final int center = mCircleRadius - mTextInset[index];
92888e51032320ab92057666e1230cc6548ca163c51Alan Viverette            innerBound = center - mSelectorRadius;
92988e51032320ab92057666e1230cc6548ca163c51Alan Viverette            outerBound = center + mSelectorRadius;
93088e51032320ab92057666e1230cc6548ca163c51Alan Viverette        }
931eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
93288e51032320ab92057666e1230cc6548ca163c51Alan Viverette        final double dX = x - mXCenter;
93388e51032320ab92057666e1230cc6548ca163c51Alan Viverette        final double dY = y - mYCenter;
93488e51032320ab92057666e1230cc6548ca163c51Alan Viverette        final double distFromCenter = Math.sqrt(dX * dX + dY * dY);
93588e51032320ab92057666e1230cc6548ca163c51Alan Viverette        if (distFromCenter < innerBound || constrainOutside && distFromCenter > outerBound) {
936eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            return -1;
937eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
938ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
93988e51032320ab92057666e1230cc6548ca163c51Alan Viverette        // Convert to degrees.
94088e51032320ab92057666e1230cc6548ca163c51Alan Viverette        final int degrees = (int) (Math.toDegrees(Math.atan2(dY, dX) + Math.PI / 2) + 0.5);
94188e51032320ab92057666e1230cc6548ca163c51Alan Viverette        if (degrees < 0) {
94288e51032320ab92057666e1230cc6548ca163c51Alan Viverette            return degrees + 360;
943eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        } else {
94488e51032320ab92057666e1230cc6548ca163c51Alan Viverette            return degrees;
945eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
94688e51032320ab92057666e1230cc6548ca163c51Alan Viverette    }
947eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
94888e51032320ab92057666e1230cc6548ca163c51Alan Viverette    private boolean getInnerCircleFromXY(float x, float y) {
94988e51032320ab92057666e1230cc6548ca163c51Alan Viverette        if (mIs24HourMode && mShowHours) {
95088e51032320ab92057666e1230cc6548ca163c51Alan Viverette            final double dX = x - mXCenter;
95188e51032320ab92057666e1230cc6548ca163c51Alan Viverette            final double dY = y - mYCenter;
95288e51032320ab92057666e1230cc6548ca163c51Alan Viverette            final double distFromCenter = Math.sqrt(dX * dX + dY * dY);
95388e51032320ab92057666e1230cc6548ca163c51Alan Viverette            return distFromCenter <= mHalfwayDist;
954eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
95588e51032320ab92057666e1230cc6548ca163c51Alan Viverette        return false;
956eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
957eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
958002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette    boolean mChangedDuringTouch = false;
959002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
960eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    @Override
961ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    public boolean onTouchEvent(MotionEvent event) {
962002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        if (!mInputEnabled) {
963eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            return true;
964eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
965eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
966002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        final int action = event.getActionMasked();
967002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        if (action == MotionEvent.ACTION_MOVE
968002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                || action == MotionEvent.ACTION_UP
969002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                || action == MotionEvent.ACTION_DOWN) {
970002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            boolean forceSelection = false;
971002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            boolean autoAdvance = false;
972002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
973002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            if (action == MotionEvent.ACTION_DOWN) {
974002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                // This is a new event stream, reset whether the value changed.
975002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                mChangedDuringTouch = false;
976002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            } else if (action == MotionEvent.ACTION_UP) {
977002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                autoAdvance = true;
978002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
979002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                // If we saw a down/up pair without the value changing, assume
980002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                // this is a single-tap selection and force a change.
981002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                if (!mChangedDuringTouch) {
982002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                    forceSelection = true;
983eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                }
984002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            }
985eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
986002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            mChangedDuringTouch |= handleTouchInput(
987002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                    event.getX(), event.getY(), forceSelection, autoAdvance);
988eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
989002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
990002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        return true;
991002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette    }
992002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
993002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette    private boolean handleTouchInput(
994002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            float x, float y, boolean forceSelection, boolean autoAdvance) {
99588e51032320ab92057666e1230cc6548ca163c51Alan Viverette        final boolean isOnInnerCircle = getInnerCircleFromXY(x, y);
996ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final int degrees = getDegreesFromXY(x, y, false);
997002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        if (degrees == -1) {
998002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            return false;
999002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        }
1000002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
10012b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        // Ensure we're showing the correct picker.
10022b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette        animatePicker(mShowHours, ANIM_DURATION_TOUCH);
10032b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette
100466a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette        final @PickerType int type;
100511a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette        final int newValue;
100611a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette        final boolean valueChanged;
1007002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
1008002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        if (mShowHours) {
1009002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            final int snapDegrees = snapOnly30s(degrees, 0) % 360;
101088e51032320ab92057666e1230cc6548ca163c51Alan Viverette            valueChanged = mIsOnInnerCircle != isOnInnerCircle
101188e51032320ab92057666e1230cc6548ca163c51Alan Viverette                    || mSelectionDegrees[HOURS] != snapDegrees;
101288e51032320ab92057666e1230cc6548ca163c51Alan Viverette            mIsOnInnerCircle = isOnInnerCircle;
101388e51032320ab92057666e1230cc6548ca163c51Alan Viverette            mSelectionDegrees[HOURS] = snapDegrees;
101411a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            type = HOURS;
101511a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            newValue = getCurrentHour();
1016002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        } else {
1017002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            final int snapDegrees = snapPrefer30s(degrees) % 360;
101888e51032320ab92057666e1230cc6548ca163c51Alan Viverette            valueChanged = mSelectionDegrees[MINUTES] != snapDegrees;
101988e51032320ab92057666e1230cc6548ca163c51Alan Viverette            mSelectionDegrees[MINUTES] = snapDegrees;
102011a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            type = MINUTES;
102111a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            newValue = getCurrentMinute();
1022002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        }
1023002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
102411a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette        if (valueChanged || forceSelection || autoAdvance) {
102511a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            // Fire the listener even if we just need to auto-advance.
1026002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            if (mListener != null) {
1027002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                mListener.onValueSelected(type, newValue, autoAdvance);
1028002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            }
102911a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette
103011a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            // Only provide feedback if the value actually changed.
103111a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            if (valueChanged || forceSelection) {
103211a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette                performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
103311a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette                invalidate();
103411a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            }
1035002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            return true;
1036002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        }
1037002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
1038002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        return false;
1039eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
1040eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1041eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    @Override
1042ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    public boolean dispatchHoverEvent(MotionEvent event) {
1043ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        // First right-of-refusal goes the touch exploration helper.
1044ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (mTouchHelper.dispatchHoverEvent(event)) {
1045eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            return true;
1046eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1047ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return super.dispatchHoverEvent(event);
1048eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
1049eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1050ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    public void setInputEnabled(boolean inputEnabled) {
1051ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        mInputEnabled = inputEnabled;
1052ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        invalidate();
1053ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
1054ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1055ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private class RadialPickerTouchHelper extends ExploreByTouchHelper {
1056ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final Rect mTempRect = new Rect();
1057ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1058ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int TYPE_HOUR = 1;
1059ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int TYPE_MINUTE = 2;
1060ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1061ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int SHIFT_TYPE = 0;
1062ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int MASK_TYPE = 0xF;
1063ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1064ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int SHIFT_VALUE = 8;
1065ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int MASK_VALUE = 0xFF;
1066ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1067ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        /** Increment in which virtual views are exposed for minutes. */
1068ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int MINUTE_INCREMENT = 5;
1069ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1070ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        public RadialPickerTouchHelper() {
1071ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            super(RadialTimePickerView.this);
1072eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1073eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1074ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1075ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
1076ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            super.onInitializeAccessibilityNodeInfo(host, info);
1077ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1078ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
1079ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
1080eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1081ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1082ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1083ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
1084ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (super.performAccessibilityAction(host, action, arguments)) {
1085ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                return true;
1086eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
1087eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1088ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            switch (action) {
1089ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
1090ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    adjustPicker(1);
1091ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    return true;
1092ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
1093ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    adjustPicker(-1);
1094ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    return true;
1095ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1096ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1097ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return false;
1098ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1099ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1100ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private void adjustPicker(int step) {
1101ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int stepSize;
11024a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette            final int initialStep;
110367945c11a5e9547f71be91ceb99e7b9ff15a6292Alan Viverette            final int maxValue;
1104ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int minValue;
1105eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if (mShowHours) {
11064a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                stepSize = 1;
1107ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
11084a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                final int currentHour24 = getCurrentHour();
1109eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                if (mIs24HourMode) {
11104a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                    initialStep = currentHour24;
1111ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    minValue = 0;
11124a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                    maxValue = 23;
1113eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                } else {
11144a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                    initialStep = hour24To12(currentHour24);
1115eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    minValue = 1;
11164a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                    maxValue = 12;
1117eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                }
1118eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            } else {
11194a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                stepSize = 5;
11204a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                initialStep = getCurrentMinute() / stepSize;
1121ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                minValue = 0;
11224a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette                maxValue = 55;
1123eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
1124ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
11254a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette            final int nextValue = (initialStep + step) * stepSize;
11264a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette            final int clampedValue = MathUtils.constrain(nextValue, minValue, maxValue);
1127ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (mShowHours) {
1128ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                setCurrentHour(clampedValue);
1129ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1130ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                setCurrentMinute(clampedValue);
1131ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1132ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1133ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1134ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1135ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected int getVirtualViewAt(float x, float y) {
1136ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int id;
1137d8c2af55420d869c140278e6643d16cd90cfeb51Alan Viverette            final int degrees = getDegreesFromXY(x, y, true);
1138ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (degrees != -1) {
1139ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int snapDegrees = snapOnly30s(degrees, 0) % 360;
1140ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                if (mShowHours) {
114188e51032320ab92057666e1230cc6548ca163c51Alan Viverette                    final boolean isOnInnerCircle = getInnerCircleFromXY(x, y);
11425efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette                    final int hour24 = getHourForDegrees(snapDegrees, isOnInnerCircle);
11435efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette                    final int hour = mIs24HourMode ? hour24 : hour24To12(hour24);
1144ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    id = makeId(TYPE_HOUR, hour);
1145ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                } else {
1146ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    final int current = getCurrentMinute();
1147ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    final int touched = getMinuteForDegrees(degrees);
1148ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    final int snapped = getMinuteForDegrees(snapDegrees);
1149ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1150ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    // If the touched minute is closer to the current minute
1151ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    // than it is to the snapped minute, return current.
1152fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette                    final int currentOffset = getCircularDiff(current, touched, MINUTES_IN_CIRCLE);
1153fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette                    final int snappedOffset = getCircularDiff(snapped, touched, MINUTES_IN_CIRCLE);
1154ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    final int minute;
115588e51032320ab92057666e1230cc6548ca163c51Alan Viverette                    if (currentOffset < snappedOffset) {
1156ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                        minute = current;
1157ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    } else {
1158ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                        minute = snapped;
1159ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    }
1160ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    id = makeId(TYPE_MINUTE, minute);
1161ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                }
1162ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1163ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                id = INVALID_ID;
1164eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
1165ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1166ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return id;
1167ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1168ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
116988e51032320ab92057666e1230cc6548ca163c51Alan Viverette        /**
117088e51032320ab92057666e1230cc6548ca163c51Alan Viverette         * Returns the difference in degrees between two values along a circle.
117188e51032320ab92057666e1230cc6548ca163c51Alan Viverette         *
117288e51032320ab92057666e1230cc6548ca163c51Alan Viverette         * @param first value in the range [0,max]
117388e51032320ab92057666e1230cc6548ca163c51Alan Viverette         * @param second value in the range [0,max]
117488e51032320ab92057666e1230cc6548ca163c51Alan Viverette         * @param max the maximum value along the circle
117588e51032320ab92057666e1230cc6548ca163c51Alan Viverette         * @return the difference in between the two values
117688e51032320ab92057666e1230cc6548ca163c51Alan Viverette         */
117788e51032320ab92057666e1230cc6548ca163c51Alan Viverette        private int getCircularDiff(int first, int second, int max) {
117888e51032320ab92057666e1230cc6548ca163c51Alan Viverette            final int diff = Math.abs(first - second);
117988e51032320ab92057666e1230cc6548ca163c51Alan Viverette            final int midpoint = max / 2;
118088e51032320ab92057666e1230cc6548ca163c51Alan Viverette            return (diff > midpoint) ? (max - diff) : diff;
118188e51032320ab92057666e1230cc6548ca163c51Alan Viverette        }
118288e51032320ab92057666e1230cc6548ca163c51Alan Viverette
1183ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1184ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected void getVisibleVirtualViews(IntArray virtualViewIds) {
1185eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if (mShowHours) {
1186ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int min = mIs24HourMode ? 0 : 1;
1187ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int max = mIs24HourMode ? 23 : 12;
1188ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                for (int i = min; i <= max ; i++) {
1189ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    virtualViewIds.add(makeId(TYPE_HOUR, i));
1190eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                }
1191eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            } else {
1192ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int current = getCurrentMinute();
1193fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette                for (int i = 0; i < MINUTES_IN_CIRCLE; i += MINUTE_INCREMENT) {
1194ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    virtualViewIds.add(makeId(TYPE_MINUTE, i));
1195ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1196ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    // If the current minute falls between two increments,
1197ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    // insert an extra node for it.
1198ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    if (current > i && current < i + MINUTE_INCREMENT) {
1199ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                        virtualViewIds.add(makeId(TYPE_MINUTE, current));
1200ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    }
1201eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                }
1202eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
1203eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1204eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1205ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1206ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
1207ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            event.setClassName(getClass().getName());
1208eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1209ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int type = getTypeFromId(virtualViewId);
1210ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int value = getValueFromId(virtualViewId);
1211ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final CharSequence description = getVirtualViewDescription(type, value);
1212ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            event.setContentDescription(description);
1213ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1214ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1215ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1216ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) {
1217ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            node.setClassName(getClass().getName());
1218ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            node.addAction(AccessibilityAction.ACTION_CLICK);
1219ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1220ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int type = getTypeFromId(virtualViewId);
1221ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int value = getValueFromId(virtualViewId);
1222ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final CharSequence description = getVirtualViewDescription(type, value);
1223ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            node.setContentDescription(description);
1224ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1225ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            getBoundsForVirtualView(virtualViewId, mTempRect);
1226ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            node.setBoundsInParent(mTempRect);
1227ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1228ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final boolean selected = isVirtualViewSelected(type, value);
1229ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            node.setSelected(selected);
12303fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette
12313fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            final int nextId = getVirtualViewIdAfter(type, value);
12323fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            if (nextId != INVALID_ID) {
12333fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                node.setTraversalBefore(RadialTimePickerView.this, nextId);
12343fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            }
12353fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette        }
12363fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette
12373fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette        private int getVirtualViewIdAfter(int type, int value) {
12383fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            if (type == TYPE_HOUR) {
12393fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                final int nextValue = value + 1;
12403fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                final int max = mIs24HourMode ? 23 : 12;
12413fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                if (nextValue <= max) {
12423fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                    return makeId(type, nextValue);
12433fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                }
12443fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            } else if (type == TYPE_MINUTE) {
12453fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                final int current = getCurrentMinute();
12463fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                final int snapValue = value - (value % MINUTE_INCREMENT);
12473fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                final int nextValue = snapValue + MINUTE_INCREMENT;
12483fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                if (value < current && nextValue > current) {
12493fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                    // The current value is between two snap values.
12503fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                    return makeId(type, current);
1251fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette                } else if (nextValue < MINUTES_IN_CIRCLE) {
12523fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                    return makeId(type, nextValue);
12533fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                }
12543fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            }
12553fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            return INVALID_ID;
1256ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1257ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1258ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1259ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
1260ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                Bundle arguments) {
1261ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (action == AccessibilityNodeInfo.ACTION_CLICK) {
1262ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int type = getTypeFromId(virtualViewId);
1263ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int value = getValueFromId(virtualViewId);
1264ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                if (type == TYPE_HOUR) {
1265ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    final int hour = mIs24HourMode ? value : hour12To24(value, mAmOrPm);
1266ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    setCurrentHour(hour);
1267ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    return true;
1268ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                } else if (type == TYPE_MINUTE) {
1269ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    setCurrentMinute(value);
1270ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    return true;
1271ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                }
1272ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1273ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return false;
1274ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1275ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1276ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private int hour12To24(int hour12, int amOrPm) {
1277ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            int hour24 = hour12;
1278ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (hour12 == 12) {
1279ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                if (amOrPm == AM) {
1280ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    hour24 = 0;
1281ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                }
1282ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else if (amOrPm == PM) {
1283ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                hour24 += 12;
1284ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1285ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return hour24;
1286ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1287ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
12885efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette        private int hour24To12(int hour24) {
12895efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette            if (hour24 == 0) {
12905efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette                return 12;
12915efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette            } else if (hour24 > 12) {
12925efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette                return hour24 - 12;
12935efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette            } else {
12945efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette                return hour24;
12955efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette            }
12965efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette        }
12975efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette
1298ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private void getBoundsForVirtualView(int virtualViewId, Rect bounds) {
1299ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final float radius;
1300ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int type = getTypeFromId(virtualViewId);
1301ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int value = getValueFromId(virtualViewId);
1302ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final float centerRadius;
1303ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final float degrees;
1304ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (type == TYPE_HOUR) {
130588e51032320ab92057666e1230cc6548ca163c51Alan Viverette                final boolean innerCircle = getInnerCircleForHour(value);
1306ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                if (innerCircle) {
1307adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette                    centerRadius = mCircleRadius - mTextInset[HOURS_INNER];
1308adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette                    radius = mSelectorRadius;
1309ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                } else {
1310adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette                    centerRadius = mCircleRadius - mTextInset[HOURS];
1311adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette                    radius = mSelectorRadius;
1312ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                }
1313ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1314ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                degrees = getDegreesForHour(value);
1315ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else if (type == TYPE_MINUTE) {
1316adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette                centerRadius = mCircleRadius - mTextInset[MINUTES];
1317ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                degrees = getDegreesForMinute(value);
1318adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette                radius = mSelectorRadius;
1319ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1320ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                // This should never happen.
1321ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                centerRadius = 0;
1322ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                degrees = 0;
1323ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                radius = 0;
1324ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1325ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1326ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final double radians = Math.toRadians(degrees);
1327ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final float xCenter = mXCenter + centerRadius * (float) Math.sin(radians);
1328ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final float yCenter = mYCenter - centerRadius * (float) Math.cos(radians);
1329ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1330ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            bounds.set((int) (xCenter - radius), (int) (yCenter - radius),
1331ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    (int) (xCenter + radius), (int) (yCenter + radius));
1332ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1333ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1334ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private CharSequence getVirtualViewDescription(int type, int value) {
1335ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final CharSequence description;
1336ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (type == TYPE_HOUR || type == TYPE_MINUTE) {
1337ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                description = Integer.toString(value);
1338ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1339ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                description = null;
1340ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1341ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return description;
1342ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1343ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1344ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private boolean isVirtualViewSelected(int type, int value) {
1345ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final boolean selected;
1346ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (type == TYPE_HOUR) {
1347ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                selected = getCurrentHour() == value;
1348ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else if (type == TYPE_MINUTE) {
1349ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                selected = getCurrentMinute() == value;
1350ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1351ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                selected = false;
1352ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1353ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return selected;
1354ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1355ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1356ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private int makeId(int type, int value) {
1357ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return type << SHIFT_TYPE | value << SHIFT_VALUE;
1358ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1359ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1360ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private int getTypeFromId(int id) {
1361ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return id >>> SHIFT_TYPE & MASK_TYPE;
1362ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1363ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1364ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private int getValueFromId(int id) {
1365ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return id >>> SHIFT_VALUE & MASK_VALUE;
1366ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1367eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
1368eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio}
1369