RadialTimePickerView.java revision d8c2af55420d869c140278e6643d16cd90cfeb51
1eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio/*
2eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Copyright (C) 2013 The Android Open Source Project
3eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio *
4eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Licensed under the Apache License, Version 2.0 (the "License");
5eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * you may not use this file except in compliance with the License.
6eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * You may obtain a copy of the License at
7eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio *
8eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio *      http://www.apache.org/licenses/LICENSE-2.0
9eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio *
10eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Unless required by applicable law or agreed to in writing, software
11eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * distributed under the License is distributed on an "AS IS" BASIS,
12eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * See the License for the specific language governing permissions and
14eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * limitations under the License.
15eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */
16eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
17eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Megliopackage android.widget;
18eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
19eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.animation.Animator;
20eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.animation.AnimatorSet;
21eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.animation.Keyframe;
22eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.animation.ObjectAnimator;
23eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.animation.PropertyValuesHolder;
24eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.animation.ValueAnimator;
25eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.content.Context;
26ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.content.res.ColorStateList;
27eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.content.res.Resources;
28eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.content.res.TypedArray;
29eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Canvas;
30eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Color;
31eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Paint;
32ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.graphics.Path;
33ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.graphics.Rect;
34ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.graphics.Region;
35eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Typeface;
36eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.os.Bundle;
37eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.util.AttributeSet;
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.ViewGroup;
47eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.accessibility.AccessibilityEvent;
48eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.accessibility.AccessibilityNodeInfo;
49ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
50eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
51eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport com.android.internal.R;
52ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport com.android.internal.widget.ExploreByTouchHelper;
53eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
54eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport java.util.ArrayList;
55eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport java.util.Calendar;
56eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport java.util.Locale;
57eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
58eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio/**
59eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * View to show a clock circle picker (with one or two picking circles)
60eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio *
61eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * @hide
62eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */
63ec9fe1ad663d59829075029508673b2e913538d3Alan Viverettepublic class RadialTimePickerView extends View {
64ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private static final String TAG = "RadialTimePickerView";
65eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
66eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final boolean DEBUG = false;
67eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
68eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int DEBUG_COLOR = 0x20FF0000;
69eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int DEBUG_TEXT_COLOR = 0x60FF0000;
70eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int DEBUG_STROKE_WIDTH = 2;
71eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
72eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int HOURS = 0;
73eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int MINUTES = 1;
74eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int HOURS_INNER = 2;
75eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
76eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int SELECTOR_CIRCLE = 0;
77eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int SELECTOR_DOT = 1;
78eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int SELECTOR_LINE = 2;
79eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
80eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int AM = 0;
81eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int PM = 1;
82eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
83eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    // Opaque alpha level
84eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int ALPHA_OPAQUE = 255;
85eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
86eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    // Transparent alpha level
87eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int ALPHA_TRANSPARENT = 0;
88eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
89eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int DEGREES_FOR_ONE_HOUR = 30;
90eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int DEGREES_FOR_ONE_MINUTE = 6;
91eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
92eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int[] HOURS_NUMBERS = {12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
93eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int[] HOURS_NUMBERS_24 = {0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23};
94eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int[] MINUTES_NUMBERS = {0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55};
95eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
96eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static final int CENTER_RADIUS = 2;
97eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
98ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private static final int FADE_OUT_DURATION = 500;
99ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private static final int FADE_IN_DURATION = 500;
100ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
101ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private static final int[] SNAP_PREFER_30S_MAP = new int[361];
102ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
103ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private static final int NUM_POSITIONS = 12;
104ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private static final float[] COS_30 = new float[NUM_POSITIONS];
105ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private static final float[] SIN_30 = new float[NUM_POSITIONS];
106ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
107ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    static {
108ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        // Prepare mapping to snap touchable degrees to selectable degrees.
109ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        preparePrefer30sMap();
110ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
111ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final double increment = 2.0 * Math.PI / NUM_POSITIONS;
112ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        double angle = Math.PI / 2.0;
113ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        for (int i = 0; i < NUM_POSITIONS; i++) {
114ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            COS_30[i] = (float) Math.cos(angle);
115ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            SIN_30[i] = (float) Math.sin(angle);
116ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            angle += increment;
117ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        }
118ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    }
119eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
120ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final InvalidateUpdateListener mInvalidateUpdateListener =
121ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            new InvalidateUpdateListener();
122ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
123eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final String[] mHours12Texts = new String[12];
124eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final String[] mOuterHours24Texts = new String[12];
125eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final String[] mInnerHours24Texts = new String[12];
126eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final String[] mMinutesTexts = new String[12];
127eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
128eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final Paint[] mPaint = new Paint[2];
129eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette    private final IntHolder[] mAlpha = new IntHolder[2];
130eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
131eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final Paint mPaintCenter = new Paint();
132eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
133eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final Paint[][] mPaintSelector = new Paint[2][3];
134eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette    private final int[][] mColorSelector = new int[2][3];
135eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette    private final IntHolder[][] mAlphaSelector = new IntHolder[2][3];
136eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
137eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final Paint mPaintBackground = new Paint();
138eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private final Paint mPaintDebug = new Paint();
139eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
140ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final Typeface mTypeface;
141ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
142ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final float[] mTextSize = new float[2];
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
150ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final float[] mNumbersRadiusMultiplier = new float[3];
151ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
152ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final float[] mTextSizeMultiplier = new float[3];
153ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
154ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final int[] mLineLength = new int[3];
155ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final int[] mSelectionRadius = new int[3];
156ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final float mSelectionRadiusMultiplier;
157ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final int[] mSelectionDegrees = new int[3];
158ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
159ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final ArrayList<Animator> mHoursToMinutesAnims = new ArrayList<Animator>();
160ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final ArrayList<Animator> mMinuteToHoursAnims = new ArrayList<Animator>();
161ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
162ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private final RadialPickerTouchHelper mTouchHelper;
163ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
164ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private ColorStateList mNumbersTextColor;
165eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
166eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private boolean mIs24HourMode;
167eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private boolean mShowHours;
168d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette
169d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette    /**
170d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * When in 24-hour mode, indicates that the current hour is between
171d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * 1 and 12 (inclusive).
172d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     */
173eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private boolean mIsOnInnerCircle;
174eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
175ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private float mXCenter;
176ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private float mYCenter;
177ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private float mCircleRadius;
178eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
179eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private int mMinHypotenuseForInnerNumber;
180eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private int mMaxHypotenuseForOuterNumber;
181eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private int mHalfwayHypotenusePoint;
182eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
183eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private String[] mOuterTextHours;
184eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private String[] mInnerTextHours;
185eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private String[] mOuterTextMinutes;
186eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private AnimatorSet mTransition;
187eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
188eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private int mAmOrPm;
1895134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette    private int mDisabledAlpha;
1905134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette
191eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private OnValueSelectedListener mListener;
192eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
193ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private boolean mInputEnabled = true;
194eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
195eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public interface OnValueSelectedListener {
196eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance);
197eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
198eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
199eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    /**
200eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * Split up the 360 degrees of the circle among the 60 selectable values. Assigns a larger
201eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * selectable area to each of the 12 visible values, such that the ratio of space apportioned
202eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * to a visible value : space apportioned to a non-visible value will be 14 : 4.
203eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * E.g. the output of 30 degrees should have a higher range of input associated with it than
204eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * the output of 24 degrees, because 30 degrees corresponds to a visible number on the clock
205eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * circle (5 on the minutes, 1 or 13 on the hours).
206eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     */
207eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static void preparePrefer30sMap() {
208eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // We'll split up the visible output and the non-visible output such that each visible
209eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // output will correspond to a range of 14 associated input degrees, and each non-visible
210eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // output will correspond to a range of 4 associate input degrees, so visible numbers
211eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // are more than 3 times easier to get than non-visible numbers:
212eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // {354-359,0-7}:0, {8-11}:6, {12-15}:12, {16-19}:18, {20-23}:24, {24-37}:30, etc.
213eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        //
214eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // If an output of 30 degrees should correspond to a range of 14 associated degrees, then
215eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // we'll need any input between 24 - 37 to snap to 30. Working out from there, 20-23 should
216eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // snap to 24, while 38-41 should snap to 36. This is somewhat counter-intuitive, that you
217eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // can be touching 36 degrees but have the selection snapped to 30 degrees; however, this
218eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // inconsistency isn't noticeable at such fine-grained degrees, and it affords us the
219eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // ability to aggressively prefer the visible values by a factor of more than 3:1, which
220eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // greatly contributes to the selectability of these values.
221eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
222eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // The first output is 0, and each following output will increment by 6 {0, 6, 12, ...}.
223eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int snappedOutputDegrees = 0;
224eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Count of how many inputs we've designated to the specified output.
225eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int count = 1;
226eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // How many input we expect for a specified output. This will be 14 for output divisible
227eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // by 30, and 4 for the remaining output. We'll special case the outputs of 0 and 360, so
228eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // the caller can decide which they need.
229eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int expectedCount = 8;
230eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Iterate through the input.
231eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        for (int degrees = 0; degrees < 361; degrees++) {
232eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            // Save the input-output mapping.
233ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            SNAP_PREFER_30S_MAP[degrees] = snappedOutputDegrees;
234eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            // If this is the last input for the specified output, calculate the next output and
235eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            // the next expected count.
236eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if (count == expectedCount) {
237eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                snappedOutputDegrees += 6;
238eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                if (snappedOutputDegrees == 360) {
239eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    expectedCount = 7;
240eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                } else if (snappedOutputDegrees % 30 == 0) {
241eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    expectedCount = 14;
242eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                } else {
243eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    expectedCount = 4;
244eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                }
245eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                count = 1;
246eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            } else {
247eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                count++;
248eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
249eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
250eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
251eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
252eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    /**
253eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * Returns mapping of any input degrees (0 to 360) to one of 60 selectable output degrees,
254eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * where the degrees corresponding to visible numbers (i.e. those divisible by 30) will be
255eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * weighted heavier than the degrees corresponding to non-visible numbers.
256eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * See {@link #preparePrefer30sMap()} documentation for the rationale and generation of the
257eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * mapping.
258eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     */
259eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static int snapPrefer30s(int degrees) {
260ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        if (SNAP_PREFER_30S_MAP == null) {
261eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            return -1;
262eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
263ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        return SNAP_PREFER_30S_MAP[degrees];
264eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
265eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
266eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    /**
267eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * Returns mapping of any input degrees (0 to 360) to one of 12 visible output degrees (all
268eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * multiples of 30), where the input will be "snapped" to the closest visible degrees.
269eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * @param degrees The input degrees
270eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * @param forceHigherOrLower The output may be forced to either the higher or lower step, or may
271eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * be allowed to snap to whichever is closer. Use 1 to force strictly higher, -1 to force
272eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * strictly lower, and 0 to snap to the closer one.
273eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * @return output degrees, will be a multiple of 30
274eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     */
275eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private static int snapOnly30s(int degrees, int forceHigherOrLower) {
276eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int stepSize = DEGREES_FOR_ONE_HOUR;
277eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        int floor = (degrees / stepSize) * stepSize;
278eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int ceiling = floor + stepSize;
279eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (forceHigherOrLower == 1) {
280eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            degrees = ceiling;
281eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        } else if (forceHigherOrLower == -1) {
282eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if (degrees == floor) {
283eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                floor -= stepSize;
284eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
285eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            degrees = floor;
286eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        } else {
287eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if ((degrees - floor) < (ceiling - degrees)) {
288eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                degrees = floor;
289eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            } else {
290eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                degrees = ceiling;
291eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
292eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
293eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        return degrees;
294eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
295eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
296ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    @SuppressWarnings("unused")
297ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    public RadialTimePickerView(Context context)  {
298ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        this(context, null);
299ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
300ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
301eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public RadialTimePickerView(Context context, AttributeSet attrs)  {
302eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        this(context, attrs, R.attr.timePickerStyle);
303eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
304eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
305ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    public RadialTimePickerView(Context context, AttributeSet attrs, int defStyleAttr)  {
306ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        this(context, attrs, defStyleAttr, 0);
307ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
308ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
309ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    public RadialTimePickerView(
310ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)  {
311eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        super(context, attrs);
312eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
3135134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette        // Pull disabled alpha from theme.
3145134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette        final TypedValue outValue = new TypedValue();
3155134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette        context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true);
3165134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette        mDisabledAlpha = (int) (outValue.getFloat() * 255 + 0.5f);
3175134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette
318eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // process style attributes
3195134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette        final Resources res = getResources();
320eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.TimePicker,
321ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                defStyleAttr, defStyleRes);
322eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
323eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mTypeface = Typeface.create("sans-serif", Typeface.NORMAL);
324eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
325eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        // Initialize all alpha values to opaque.
326eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        for (int i = 0; i < mAlpha.length; i++) {
327eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mAlpha[i] = new IntHolder(ALPHA_OPAQUE);
328eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        }
329eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        for (int i = 0; i < mAlphaSelector.length; i++) {
330eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            for (int j = 0; j < mAlphaSelector[i].length; j++) {
331eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette                mAlphaSelector[i][j] = new IntHolder(ALPHA_OPAQUE);
332eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            }
333eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        }
334eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
335ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mNumbersTextColor = a.getColorStateList(
336ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                R.styleable.TimePicker_numbersTextColor);
337eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
338eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[HOURS] = new Paint();
339eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[HOURS].setAntiAlias(true);
340eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[HOURS].setTextAlign(Paint.Align.CENTER);
341eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
342eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[MINUTES] = new Paint();
343eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[MINUTES].setAntiAlias(true);
344eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaint[MINUTES].setTextAlign(Paint.Align.CENTER);
345eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
346ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final ColorStateList selectorColors = a.getColorStateList(
347ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                R.styleable.TimePicker_numbersSelectorColor);
348ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final int selectorActivatedColor = selectorColors.getColorForState(
349ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0);
350ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
351ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mPaintCenter.setColor(selectorActivatedColor);
352eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintCenter.setAntiAlias(true);
353ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
354ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final int textActivatedColor = mNumbersTextColor.getColorForState(
355ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0);
356eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
357eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[HOURS][SELECTOR_CIRCLE] = new Paint();
358eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[HOURS][SELECTOR_CIRCLE].setAntiAlias(true);
359ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mColorSelector[HOURS][SELECTOR_CIRCLE] = selectorActivatedColor;
360eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
361eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[HOURS][SELECTOR_DOT] = new Paint();
362eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[HOURS][SELECTOR_DOT].setAntiAlias(true);
363ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mColorSelector[HOURS][SELECTOR_DOT] = textActivatedColor;
364eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
365eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[HOURS][SELECTOR_LINE] = new Paint();
366eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[HOURS][SELECTOR_LINE].setAntiAlias(true);
367eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[HOURS][SELECTOR_LINE].setStrokeWidth(2);
368ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mColorSelector[HOURS][SELECTOR_LINE] = selectorActivatedColor;
369eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
370eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[MINUTES][SELECTOR_CIRCLE] = new Paint();
371eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[MINUTES][SELECTOR_CIRCLE].setAntiAlias(true);
372ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mColorSelector[MINUTES][SELECTOR_CIRCLE] = selectorActivatedColor;
373eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
374eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[MINUTES][SELECTOR_DOT] = new Paint();
375eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[MINUTES][SELECTOR_DOT].setAntiAlias(true);
376ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mColorSelector[MINUTES][SELECTOR_DOT] = textActivatedColor;
377eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
378eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[MINUTES][SELECTOR_LINE] = new Paint();
379eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[MINUTES][SELECTOR_LINE].setAntiAlias(true);
380eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintSelector[MINUTES][SELECTOR_LINE].setStrokeWidth(2);
381ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mColorSelector[MINUTES][SELECTOR_LINE] = selectorActivatedColor;
382eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
383eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        mPaintBackground.setColor(a.getColor(R.styleable.TimePicker_numbersBackgroundColor,
384ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                context.getColor(R.color.timepicker_default_numbers_background_color_material)));
385eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mPaintBackground.setAntiAlias(true);
386eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
387eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (DEBUG) {
388eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mPaintDebug.setColor(DEBUG_COLOR);
389eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mPaintDebug.setAntiAlias(true);
390eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mPaintDebug.setStrokeWidth(DEBUG_STROKE_WIDTH);
391eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mPaintDebug.setStyle(Paint.Style.STROKE);
392eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mPaintDebug.setTextAlign(Paint.Align.CENTER);
393eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
394eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
395eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mShowHours = true;
396eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mIs24HourMode = false;
397eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mAmOrPm = AM;
398eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
399ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        // Set up accessibility components.
400ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        mTouchHelper = new RadialPickerTouchHelper();
401ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setAccessibilityDelegate(mTouchHelper);
402ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
403ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
404ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
405ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
406ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
407eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        initHoursAndMinutesText();
408eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        initData();
409eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
410ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mSelectionRadiusMultiplier = res.getFloat(R.dimen.timepicker_selection_radius_multiplier);
411eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
412eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        a.recycle();
413eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
414eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Initial values
415eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final Calendar calendar = Calendar.getInstance(Locale.getDefault());
416eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int currentHour = calendar.get(Calendar.HOUR_OF_DAY);
417eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int currentMinute = calendar.get(Calendar.MINUTE);
418eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
419ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentHourInternal(currentHour, false, false);
420ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentMinuteInternal(currentMinute, false);
421eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
422eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        setHapticFeedbackEnabled(true);
423eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
424eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
425eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void initialize(int hour, int minute, boolean is24HourMode) {
426448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette        if (mIs24HourMode != is24HourMode) {
427448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette            mIs24HourMode = is24HourMode;
428448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette            initData();
429448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette        }
430ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
431ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentHourInternal(hour, false, false);
432ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentMinuteInternal(minute, false);
433eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
434eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
435eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void setCurrentItemShowing(int item, boolean animate) {
436eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        switch (item){
437eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            case HOURS:
438eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                showHours(animate);
439eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                break;
440eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            case MINUTES:
441eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                showMinutes(animate);
442eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                break;
443eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            default:
444eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                Log.e(TAG, "ClockView does not support showing item " + item);
445eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
446eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
447eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
448eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public int getCurrentItemShowing() {
449eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        return mShowHours ? HOURS : MINUTES;
450eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
451eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
452eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void setOnValueSelectedListener(OnValueSelectedListener listener) {
453eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mListener = listener;
454eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
455eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
456d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette    /**
457d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * Sets the current hour in 24-hour time.
458d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     *
459d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * @param hour the current hour between 0 and 23 (inclusive)
460d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     */
461eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void setCurrentHour(int hour) {
462ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentHourInternal(hour, true, false);
463ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
464ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
465ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    /**
466ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     * Sets the current hour.
467ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     *
468ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     * @param hour The current hour
469ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     * @param callback Whether the value listener should be invoked
470ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     * @param autoAdvance Whether the listener should auto-advance to the next
471ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     *                    selection mode, e.g. hour to minutes
472ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette     */
473ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private void setCurrentHourInternal(int hour, boolean callback, boolean autoAdvance) {
474eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int degrees = (hour % 12) * DEGREES_FOR_ONE_HOUR;
475eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mSelectionDegrees[HOURS] = degrees;
476eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mSelectionDegrees[HOURS_INNER] = degrees;
477d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette
478d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette        // 0 is 12 AM (midnight) and 12 is 12 PM (noon).
479ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        final int amOrPm = (hour == 0 || (hour % 24) < 12) ? AM : PM;
480ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        final boolean isOnInnerCircle = mIs24HourMode && hour >= 1 && hour <= 12;
481ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (mAmOrPm != amOrPm || mIsOnInnerCircle != isOnInnerCircle) {
482ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            mAmOrPm = amOrPm;
483ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            mIsOnInnerCircle = isOnInnerCircle;
484ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
485ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            initData();
486ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            mTouchHelper.invalidateRoot();
487ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
488d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette
489eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        invalidate();
490ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
491ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (callback && mListener != null) {
492ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            mListener.onValueSelected(HOURS, hour, autoAdvance);
493ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
494eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
495eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
496d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette    /**
497d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * Returns the current hour in 24-hour time.
498d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     *
499d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     * @return the current hour between 0 and 23 (inclusive)
500d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette     */
501eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public int getCurrentHour() {
502ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return getHourForDegrees(
503ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                mSelectionDegrees[mIsOnInnerCircle ? HOURS_INNER : HOURS], mIsOnInnerCircle);
504ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
505ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
506ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private int getHourForDegrees(int degrees, boolean innerCircle) {
507ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        int hour = (degrees / DEGREES_FOR_ONE_HOUR) % 12;
508eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mIs24HourMode) {
509d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette            // Convert the 12-hour value into 24-hour time based on where the
510d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette            // selector is positioned.
511ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (innerCircle && hour == 0) {
512d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette                // Inner circle is 1 through 12.
513d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette                hour = 12;
514ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else if (!innerCircle && hour != 0) {
515d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette                // Outer circle is 13 through 23 and 0.
516d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette                hour += 12;
517eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
518d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette        } else if (mAmOrPm == PM) {
519d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette            hour += 12;
520eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
521d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette        return hour;
522eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
523eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
524ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private int getDegreesForHour(int hour) {
525ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        // Convert to be 0-11.
526ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (mIs24HourMode) {
527ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (hour >= 12) {
528ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                hour -= 12;
529ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
530ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        } else if (hour == 12) {
531ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            hour = 0;
532ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
533ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return hour * DEGREES_FOR_ONE_HOUR;
534ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
535ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
536eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void setCurrentMinute(int minute) {
537ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        setCurrentMinuteInternal(minute, true);
538ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
539ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
540ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private void setCurrentMinuteInternal(int minute, boolean callback) {
541eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mSelectionDegrees[MINUTES] = (minute % 60) * DEGREES_FOR_ONE_MINUTE;
542ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
543eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        invalidate();
544ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
545ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (callback && mListener != null) {
546ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            mListener.onValueSelected(MINUTES, minute, false);
547ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
548eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
549eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
550eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    // Returns minutes in 0-59 range
551eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public int getCurrentMinute() {
552ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return getMinuteForDegrees(mSelectionDegrees[MINUTES]);
553ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
554ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
555ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private int getMinuteForDegrees(int degrees) {
556ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return degrees / DEGREES_FOR_ONE_MINUTE;
557ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
558ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
559ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private int getDegreesForMinute(int minute) {
560ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return minute * DEGREES_FOR_ONE_MINUTE;
561eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
562eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
563eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void setAmOrPm(int val) {
564eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mAmOrPm = (val % 2);
565eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        invalidate();
566ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        mTouchHelper.invalidateRoot();
567eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
568eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
569eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public int getAmOrPm() {
570eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        return mAmOrPm;
571eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
572eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
573eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void showHours(boolean animate) {
574ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        if (mShowHours) {
575ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            return;
576ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        }
577ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
578eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mShowHours = true;
579ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
580eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (animate) {
581eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            startMinutesToHoursAnimation();
582eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
583ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
584eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        initData();
585eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        invalidate();
586eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
587eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
588eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void showMinutes(boolean animate) {
589ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        if (!mShowHours) {
590ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            return;
591ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        }
592ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
593eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mShowHours = false;
594ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
595eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (animate) {
596eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            startHoursToMinutesAnimation();
597eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
598ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
599eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        initData();
600eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        invalidate();
601eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
602eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
603eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void initHoursAndMinutesText() {
604eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Initialize the hours and minutes numbers.
605eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        for (int i = 0; i < 12; i++) {
606eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mHours12Texts[i] = String.format("%d", HOURS_NUMBERS[i]);
607eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mOuterHours24Texts[i] = String.format("%02d", HOURS_NUMBERS_24[i]);
608eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mInnerHours24Texts[i] = String.format("%d", HOURS_NUMBERS[i]);
609eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mMinutesTexts[i] = String.format("%02d", MINUTES_NUMBERS[i]);
610eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
611eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
612eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
613eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void initData() {
614eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mIs24HourMode) {
615eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mOuterTextHours = mOuterHours24Texts;
616eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mInnerTextHours = mInnerHours24Texts;
617eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        } else {
618eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mOuterTextHours = mHours12Texts;
619eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mInnerTextHours = null;
620eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
621eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
622eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mOuterTextMinutes = mMinutesTexts;
623eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
624eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final Resources res = getResources();
625eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
626ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        if (mIs24HourMode) {
627ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            mNumbersRadiusMultiplier[HOURS] = res.getFloat(
628ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    R.dimen.timepicker_numbers_radius_multiplier_outer);
629ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            mTextSizeMultiplier[HOURS] = res.getFloat(
630ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    R.dimen.timepicker_text_size_multiplier_outer);
631ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
632ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            mNumbersRadiusMultiplier[HOURS_INNER] = res.getFloat(
633ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    R.dimen.timepicker_numbers_radius_multiplier_inner);
634ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            mTextSizeMultiplier[HOURS_INNER] = res.getFloat(
635ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    R.dimen.timepicker_text_size_multiplier_inner);
636eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        } else {
637ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            mNumbersRadiusMultiplier[HOURS] = res.getFloat(
638ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    R.dimen.timepicker_numbers_radius_multiplier_normal);
639ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            mTextSizeMultiplier[HOURS] = res.getFloat(
640ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    R.dimen.timepicker_text_size_multiplier_normal);
641eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
642eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
643ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mNumbersRadiusMultiplier[MINUTES] = res.getFloat(
644ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                R.dimen.timepicker_numbers_radius_multiplier_normal);
645ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mTextSizeMultiplier[MINUTES] = res.getFloat(
646ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                R.dimen.timepicker_text_size_multiplier_normal);
647ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
648ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final int hoursAlpha = mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT;
649ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mAlpha[HOURS].setValue(hoursAlpha);
650ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mAlphaSelector[HOURS][SELECTOR_CIRCLE].setValue(hoursAlpha);
651ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mAlphaSelector[HOURS][SELECTOR_DOT].setValue(hoursAlpha);
652ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mAlphaSelector[HOURS][SELECTOR_LINE].setValue(hoursAlpha);
653ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
654ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final int minutesAlpha = mShowHours ? ALPHA_TRANSPARENT : ALPHA_OPAQUE;
655ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mAlpha[MINUTES].setValue(minutesAlpha);
656ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mAlphaSelector[MINUTES][SELECTOR_CIRCLE].setValue(minutesAlpha);
657ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mAlphaSelector[MINUTES][SELECTOR_DOT].setValue(minutesAlpha);
658ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mAlphaSelector[MINUTES][SELECTOR_LINE].setValue(minutesAlpha);
659eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
660eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
661eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    @Override
662eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
663ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        if (!changed) {
664ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            return;
665ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        }
666eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
667eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mXCenter = getWidth() / 2;
668eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mYCenter = getHeight() / 2;
669ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mCircleRadius = Math.min(mXCenter, mYCenter);
670eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
671ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mMinHypotenuseForInnerNumber = (int) (mCircleRadius
672eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                * mNumbersRadiusMultiplier[HOURS_INNER]) - mSelectionRadius[HOURS];
673ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mMaxHypotenuseForOuterNumber = (int) (mCircleRadius
674eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                * mNumbersRadiusMultiplier[HOURS]) + mSelectionRadius[HOURS];
675ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mHalfwayHypotenusePoint = (int) (mCircleRadius
676eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                * ((mNumbersRadiusMultiplier[HOURS] + mNumbersRadiusMultiplier[HOURS_INNER]) / 2));
677eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
678ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mTextSize[HOURS] = mCircleRadius * mTextSizeMultiplier[HOURS];
679ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mTextSize[MINUTES] = mCircleRadius * mTextSizeMultiplier[MINUTES];
680eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
681eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mIs24HourMode) {
682ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            mTextSize[HOURS_INNER] = mCircleRadius * mTextSizeMultiplier[HOURS_INNER];
683eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
684eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
685ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        calculatePositionsHours();
686ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        calculatePositionsMinutes();
687eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
688ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mSelectionRadius[HOURS] = (int) (mCircleRadius * mSelectionRadiusMultiplier);
689eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mSelectionRadius[HOURS_INNER] = mSelectionRadius[HOURS];
690ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mSelectionRadius[MINUTES] = (int) (mCircleRadius * mSelectionRadiusMultiplier);
691ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
692ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        mTouchHelper.invalidateRoot();
693eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
694eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
695eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    @Override
696eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    public void onDraw(Canvas canvas) {
6975134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette        if (!mInputEnabled) {
6985134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette            canvas.saveLayerAlpha(0, 0, getWidth(), getHeight(), mDisabledAlpha);
6995134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette        } else {
7005134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette            canvas.save();
7015134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette        }
702eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
703eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        drawCircleBackground(canvas);
704eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
705ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final int hoursAlpha = mAlpha[HOURS].getValue();
706ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        if (hoursAlpha > 0) {
707ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            // Draw the hour selector under the elements.
708ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            drawSelector(canvas, mIsOnInnerCircle ? HOURS_INNER : HOURS, null);
709ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
710ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            // Draw outer hours.
711ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            drawTextElements(canvas, mTextSize[HOURS], mTypeface, mOuterTextHours,
712ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    mOuterTextX[HOURS], mOuterTextY[HOURS], mPaint[HOURS], hoursAlpha,
713ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    !mIsOnInnerCircle, mSelectionDegrees[HOURS], false);
714ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
715ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            // Draw inner hours (12-23) for 24-hour time.
716ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            if (mIs24HourMode && mInnerTextHours != null) {
717ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                drawTextElements(canvas, mTextSize[HOURS_INNER], mTypeface, mInnerTextHours,
718ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                        mInnerTextX, mInnerTextY, mPaint[HOURS], hoursAlpha,
719ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                        mIsOnInnerCircle, mSelectionDegrees[HOURS], false);
720ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            }
721eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
722eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
723ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final int minutesAlpha = mAlpha[MINUTES].getValue();
724ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        if (minutesAlpha > 0) {
725ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            drawSelector(canvas, MINUTES, mSelectorPath);
726ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
727ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            // Exclude the selector region, then draw minutes with no
728ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            // activated states.
729ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            canvas.save(Canvas.CLIP_SAVE_FLAG);
730ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            canvas.clipPath(mSelectorPath, Region.Op.DIFFERENCE);
731ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mOuterTextMinutes,
732ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES], minutesAlpha,
733ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    false, 0, false);
734ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            canvas.restore();
735ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
736ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            // Intersect the selector region, then draw minutes with only
737ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            // activated states.
738ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            canvas.save(Canvas.CLIP_SAVE_FLAG);
739ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            canvas.clipPath(mSelectorPath, Region.Op.INTERSECT);
740ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mOuterTextMinutes,
741ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES], minutesAlpha,
742ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    true, mSelectionDegrees[MINUTES], true);
743ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            canvas.restore();
744ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        }
745eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
746eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        drawCenter(canvas);
747eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
748eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (DEBUG) {
749eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            drawDebug(canvas);
750eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
751eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
752eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        canvas.restore();
753eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
754eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
755eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void drawCircleBackground(Canvas canvas) {
756ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        canvas.drawCircle(mXCenter, mYCenter, mCircleRadius, mPaintBackground);
757eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
758eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
759eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void drawCenter(Canvas canvas) {
760eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        canvas.drawCircle(mXCenter, mYCenter, CENTER_RADIUS, mPaintCenter);
761eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
762eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
763eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette    private int getMultipliedAlpha(int argb, int alpha) {
764eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        return (int) (Color.alpha(argb) * (alpha / 255.0) + 0.5);
765eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette    }
766eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
767ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private final Path mSelectorPath = new Path();
768ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
769ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private void drawSelector(Canvas canvas, int index, Path selectorPath) {
770eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Calculate the current radius at which to place the selection circle.
771ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        mLineLength[index] = (int) (mCircleRadius * mNumbersRadiusMultiplier[index]);
772eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
773ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final double selectionRadians = Math.toRadians(mSelectionDegrees[index]);
774eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
775ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        float pointX = mXCenter + (int) (mLineLength[index] * Math.sin(selectionRadians));
776ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        float pointY = mYCenter - (int) (mLineLength[index] * Math.cos(selectionRadians));
777eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
778eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        int color;
779eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        int alpha;
780eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        Paint paint;
781eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
782eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Draw the selection circle
783eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        color = mColorSelector[index % 2][SELECTOR_CIRCLE];
784eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        alpha = mAlphaSelector[index % 2][SELECTOR_CIRCLE].getValue();
785eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        paint = mPaintSelector[index % 2][SELECTOR_CIRCLE];
786eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        paint.setColor(color);
787eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        paint.setAlpha(getMultipliedAlpha(color, alpha));
788eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        canvas.drawCircle(pointX, pointY, mSelectionRadius[index], paint);
789eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
790ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        // If needed, set up the clip path for later.
791ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        if (selectorPath != null) {
792ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            mSelectorPath.reset();
793ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            mSelectorPath.addCircle(pointX, pointY, mSelectionRadius[index], Path.Direction.CCW);
794ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        }
795ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
796ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        // Draw the dot if needed.
797ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final boolean shouldDrawDot = mSelectionDegrees[index] % 30 != 0;
798ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        if (shouldDrawDot) {
799eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            // We're not on a direct tick
800eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            color = mColorSelector[index % 2][SELECTOR_DOT];
801eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            alpha = mAlphaSelector[index % 2][SELECTOR_DOT].getValue();
802eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            paint = mPaintSelector[index % 2][SELECTOR_DOT];
803eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            paint.setColor(color);
804eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            paint.setAlpha(getMultipliedAlpha(color, alpha));
805ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            canvas.drawCircle(pointX, pointY, (mSelectionRadius[index] * 0.125f), paint);
806eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
807eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
808ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        // Shorten the line to only go to the edge of the selection circle.
809ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final int lineLength = mLineLength[index] - mSelectionRadius[index];
810ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        pointX = mXCenter + (int) (lineLength * Math.sin(selectionRadians));
811ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        pointY = mYCenter - (int) (lineLength * Math.cos(selectionRadians));
812ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
813eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Draw the line
814eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        color = mColorSelector[index % 2][SELECTOR_LINE];
815eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        alpha = mAlphaSelector[index % 2][SELECTOR_LINE].getValue();
816eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        paint = mPaintSelector[index % 2][SELECTOR_LINE];
817eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        paint.setColor(color);
818eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        paint.setAlpha(getMultipliedAlpha(color, alpha));
819eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        canvas.drawLine(mXCenter, mYCenter, pointX, pointY, paint);
820eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
821eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
822eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void drawDebug(Canvas canvas) {
823eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Draw outer numbers circle
824ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final float outerRadius = mCircleRadius * mNumbersRadiusMultiplier[HOURS];
825eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        canvas.drawCircle(mXCenter, mYCenter, outerRadius, mPaintDebug);
826eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
827eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Draw inner numbers circle
828ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final float innerRadius = mCircleRadius * mNumbersRadiusMultiplier[HOURS_INNER];
829eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        canvas.drawCircle(mXCenter, mYCenter, innerRadius, mPaintDebug);
830eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
831eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Draw outer background circle
832ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        canvas.drawCircle(mXCenter, mYCenter, mCircleRadius, mPaintDebug);
833eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
834eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Draw outer rectangle for circles
835eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        float left = mXCenter - outerRadius;
836eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        float top = mYCenter - outerRadius;
837eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        float right = mXCenter + outerRadius;
838eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        float bottom = mYCenter + outerRadius;
839ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        canvas.drawRect(left, top, right, bottom, mPaintDebug);
840eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
841eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Draw outer rectangle for background
842ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        left = mXCenter - mCircleRadius;
843ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        top = mYCenter - mCircleRadius;
844ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        right = mXCenter + mCircleRadius;
845ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        bottom = mYCenter + mCircleRadius;
846ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        canvas.drawRect(left, top, right, bottom, mPaintDebug);
847eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
848eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Draw outer view rectangle
849ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        canvas.drawRect(0, 0, getWidth(), getHeight(), mPaintDebug);
850eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
851eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Draw selected time
852eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final String selected = String.format("%02d:%02d", getCurrentHour(), getCurrentMinute());
853eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
854eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
855eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                ViewGroup.LayoutParams.WRAP_CONTENT);
856eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        TextView tv = new TextView(getContext());
857eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        tv.setLayoutParams(lp);
858eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        tv.setText(selected);
859eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        tv.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
860eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        Paint paint = tv.getPaint();
861eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        paint.setColor(DEBUG_TEXT_COLOR);
862eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
863eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final int width = tv.getMeasuredWidth();
864eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
865eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        float height = paint.descent() - paint.ascent();
866eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        float x = mXCenter - width / 2;
867eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        float y = mYCenter + 1.5f * height;
868eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
86967945c11a5e9547f71be91ceb99e7b9ff15a6292Alan Viverette        canvas.drawText(selected, x, y, paint);
870eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
871eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
872ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private void calculatePositionsHours() {
873eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Calculate the text positions
874ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final float numbersRadius = mCircleRadius * mNumbersRadiusMultiplier[HOURS];
875eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
876eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Calculate the positions for the 12 numbers in the main circle.
877ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        calculatePositions(mPaint[HOURS], numbersRadius, mXCenter, mYCenter,
878ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                mTextSize[HOURS], mOuterTextX[HOURS], mOuterTextY[HOURS]);
879eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
880eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // If we have an inner circle, calculate those positions too.
881eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mIs24HourMode) {
882ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            final float innerNumbersRadius = mCircleRadius
883ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    * mNumbersRadiusMultiplier[HOURS_INNER];
884eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
885ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            calculatePositions(mPaint[HOURS], innerNumbersRadius, mXCenter, mYCenter,
886ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    mTextSize[HOURS_INNER], mInnerTextX, mInnerTextY);
887eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
888eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
889eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
890ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private void calculatePositionsMinutes() {
891eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Calculate the text positions
892ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final float numbersRadius = mCircleRadius * mNumbersRadiusMultiplier[MINUTES];
893eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
894eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Calculate the positions for the 12 numbers in the main circle.
895ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        calculatePositions(mPaint[MINUTES], numbersRadius, mXCenter, mYCenter,
896ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                mTextSize[MINUTES], mOuterTextX[MINUTES], mOuterTextY[MINUTES]);
897eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
898eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
899eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    /**
900eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * Using the trigonometric Unit Circle, calculate the positions that the text will need to be
901eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * drawn at based on the specified circle radius. Place the values in the textGridHeights and
902eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * textGridWidths parameters.
903eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     */
904ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private static void calculatePositions(Paint paint, float radius, float xCenter, float yCenter,
905ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            float textSize, float[] x, float[] y) {
906ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        // Adjust yCenter to account for the text's baseline.
907eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        paint.setTextSize(textSize);
908eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        yCenter -= (paint.descent() + paint.ascent()) / 2;
909eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
910ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        for (int i = 0; i < NUM_POSITIONS; i++) {
911ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            x[i] = xCenter - radius * COS_30[i];
912ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            y[i] = yCenter - radius * SIN_30[i];
913ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        }
914eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
915eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
916eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    /**
917eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     * Draw the 12 text values at the positions specified by the textGrid parameters.
918eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio     */
919eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void drawTextElements(Canvas canvas, float textSize, Typeface typeface, String[] texts,
920ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            float[] textX, float[] textY, Paint paint, int alpha, boolean showActivated,
921ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            int activatedDegrees, boolean activatedOnly) {
922eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        paint.setTextSize(textSize);
923eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        paint.setTypeface(typeface);
924eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
925ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        // The activated index can touch a range of elements.
926ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final float activatedIndex = activatedDegrees / (360.0f / NUM_POSITIONS);
927ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final int activatedFloor = (int) activatedIndex;
928ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final int activatedCeil = ((int) Math.ceil(activatedIndex)) % NUM_POSITIONS;
929eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
930ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        for (int i = 0; i < 12; i++) {
931ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            final boolean activated = (activatedFloor == i || activatedCeil == i);
932ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            if (activatedOnly && !activated) {
933ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                continue;
934ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            }
935eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
936ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            final int stateMask = StateSet.VIEW_STATE_ENABLED
937ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    | (showActivated && activated ? StateSet.VIEW_STATE_ACTIVATED : 0);
938ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            final int color = mNumbersTextColor.getColorForState(StateSet.get(stateMask), 0);
939ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            paint.setColor(color);
940ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            paint.setAlpha(getMultipliedAlpha(color, alpha));
941eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
942ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            canvas.drawText(texts[i], textX[i], textY[i], paint);
943ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        }
944eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
945eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
946eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette    private static ObjectAnimator getFadeOutAnimator(IntHolder target, int startAlpha, int endAlpha,
947eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                InvalidateUpdateListener updateListener) {
948ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final ObjectAnimator animator = ObjectAnimator.ofInt(target, "value", startAlpha, endAlpha);
949ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        animator.setDuration(FADE_OUT_DURATION);
950eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        animator.addUpdateListener(updateListener);
951eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        return animator;
952eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
953eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
954eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette    private static ObjectAnimator getFadeInAnimator(IntHolder target, int startAlpha, int endAlpha,
955eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                InvalidateUpdateListener updateListener) {
956ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final float delayMultiplier = 0.25f;
957ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final float transitionDurationMultiplier = 1f;
958ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier;
959ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final int totalDuration = (int) (FADE_IN_DURATION * totalDurationMultiplier);
960ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final float delayPoint = (delayMultiplier * FADE_IN_DURATION) / totalDuration;
961eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
962ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final Keyframe kf0, kf1, kf2;
963eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        kf0 = Keyframe.ofInt(0f, startAlpha);
964eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        kf1 = Keyframe.ofInt(delayPoint, startAlpha);
965eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        kf2 = Keyframe.ofInt(1f, endAlpha);
966ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final PropertyValuesHolder fadeIn = PropertyValuesHolder.ofKeyframe("value", kf0, kf1, kf2);
967eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
968ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(target, fadeIn);
969ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        animator.setDuration(totalDuration);
970eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        animator.addUpdateListener(updateListener);
971eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        return animator;
972eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
973eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
974eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private class InvalidateUpdateListener implements ValueAnimator.AnimatorUpdateListener {
975eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        @Override
976eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        public void onAnimationUpdate(ValueAnimator animation) {
977eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            RadialTimePickerView.this.invalidate();
978eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
979eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
980eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
981eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void startHoursToMinutesAnimation() {
982eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mHoursToMinutesAnims.size() == 0) {
983eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mHoursToMinutesAnims.add(getFadeOutAnimator(mAlpha[HOURS],
984eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
985eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_CIRCLE],
986ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
987eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_DOT],
988eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
989eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_LINE],
990ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
991eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
992eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mHoursToMinutesAnims.add(getFadeInAnimator(mAlpha[MINUTES],
993eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
994eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_CIRCLE],
995ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
996eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_DOT],
997eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
998eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_LINE],
999ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
1000eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1001eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1002eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mTransition != null && mTransition.isRunning()) {
1003eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mTransition.end();
1004eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1005eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mTransition = new AnimatorSet();
1006eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mTransition.playTogether(mHoursToMinutesAnims);
1007eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mTransition.start();
1008eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
1009eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1010eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    private void startMinutesToHoursAnimation() {
1011eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mMinuteToHoursAnims.size() == 0) {
1012eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mMinuteToHoursAnims.add(getFadeOutAnimator(mAlpha[MINUTES],
1013eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
1014eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_CIRCLE],
1015ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
1016eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_DOT],
1017eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
1018eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_LINE],
1019ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
1020eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1021eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mMinuteToHoursAnims.add(getFadeInAnimator(mAlpha[HOURS],
1022eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
1023eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_CIRCLE],
1024ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
1025eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_DOT],
1026eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
1027eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_LINE],
1028ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
1029eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1030eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1031eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mTransition != null && mTransition.isRunning()) {
1032eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            mTransition.end();
1033eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1034eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mTransition = new AnimatorSet();
1035eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mTransition.playTogether(mMinuteToHoursAnims);
1036eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        mTransition.start();
1037eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
1038eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1039ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    private int getDegreesFromXY(float x, float y, boolean constrainOutside) {
1040eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final double hypotenuse = Math.sqrt(
1041eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                (y - mYCenter) * (y - mYCenter) + (x - mXCenter) * (x - mXCenter));
1042eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1043eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Basic check if we're outside the range of the disk
1044ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        if (constrainOutside && hypotenuse > mCircleRadius) {
1045eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            return -1;
1046eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1047ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette
1048eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Check
1049eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        if (mIs24HourMode && mShowHours) {
1050eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if (hypotenuse >= mMinHypotenuseForInnerNumber
1051eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    && hypotenuse <= mHalfwayHypotenusePoint) {
1052eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                mIsOnInnerCircle = true;
1053ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            } else if ((hypotenuse <= mMaxHypotenuseForOuterNumber || !constrainOutside)
1054eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    && hypotenuse >= mHalfwayHypotenusePoint) {
1055eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                mIsOnInnerCircle = false;
1056eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            } else {
1057eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                return -1;
1058eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
1059eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        } else {
1060eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            final int index =  (mShowHours) ? HOURS : MINUTES;
1061ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            final float length = (mCircleRadius * mNumbersRadiusMultiplier[index]);
1062ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            final int distanceToNumber = (int) (hypotenuse - length);
1063eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            final int maxAllowedDistance =
1064ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    (int) (mCircleRadius * (1 - mNumbersRadiusMultiplier[index]));
1065ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette            if (distanceToNumber < -maxAllowedDistance
1066ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    || (constrainOutside && distanceToNumber > maxAllowedDistance)) {
1067eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                return -1;
1068eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
1069eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1070eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1071eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        final float opposite = Math.abs(y - mYCenter);
1072ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        int degrees = (int) (Math.toDegrees(Math.asin(opposite / hypotenuse)) + 0.5);
1073eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1074eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        // Now we have to translate to the correct quadrant.
1075ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        final boolean rightSide = (x > mXCenter);
1076ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        final boolean topSide = (y < mYCenter);
1077ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (rightSide) {
1078ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (topSide) {
1079ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                degrees = 90 - degrees;
1080ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1081ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                degrees = 90 + degrees;
1082ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1083ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        } else {
1084ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (topSide) {
1085ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                degrees = 270 + degrees;
1086ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1087ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                degrees = 270 - degrees;
1088ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1089eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1090ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return degrees;
1091eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
1092eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1093002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette    boolean mChangedDuringTouch = false;
1094002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
1095eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    @Override
1096ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette    public boolean onTouchEvent(MotionEvent event) {
1097002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        if (!mInputEnabled) {
1098eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            return true;
1099eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1100eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1101002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        final int action = event.getActionMasked();
1102002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        if (action == MotionEvent.ACTION_MOVE
1103002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                || action == MotionEvent.ACTION_UP
1104002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                || action == MotionEvent.ACTION_DOWN) {
1105002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            boolean forceSelection = false;
1106002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            boolean autoAdvance = false;
1107002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
1108002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            if (action == MotionEvent.ACTION_DOWN) {
1109002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                // This is a new event stream, reset whether the value changed.
1110002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                mChangedDuringTouch = false;
1111002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            } else if (action == MotionEvent.ACTION_UP) {
1112002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                autoAdvance = true;
1113002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
1114002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                // If we saw a down/up pair without the value changing, assume
1115002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                // this is a single-tap selection and force a change.
1116002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                if (!mChangedDuringTouch) {
1117002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                    forceSelection = true;
1118eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                }
1119002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            }
1120eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1121002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            mChangedDuringTouch |= handleTouchInput(
1122002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                    event.getX(), event.getY(), forceSelection, autoAdvance);
1123eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1124002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
1125002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        return true;
1126002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette    }
1127002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
1128002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette    private boolean handleTouchInput(
1129002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            float x, float y, boolean forceSelection, boolean autoAdvance) {
1130002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        // Calling getDegreesFromXY has side effects, so cache
1131002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        // whether we used to be on the inner circle.
1132002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        final boolean wasOnInnerCircle = mIsOnInnerCircle;
1133ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette        final int degrees = getDegreesFromXY(x, y, false);
1134002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        if (degrees == -1) {
1135002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            return false;
1136002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        }
1137002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
1138002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        final int[] selectionDegrees = mSelectionDegrees;
113911a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette        final int type;
114011a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette        final int newValue;
114111a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette        final boolean valueChanged;
1142002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
1143002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        if (mShowHours) {
1144002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            final int snapDegrees = snapOnly30s(degrees, 0) % 360;
114511a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            valueChanged = selectionDegrees[HOURS] != snapDegrees
1146002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                    || selectionDegrees[HOURS_INNER] != snapDegrees
114711a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette                    || wasOnInnerCircle != mIsOnInnerCircle;
1148002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
114911a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            selectionDegrees[HOURS] = snapDegrees;
115011a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            selectionDegrees[HOURS_INNER] = snapDegrees;
115111a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            type = HOURS;
115211a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            newValue = getCurrentHour();
1153002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        } else {
1154002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            final int snapDegrees = snapPrefer30s(degrees) % 360;
115511a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            valueChanged = selectionDegrees[MINUTES] != snapDegrees;
1156002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
115711a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            selectionDegrees[MINUTES] = snapDegrees;
115811a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            type = MINUTES;
115911a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            newValue = getCurrentMinute();
1160002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        }
1161002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
116211a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette        if (valueChanged || forceSelection || autoAdvance) {
116311a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            // Fire the listener even if we just need to auto-advance.
1164002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            if (mListener != null) {
1165002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette                mListener.onValueSelected(type, newValue, autoAdvance);
1166002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            }
116711a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette
116811a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            // Only provide feedback if the value actually changed.
116911a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            if (valueChanged || forceSelection) {
117011a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette                performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
117111a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette                invalidate();
117211a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette            }
1173002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette            return true;
1174002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        }
1175002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette
1176002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette        return false;
1177eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
1178eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1179eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    @Override
1180ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    public boolean dispatchHoverEvent(MotionEvent event) {
1181ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        // First right-of-refusal goes the touch exploration helper.
1182ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        if (mTouchHelper.dispatchHoverEvent(event)) {
1183eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            return true;
1184eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1185ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        return super.dispatchHoverEvent(event);
1186eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
1187eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1188ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    public void setInputEnabled(boolean inputEnabled) {
1189ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        mInputEnabled = inputEnabled;
1190ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        invalidate();
1191ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    }
1192ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1193ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette    private class RadialPickerTouchHelper extends ExploreByTouchHelper {
1194ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final Rect mTempRect = new Rect();
1195ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1196ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int TYPE_HOUR = 1;
1197ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int TYPE_MINUTE = 2;
1198ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1199ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int SHIFT_TYPE = 0;
1200ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int MASK_TYPE = 0xF;
1201ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1202ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int SHIFT_VALUE = 8;
1203ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int MASK_VALUE = 0xFF;
1204ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1205ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        /** Increment in which virtual views are exposed for minutes. */
1206ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private final int MINUTE_INCREMENT = 5;
1207ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1208ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        public RadialPickerTouchHelper() {
1209ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            super(RadialTimePickerView.this);
1210eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1211eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1212ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1213ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
1214ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            super.onInitializeAccessibilityNodeInfo(host, info);
1215ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1216ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
1217ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
1218eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1219ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1220ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1221ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
1222ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (super.performAccessibilityAction(host, action, arguments)) {
1223ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                return true;
1224eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
1225eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1226ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            switch (action) {
1227ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
1228ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    adjustPicker(1);
1229ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    return true;
1230ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
1231ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    adjustPicker(-1);
1232ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    return true;
1233ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1234ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1235ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return false;
1236ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1237ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1238ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private void adjustPicker(int step) {
1239ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int stepSize;
1240ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int initialValue;
124167945c11a5e9547f71be91ceb99e7b9ff15a6292Alan Viverette            final int maxValue;
1242ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int minValue;
1243eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if (mShowHours) {
1244ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                stepSize = DEGREES_FOR_ONE_HOUR;
1245ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                initialValue = getCurrentHour() % 12;
1246ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1247eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                if (mIs24HourMode) {
1248eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    maxValue = 23;
1249ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    minValue = 0;
1250eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                } else {
1251eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    maxValue = 12;
1252eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                    minValue = 1;
1253eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                }
1254eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            } else {
1255ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                stepSize = DEGREES_FOR_ONE_MINUTE;
1256ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                initialValue = getCurrentMinute();
1257ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1258eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                maxValue = 55;
1259ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                minValue = 0;
1260eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
1261ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1262ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int steppedValue = snapOnly30s(initialValue * stepSize, step) / stepSize;
1263ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int clampedValue = MathUtils.constrain(steppedValue, minValue, maxValue);
1264ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (mShowHours) {
1265ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                setCurrentHour(clampedValue);
1266ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1267ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                setCurrentMinute(clampedValue);
1268ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1269ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1270ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1271ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1272ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected int getVirtualViewAt(float x, float y) {
1273ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int id;
12745efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette
12755efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette            // Calling getDegreesXY() has side-effects, so we need to cache the
12765efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette            // current inner circle value and restore after the call.
12775efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette            final boolean wasOnInnerCircle = mIsOnInnerCircle;
1278d8c2af55420d869c140278e6643d16cd90cfeb51Alan Viverette            final int degrees = getDegreesFromXY(x, y, true);
12795efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette            final boolean isOnInnerCircle = mIsOnInnerCircle;
12805efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette            mIsOnInnerCircle = wasOnInnerCircle;
12815efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette
1282ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (degrees != -1) {
1283ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int snapDegrees = snapOnly30s(degrees, 0) % 360;
1284ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                if (mShowHours) {
12855efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette                    final int hour24 = getHourForDegrees(snapDegrees, isOnInnerCircle);
12865efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette                    final int hour = mIs24HourMode ? hour24 : hour24To12(hour24);
1287ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    id = makeId(TYPE_HOUR, hour);
1288ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                } else {
1289ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    final int current = getCurrentMinute();
1290ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    final int touched = getMinuteForDegrees(degrees);
1291ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    final int snapped = getMinuteForDegrees(snapDegrees);
1292ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1293ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    // If the touched minute is closer to the current minute
1294ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    // than it is to the snapped minute, return current.
1295ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    final int minute;
1296ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    if (Math.abs(current - touched) < Math.abs(snapped - touched)) {
1297ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                        minute = current;
1298ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    } else {
1299ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                        minute = snapped;
1300ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    }
1301ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    id = makeId(TYPE_MINUTE, minute);
1302ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                }
1303ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1304ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                id = INVALID_ID;
1305eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
1306ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1307ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return id;
1308ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1309ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1310ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1311ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected void getVisibleVirtualViews(IntArray virtualViewIds) {
1312eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            if (mShowHours) {
1313ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int min = mIs24HourMode ? 0 : 1;
1314ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int max = mIs24HourMode ? 23 : 12;
1315ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                for (int i = min; i <= max ; i++) {
1316ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    virtualViewIds.add(makeId(TYPE_HOUR, i));
1317eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                }
1318eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            } else {
1319ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int current = getCurrentMinute();
1320ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                for (int i = 0; i < 60; i += MINUTE_INCREMENT) {
1321ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    virtualViewIds.add(makeId(TYPE_MINUTE, i));
1322ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1323ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    // If the current minute falls between two increments,
1324ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    // insert an extra node for it.
1325ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    if (current > i && current < i + MINUTE_INCREMENT) {
1326ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                        virtualViewIds.add(makeId(TYPE_MINUTE, current));
1327ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    }
1328eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio                }
1329eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio            }
1330eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio        }
1331eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1332ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1333ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
1334ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            event.setClassName(getClass().getName());
1335eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio
1336ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int type = getTypeFromId(virtualViewId);
1337ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int value = getValueFromId(virtualViewId);
1338ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final CharSequence description = getVirtualViewDescription(type, value);
1339ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            event.setContentDescription(description);
1340ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1341ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1342ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1343ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) {
1344ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            node.setClassName(getClass().getName());
1345ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            node.addAction(AccessibilityAction.ACTION_CLICK);
1346ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1347ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int type = getTypeFromId(virtualViewId);
1348ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int value = getValueFromId(virtualViewId);
1349ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final CharSequence description = getVirtualViewDescription(type, value);
1350ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            node.setContentDescription(description);
1351ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1352ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            getBoundsForVirtualView(virtualViewId, mTempRect);
1353ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            node.setBoundsInParent(mTempRect);
1354ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1355ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final boolean selected = isVirtualViewSelected(type, value);
1356ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            node.setSelected(selected);
13573fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette
13583fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            final int nextId = getVirtualViewIdAfter(type, value);
13593fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            if (nextId != INVALID_ID) {
13603fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                node.setTraversalBefore(RadialTimePickerView.this, nextId);
13613fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            }
13623fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette        }
13633fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette
13643fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette        private int getVirtualViewIdAfter(int type, int value) {
13653fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            if (type == TYPE_HOUR) {
13663fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                final int nextValue = value + 1;
13673fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                final int max = mIs24HourMode ? 23 : 12;
13683fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                if (nextValue <= max) {
13693fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                    return makeId(type, nextValue);
13703fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                }
13713fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            } else if (type == TYPE_MINUTE) {
13723fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                final int current = getCurrentMinute();
13733fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                final int snapValue = value - (value % MINUTE_INCREMENT);
13743fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                final int nextValue = snapValue + MINUTE_INCREMENT;
13753fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                if (value < current && nextValue > current) {
13763fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                    // The current value is between two snap values.
13773fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                    return makeId(type, current);
13783fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                } else if (nextValue < 60) {
13793fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                    return makeId(type, nextValue);
13803fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette                }
13813fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            }
13823fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette            return INVALID_ID;
1383ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1384ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1385ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        @Override
1386ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
1387ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                Bundle arguments) {
1388ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (action == AccessibilityNodeInfo.ACTION_CLICK) {
1389ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int type = getTypeFromId(virtualViewId);
1390ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final int value = getValueFromId(virtualViewId);
1391ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                if (type == TYPE_HOUR) {
1392ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    final int hour = mIs24HourMode ? value : hour12To24(value, mAmOrPm);
1393ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    setCurrentHour(hour);
1394ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    return true;
1395ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                } else if (type == TYPE_MINUTE) {
1396ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    setCurrentMinute(value);
1397ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    return true;
1398ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                }
1399ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1400ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return false;
1401ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1402ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1403ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private int hour12To24(int hour12, int amOrPm) {
1404ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            int hour24 = hour12;
1405ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (hour12 == 12) {
1406ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                if (amOrPm == AM) {
1407ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    hour24 = 0;
1408ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                }
1409ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else if (amOrPm == PM) {
1410ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                hour24 += 12;
1411ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1412ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return hour24;
1413ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1414ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
14155efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette        private int hour24To12(int hour24) {
14165efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette            if (hour24 == 0) {
14175efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette                return 12;
14185efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette            } else if (hour24 > 12) {
14195efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette                return hour24 - 12;
14205efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette            } else {
14215efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette                return hour24;
14225efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette            }
14235efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette        }
14245efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette
1425ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private void getBoundsForVirtualView(int virtualViewId, Rect bounds) {
1426ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final float radius;
1427ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int type = getTypeFromId(virtualViewId);
1428ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final int value = getValueFromId(virtualViewId);
1429ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final float centerRadius;
1430ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final float degrees;
1431ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (type == TYPE_HOUR) {
1432ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                final boolean innerCircle = mIs24HourMode && value > 0 && value <= 12;
1433ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                if (innerCircle) {
1434ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    centerRadius = mCircleRadius * mNumbersRadiusMultiplier[HOURS_INNER];
1435ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    radius = mSelectionRadius[HOURS_INNER];
1436ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                } else {
1437ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                    centerRadius = mCircleRadius * mNumbersRadiusMultiplier[HOURS];
1438ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    radius = mSelectionRadius[HOURS];
1439ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                }
1440ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1441ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                degrees = getDegreesForHour(value);
1442ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else if (type == TYPE_MINUTE) {
1443ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette                centerRadius = mCircleRadius * mNumbersRadiusMultiplier[MINUTES];
1444ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                degrees = getDegreesForMinute(value);
1445ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                radius = mSelectionRadius[MINUTES];
1446ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1447ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                // This should never happen.
1448ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                centerRadius = 0;
1449ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                degrees = 0;
1450ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                radius = 0;
1451ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1452ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1453ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final double radians = Math.toRadians(degrees);
1454ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final float xCenter = mXCenter + centerRadius * (float) Math.sin(radians);
1455ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final float yCenter = mYCenter - centerRadius * (float) Math.cos(radians);
1456ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1457ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            bounds.set((int) (xCenter - radius), (int) (yCenter - radius),
1458ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                    (int) (xCenter + radius), (int) (yCenter + radius));
1459ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1460ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1461ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private CharSequence getVirtualViewDescription(int type, int value) {
1462ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final CharSequence description;
1463ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (type == TYPE_HOUR || type == TYPE_MINUTE) {
1464ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                description = Integer.toString(value);
1465ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1466ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                description = null;
1467ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1468ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return description;
1469ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1470ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1471ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private boolean isVirtualViewSelected(int type, int value) {
1472ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            final boolean selected;
1473ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            if (type == TYPE_HOUR) {
1474ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                selected = getCurrentHour() == value;
1475ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else if (type == TYPE_MINUTE) {
1476ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                selected = getCurrentMinute() == value;
1477ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            } else {
1478ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette                selected = false;
1479ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            }
1480ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return selected;
1481ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1482ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1483ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private int makeId(int type, int value) {
1484ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return type << SHIFT_TYPE | value << SHIFT_VALUE;
1485ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1486ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1487ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private int getTypeFromId(int id) {
1488ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return id >>> SHIFT_TYPE & MASK_TYPE;
1489ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1490ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette
1491ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        private int getValueFromId(int id) {
1492ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette            return id >>> SHIFT_VALUE & MASK_VALUE;
1493ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        }
1494eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio    }
1495eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
1496eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette    private static class IntHolder {
1497eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        private int mValue;
1498eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
1499eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        public IntHolder(int value) {
1500eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mValue = value;
1501eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        }
1502eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
1503eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        public void setValue(int value) {
1504eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            mValue = value;
1505eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        }
1506eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette
1507eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        public int getValue() {
1508eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette            return mValue;
1509eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette        }
1510eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette    }
1511eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio}
1512