16e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein/*
26e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein * Copyright (C) 2013 The Android Open Source Project
36e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein *
46e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein * Licensed under the Apache License, Version 2.0 (the "License");
56e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein * you may not use this file except in compliance with the License.
66e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein * You may obtain a copy of the License at
76e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein *
86e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein *      http://www.apache.org/licenses/LICENSE-2.0
96e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein *
106e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein * Unless required by applicable law or agreed to in writing, software
116e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein * distributed under the License is distributed on an "AS IS" BASIS,
126e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
136e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein * See the License for the specific language governing permissions and
146e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein * limitations under the License.
156e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein */
166e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
17b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzsteinpackage com.android.datetimepicker.time;
186e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
196e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.animation.AnimatorSet;
206e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.animation.ObjectAnimator;
21b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzsteinimport android.annotation.SuppressLint;
226e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.content.Context;
236e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.content.res.Resources;
24b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzsteinimport android.os.Bundle;
256e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.os.Handler;
266e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.os.SystemClock;
27b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzsteinimport android.text.format.DateUtils;
28b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzsteinimport android.text.format.Time;
296e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.util.AttributeSet;
306e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.util.Log;
316e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.view.MotionEvent;
326e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.view.View;
336e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.view.View.OnTouchListener;
346e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.view.ViewConfiguration;
35b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzsteinimport android.view.ViewGroup;
36b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzsteinimport android.view.accessibility.AccessibilityEvent;
37b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzsteinimport android.view.accessibility.AccessibilityManager;
38b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzsteinimport android.view.accessibility.AccessibilityNodeInfo;
396e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.widget.FrameLayout;
406e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
41f2346bc730dc676b6ba6248f5b6949b90d201b9bPaul Sliwowskiimport com.android.datetimepicker.HapticFeedbackController;
426e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport com.android.datetimepicker.R;
436e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
441f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein/**
451f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein * The primary layout to hold the circular picker, and the am/pm buttons. This view well measure
461f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein * itself to end up as a square. It also handles touches to be passed in to views that need to know
471f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein * when they'd been touched.
481f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein */
49b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzsteinpublic class RadialPickerLayout extends FrameLayout implements OnTouchListener {
50f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    private static final String TAG = "RadialPickerLayout";
516e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
526e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private final int TOUCH_SLOP;
536e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private final int TAP_TIMEOUT;
54f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein
55f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    private static final int VISIBLE_DEGREES_STEP_SIZE = 30;
56f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    private static final int HOUR_VALUE_TO_DEGREES_STEP_SIZE = VISIBLE_DEGREES_STEP_SIZE;
576e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private static final int MINUTE_VALUE_TO_DEGREES_STEP_SIZE = 6;
586e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private static final int HOUR_INDEX = TimePickerDialog.HOUR_INDEX;
596e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private static final int MINUTE_INDEX = TimePickerDialog.MINUTE_INDEX;
606e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private static final int AMPM_INDEX = TimePickerDialog.AMPM_INDEX;
61b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    private static final int ENABLE_PICKER_INDEX = TimePickerDialog.ENABLE_PICKER_INDEX;
626e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private static final int AM = TimePickerDialog.AM;
636e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private static final int PM = TimePickerDialog.PM;
646e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
656e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int mLastValueSelected;
666e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
67f2346bc730dc676b6ba6248f5b6949b90d201b9bPaul Sliwowski    private HapticFeedbackController mHapticFeedbackController;
686e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private OnValueSelectedListener mListener;
696e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private boolean mTimeInitialized;
706e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int mCurrentHoursOfDay;
716e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int mCurrentMinutes;
726e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private boolean mIs24HourMode;
73b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    private boolean mHideAmPm;
746e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int mCurrentItemShowing;
756e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
766e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private CircleView mCircleView;
776e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private AmPmCirclesView mAmPmCirclesView;
786e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private RadialTextsView mHourRadialTextsView;
796e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private RadialTextsView mMinuteRadialTextsView;
806e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private RadialSelectorView mHourRadialSelectorView;
816e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private RadialSelectorView mMinuteRadialSelectorView;
82b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    private View mGrayBox;
836e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
84f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    private int[] mSnapPrefer30sMap;
85b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    private boolean mInputEnabled;
866e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int mIsTouchingAmOrPm = -1;
876e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private boolean mDoingMove;
88b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    private boolean mDoingTouch;
896e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int mDownDegrees;
906e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mDownX;
916e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mDownY;
92b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    private AccessibilityManager mAccessibilityManager;
936e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
94f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    private AnimatorSet mTransition;
956e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private Handler mHandler = new Handler();
966e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
976e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public interface OnValueSelectedListener {
986e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance);
996e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
1006e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
101b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    public RadialPickerLayout(Context context, AttributeSet attrs) {
1026e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        super(context, attrs);
1036e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1046e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        setOnTouchListener(this);
1056e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        ViewConfiguration vc = ViewConfiguration.get(context);
1066e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        TOUCH_SLOP = vc.getScaledTouchSlop();
1076e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
1086e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mDoingMove = false;
1096e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1106e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mCircleView = new CircleView(context);
1116e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        addView(mCircleView);
1126e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1136e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mAmPmCirclesView = new AmPmCirclesView(context);
1146e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        addView(mAmPmCirclesView);
1156e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1166e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mHourRadialTextsView = new RadialTextsView(context);
1176e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        addView(mHourRadialTextsView);
1186e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mMinuteRadialTextsView = new RadialTextsView(context);
1196e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        addView(mMinuteRadialTextsView);
1206e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1216e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mHourRadialSelectorView = new RadialSelectorView(context);
1226e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        addView(mHourRadialSelectorView);
1236e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mMinuteRadialSelectorView = new RadialSelectorView(context);
1246e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        addView(mMinuteRadialSelectorView);
1256e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
126f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // Prepare mapping to snap touchable degrees to selectable degrees.
127f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        preparePrefer30sMap();
128f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein
1296e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mLastValueSelected = -1;
1306e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
131b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        mInputEnabled = true;
132b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        mGrayBox = new View(context);
133b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        mGrayBox.setLayoutParams(new ViewGroup.LayoutParams(
134b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
135f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        mGrayBox.setBackgroundColor(getResources().getColor(R.color.transparent_black));
136b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        mGrayBox.setVisibility(View.INVISIBLE);
137b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        addView(mGrayBox);
138b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein
139b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        mAccessibilityManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
140f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein
141f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        mTimeInitialized = false;
1426e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
1436e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
144f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
145f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Measure the view to end up as a square, based on the minimum of the height and width.
146f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
1476e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    @Override
1486e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1496e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
150f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
1516e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
152f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
153f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        int minDimension = Math.min(measuredWidth, measuredHeight);
154f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein
155f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        super.onMeasure(MeasureSpec.makeMeasureSpec(minDimension, widthMode),
156f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                MeasureSpec.makeMeasureSpec(minDimension, heightMode));
1576e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
1586e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1596e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public void setOnValueSelectedListener(OnValueSelectedListener listener) {
1606e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mListener = listener;
1616e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
1626e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
163f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
164f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Initialize the Layout with starting values.
165f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param context
166f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param initialHoursOfDay
167f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param initialMinutes
168f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param is24HourMode
169f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
170f2346bc730dc676b6ba6248f5b6949b90d201b9bPaul Sliwowski    public void initialize(Context context, HapticFeedbackController hapticFeedbackController,
1713fc32c45f5efc4ce4b91cbcdd925d9b30f67046ePaul Sliwowski            int initialHoursOfDay, int initialMinutes, boolean is24HourMode) {
1726e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (mTimeInitialized) {
1736e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            Log.e(TAG, "Time has already been initialized.");
1746e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return;
1756e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
1763fc32c45f5efc4ce4b91cbcdd925d9b30f67046ePaul Sliwowski
177f2346bc730dc676b6ba6248f5b6949b90d201b9bPaul Sliwowski        mHapticFeedbackController = hapticFeedbackController;
1786e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mIs24HourMode = is24HourMode;
179b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        mHideAmPm = mAccessibilityManager.isTouchExplorationEnabled()? true : mIs24HourMode;
1806e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
181f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // Initialize the circle and AM/PM circles if applicable.
182b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        mCircleView.initialize(context, mHideAmPm);
1836e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mCircleView.invalidate();
184b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        if (!mHideAmPm) {
1856e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mAmPmCirclesView.initialize(context, initialHoursOfDay < 12? AM : PM);
1866e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mAmPmCirclesView.invalidate();
1876e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
1886e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
189f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // Initialize the hours and minutes numbers.
1906e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        Resources res = context.getResources();
191b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        int[] hours = {12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
192b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        int[] hours_24 = {0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23};
193b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        int[] minutes = {0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55};
194b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        String[] hoursTexts = new String[12];
195b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        String[] innerHoursTexts = new String[12];
196b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        String[] minutesTexts = new String[12];
197b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        for (int i = 0; i < 12; i++) {
198b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            hoursTexts[i] = is24HourMode?
199b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                    String.format("%02d", hours_24[i]) : String.format("%d", hours[i]);
200b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            innerHoursTexts[i] = String.format("%d", hours[i]);
201b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            minutesTexts[i] = String.format("%02d", minutes[i]);
202b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        }
2036e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mHourRadialTextsView.initialize(res,
204b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                hoursTexts, (is24HourMode? innerHoursTexts : null), mHideAmPm, true);
2056e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mHourRadialTextsView.invalidate();
206b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        mMinuteRadialTextsView.initialize(res, minutesTexts, null, mHideAmPm, false);
2076e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mMinuteRadialTextsView.invalidate();
2086e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
209f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // Initialize the currently-selected hour and minute.
210b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        setValueForItem(HOUR_INDEX, initialHoursOfDay);
211b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        setValueForItem(MINUTE_INDEX, initialMinutes);
212b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        int hourDegrees = (initialHoursOfDay % 12) * HOUR_VALUE_TO_DEGREES_STEP_SIZE;
213b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        mHourRadialSelectorView.initialize(context, mHideAmPm, is24HourMode, true,
214b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                hourDegrees, isHourInnerCircle(initialHoursOfDay));
215b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        int minuteDegrees = initialMinutes * MINUTE_VALUE_TO_DEGREES_STEP_SIZE;
216b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        mMinuteRadialSelectorView.initialize(context, mHideAmPm, false, false,
217b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                minuteDegrees, false);
2186e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2196e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mTimeInitialized = true;
2206e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
2216e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2221f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein    /* package */ void setTheme(Context context, boolean themeDark) {
2231f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein        mCircleView.setTheme(context, themeDark);
2241f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein        mAmPmCirclesView.setTheme(context, themeDark);
2251f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein        mHourRadialTextsView.setTheme(context, themeDark);
2261f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein        mMinuteRadialTextsView.setTheme(context, themeDark);
2271f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein        mHourRadialSelectorView.setTheme(context, themeDark);
2281f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein        mMinuteRadialSelectorView.setTheme(context, themeDark);
2291f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein   }
2301f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein
231b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    public void setTime(int hours, int minutes) {
232b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        setItem(HOUR_INDEX, hours);
233b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        setItem(MINUTE_INDEX, minutes);
234b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    }
235b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein
236f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
237f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Set either the hour or the minute. Will set the internal value, and set the selection.
238f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
239b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    private void setItem(int index, int value) {
240b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        if (index == HOUR_INDEX) {
241b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            setValueForItem(HOUR_INDEX, value);
242b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            int hourDegrees = (value % 12) * HOUR_VALUE_TO_DEGREES_STEP_SIZE;
243f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            mHourRadialSelectorView.setSelection(hourDegrees, isHourInnerCircle(value), false);
244b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            mHourRadialSelectorView.invalidate();
245b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        } else if (index == MINUTE_INDEX) {
246b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            setValueForItem(MINUTE_INDEX, value);
247b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            int minuteDegrees = value * MINUTE_VALUE_TO_DEGREES_STEP_SIZE;
248f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            mMinuteRadialSelectorView.setSelection(minuteDegrees, false, false);
249b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            mMinuteRadialSelectorView.invalidate();
250b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        }
251b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    }
252b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein
253f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
254f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Check if a given hour appears in the outer circle or the inner circle
255f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @return true if the hour is in the inner circle, false if it's in the outer circle.
256f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
2576e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private boolean isHourInnerCircle(int hourOfDay) {
2586e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        // We'll have the 00 hours on the outside circle.
2596e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        return mIs24HourMode && (hourOfDay <= 12 && hourOfDay != 0);
2606e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
2616e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2626e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public int getHours() {
2636e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        return mCurrentHoursOfDay;
2646e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
2656e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2666e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public int getMinutes() {
2676e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        return mCurrentMinutes;
2686e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
2696e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
270f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
271f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * If the hours are showing, return the current hour. If the minutes are showing, return the
272f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * current minute.
273f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
2746e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int getCurrentlyShowingValue() {
2756e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int currentIndex = getCurrentItemShowing();
2766e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (currentIndex == HOUR_INDEX) {
2776e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return mCurrentHoursOfDay;
2786e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else if (currentIndex == MINUTE_INDEX) {
2796e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return mCurrentMinutes;
2806e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else {
2816e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return -1;
2826e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
2836e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
2846e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2856e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public int getIsCurrentlyAmOrPm() {
2866e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (mCurrentHoursOfDay < 12) {
2876e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return AM;
2886e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else if (mCurrentHoursOfDay < 24) {
2896e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return PM;
2906e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
2916e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        return -1;
2926e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
2936e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
294f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
295f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Set the internal value for the hour, minute, or AM/PM.
296f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
2976e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private void setValueForItem(int index, int value) {
2986e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (index == HOUR_INDEX) {
2996e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mCurrentHoursOfDay = value;
3006e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else if (index == MINUTE_INDEX){
3016e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mCurrentMinutes = value;
3026e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else if (index == AMPM_INDEX) {
3036e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            if (value == AM) {
3046e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                mCurrentHoursOfDay = mCurrentHoursOfDay % 12;
3056e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            } else if (value == PM) {
3066e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                mCurrentHoursOfDay = (mCurrentHoursOfDay % 12) + 12;
3076e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            }
3086e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
3096e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
3106e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
311f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
312f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Set the internal value as either AM or PM, and update the AM/PM circle displays.
313f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param amOrPm
314f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
3156e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public void setAmOrPm(int amOrPm) {
3166e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mAmPmCirclesView.setAmOrPm(amOrPm);
3176e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mAmPmCirclesView.invalidate();
3186e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        setValueForItem(AMPM_INDEX, amOrPm);
3196e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
3206e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
321f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
322f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Split up the 360 degrees of the circle among the 60 selectable values. Assigns a larger
323f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * selectable area to each of the 12 visible values, such that the ratio of space apportioned
324f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * to a visible value : space apportioned to a non-visible value will be 14 : 4.
325f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * E.g. the output of 30 degrees should have a higher range of input associated with it than
326f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * the output of 24 degrees, because 30 degrees corresponds to a visible number on the clock
327f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * circle (5 on the minutes, 1 or 13 on the hours).
328f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
329f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    private void preparePrefer30sMap() {
330f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // We'll split up the visible output and the non-visible output such that each visible
331f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // output will correspond to a range of 14 associated input degrees, and each non-visible
332f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // output will correspond to a range of 4 associate input degrees, so visible numbers
333f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // are more than 3 times easier to get than non-visible numbers:
334f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // {354-359,0-7}:0, {8-11}:6, {12-15}:12, {16-19}:18, {20-23}:24, {24-37}:30, etc.
335f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        //
336f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // If an output of 30 degrees should correspond to a range of 14 associated degrees, then
337f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // we'll need any input between 24 - 37 to snap to 30. Working out from there, 20-23 should
338f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // snap to 24, while 38-41 should snap to 36. This is somewhat counter-intuitive, that you
339f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // can be touching 36 degrees but have the selection snapped to 30 degrees; however, this
340f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // inconsistency isn't noticeable at such fine-grained degrees, and it affords us the
341f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // ability to aggressively prefer the visible values by a factor of more than 3:1, which
342f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // greatly contributes to the selectability of these values.
343f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein
344f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // Our input will be 0 through 360.
345f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        mSnapPrefer30sMap = new int[361];
346f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein
347f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // The first output is 0, and each following output will increment by 6 {0, 6, 12, ...}.
348f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        int snappedOutputDegrees = 0;
349f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // Count of how many inputs we've designated to the specified output.
350f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        int count = 1;
351f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // How many input we expect for a specified output. This will be 14 for output divisible
352f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // by 30, and 4 for the remaining output. We'll special case the outputs of 0 and 360, so
353f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // the caller can decide which they need.
354f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        int expectedCount = 8;
355f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // Iterate through the input.
356f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        for (int degrees = 0; degrees < 361; degrees++) {
357f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            // Save the input-output mapping.
358f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            mSnapPrefer30sMap[degrees] = snappedOutputDegrees;
359f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            // If this is the last input for the specified output, calculate the next output and
360f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            // the next expected count.
361f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            if (count == expectedCount) {
362f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                snappedOutputDegrees += 6;
363f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                if (snappedOutputDegrees == 360) {
364f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                    expectedCount = 7;
365f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                } else if (snappedOutputDegrees % 30 == 0) {
366f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                    expectedCount = 14;
367f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                } else {
368f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                    expectedCount = 4;
369f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                }
370f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                count = 1;
371f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            } else {
372f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                count++;
373f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            }
374f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        }
375b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    }
376b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein
377f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
378f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Returns mapping of any input degrees (0 to 360) to one of 60 selectable output degrees,
379f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * where the degrees corresponding to visible numbers (i.e. those divisible by 30) will be
380f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * weighted heavier than the degrees corresponding to non-visible numbers.
381f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * See {@link #preparePrefer30sMap()} documentation for the rationale and generation of the
382f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * mapping.
383f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
384f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    private int snapPrefer30s(int degrees) {
385f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        if (mSnapPrefer30sMap == null) {
386f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            return -1;
387f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        }
388f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        return mSnapPrefer30sMap[degrees];
389f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    }
390f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein
391f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
392f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Returns mapping of any input degrees (0 to 360) to one of 12 visible output degrees (all
393f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * multiples of 30), where the input will be "snapped" to the closest visible degrees.
394f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param degrees The input degrees
395f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param forceAboveOrBelow The output may be forced to either the higher or lower step, or may
396f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * be allowed to snap to whichever is closer. Use 1 to force strictly higher, -1 to force
397f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * strictly lower, and 0 to snap to the closer one.
398f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @return output degrees, will be a multiple of 30
399f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
400f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    private int snapOnly30s(int degrees, int forceHigherOrLower) {
401f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        int stepSize = HOUR_VALUE_TO_DEGREES_STEP_SIZE;
402b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        int floor = (degrees / stepSize) * stepSize;
403b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        int ceiling = floor + stepSize;
404f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        if (forceHigherOrLower == 1) {
405b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            degrees = ceiling;
406f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        } else if (forceHigherOrLower == -1) {
407b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            if (degrees == floor) {
408b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                floor -= stepSize;
409b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            }
410b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            degrees = floor;
411b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        } else {
412b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            if ((degrees - floor) < (ceiling - degrees)) {
413b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                degrees = floor;
414b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            } else {
415b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                degrees = ceiling;
416b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            }
417b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        }
418b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        return degrees;
419b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    }
420b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein
421f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
422f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * For the currently showing view (either hours or minutes), re-calculate the position for the
423f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * selector, and redraw it at that position. The input degrees will be snapped to a selectable
424f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * value.
425f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param degrees Degrees which should be selected.
426f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param isInnerCircle Whether the selection should be in the inner circle; will be ignored
427f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * if there is no inner circle.
428f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param forceToVisibleValue Even if the currently-showing circle allows for fine-grained
429f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * selection (i.e. minutes), force the selection to one of the visibly-showing values.
430f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param forceDrawDot The dot in the circle will generally only be shown when the selection
431f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * is on non-visible values, but use this to force the dot to be shown.
432f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @return The value that was selected, i.e. 0-23 for hours, 0-59 for minutes.
433f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
434b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    private int reselectSelector(int degrees, boolean isInnerCircle,
435f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            boolean forceToVisibleValue, boolean forceDrawDot) {
436b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        if (degrees == -1) {
4376e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return -1;
4386e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
439b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        int currentShowing = getCurrentItemShowing();
4406e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
4416e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int stepSize;
442f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        boolean allowFineGrained = !forceToVisibleValue && (currentShowing == MINUTE_INDEX);
443b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        if (allowFineGrained) {
444f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            degrees = snapPrefer30s(degrees);
4456e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else {
446f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            degrees = snapOnly30s(degrees, 0);
4476e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
4486e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
4496e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        RadialSelectorView radialSelectorView;
450b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        if (currentShowing == HOUR_INDEX) {
4516e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            radialSelectorView = mHourRadialSelectorView;
4526e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            stepSize = HOUR_VALUE_TO_DEGREES_STEP_SIZE;
4536e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else {
4546e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            radialSelectorView = mMinuteRadialSelectorView;
4556e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            stepSize = MINUTE_VALUE_TO_DEGREES_STEP_SIZE;
4566e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
457f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        radialSelectorView.setSelection(degrees, isInnerCircle, forceDrawDot);
4586e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        radialSelectorView.invalidate();
4596e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
4606e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
4616e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (currentShowing == HOUR_INDEX) {
4626e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            if (mIs24HourMode) {
4636e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                if (degrees == 0 && isInnerCircle) {
4646e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    degrees = 360;
4656e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                } else if (degrees == 360 && !isInnerCircle) {
4666e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    degrees = 0;
4676e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                }
4686e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            } else if (degrees == 0) {
4696e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                degrees = 360;
4706e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            }
4716e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else if (degrees == 360 && currentShowing == MINUTE_INDEX) {
4726e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            degrees = 0;
4736e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
4746e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
4756e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int value = degrees / stepSize;
4766e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (currentShowing == HOUR_INDEX && mIs24HourMode && !isInnerCircle && degrees != 0) {
4776e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            value += 12;
4786e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
4796e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        return value;
4806e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
4816e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
482f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
483f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Calculate the degrees within the circle that corresponds to the specified coordinates, if
484f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * the coordinates are within the range that will trigger a selection.
485f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param pointX The x coordinate.
486f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param pointY The y coordinate.
487f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param forceLegal Force the selection to be legal, regardless of how far the coordinates are
488f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * from the actual numbers.
489f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param isInnerCircle If the selection may be in the inner circle, pass in a size-1 boolean
490f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * array here, inside which the value will be true if the selection is in the inner circle,
491f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * and false if in the outer circle.
492f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @return Degrees from 0 to 360, if the selection was within the legal range. -1 if not.
493f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
4946e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int getDegreesFromCoords(float pointX, float pointY, boolean forceLegal,
4956e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            final Boolean[] isInnerCircle) {
4966e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int currentItem = getCurrentItemShowing();
497b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        if (currentItem == HOUR_INDEX) {
4986e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return mHourRadialSelectorView.getDegreesFromCoords(
4996e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    pointX, pointY, forceLegal, isInnerCircle);
500b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        } else if (currentItem == MINUTE_INDEX) {
5016e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return mMinuteRadialSelectorView.getDegreesFromCoords(
5026e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    pointX, pointY, forceLegal, isInnerCircle);
5036e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else {
5046e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return -1;
5056e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
5066e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
5076e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
508f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
509f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Get the item (hours or minutes) that is currently showing.
510f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
5116e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public int getCurrentItemShowing() {
5126e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (mCurrentItemShowing != HOUR_INDEX && mCurrentItemShowing != MINUTE_INDEX) {
5136e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            Log.e(TAG, "Current item showing was unfortunately set to "+mCurrentItemShowing);
5146e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return -1;
5156e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
5166e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        return mCurrentItemShowing;
5176e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
5186e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
519f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
520f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Set either minutes or hours as showing.
521f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param animate True to animate the transition, false to show with no animation.
522f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
5236e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public void setCurrentItemShowing(int index, boolean animate) {
5246e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (index != HOUR_INDEX && index != MINUTE_INDEX) {
5256e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            Log.e(TAG, "TimePicker does not support view at index "+index);
5266e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return;
5276e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
5286e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
529b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        int lastIndex = getCurrentItemShowing();
530b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        mCurrentItemShowing = index;
531b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein
532b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        if (animate && (index != lastIndex)) {
5336e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            ObjectAnimator[] anims = new ObjectAnimator[4];
5346e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            if (index == MINUTE_INDEX) {
5356e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                anims[0] = mHourRadialTextsView.getDisappearAnimator();
5366e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                anims[1] = mHourRadialSelectorView.getDisappearAnimator();
5376e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                anims[2] = mMinuteRadialTextsView.getReappearAnimator();
5386e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                anims[3] = mMinuteRadialSelectorView.getReappearAnimator();
5396e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            } else if (index == HOUR_INDEX){
5406e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                anims[0] = mHourRadialTextsView.getReappearAnimator();
5416e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                anims[1] = mHourRadialSelectorView.getReappearAnimator();
5426e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                anims[2] = mMinuteRadialTextsView.getDisappearAnimator();
5436e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                anims[3] = mMinuteRadialSelectorView.getDisappearAnimator();
5446e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            }
5456e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
546f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            if (mTransition != null && mTransition.isRunning()) {
547f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                mTransition.end();
548f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            }
549f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            mTransition = new AnimatorSet();
550f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            mTransition.playTogether(anims);
551f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            mTransition.start();
5526e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else {
553b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            int hourAlpha = (index == HOUR_INDEX) ? 255 : 0;
554b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            int minuteAlpha = (index == MINUTE_INDEX) ? 255 : 0;
5556e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mHourRadialTextsView.setAlpha(hourAlpha);
5566e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mHourRadialSelectorView.setAlpha(hourAlpha);
5576e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mMinuteRadialTextsView.setAlpha(minuteAlpha);
5586e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mMinuteRadialSelectorView.setAlpha(minuteAlpha);
5596e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
5606e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
5616e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
5626e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
5636e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    @Override
5646e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public boolean onTouch(View v, MotionEvent event) {
5656e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        final float eventX = event.getX();
5666e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        final float eventY = event.getY();
5676e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int degrees;
5686e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int value;
5696e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        final Boolean[] isInnerCircle = new Boolean[1];
5706e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        isInnerCircle[0] = false;
5716e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
5726e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        long millis = SystemClock.uptimeMillis();
5736e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
5746e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        switch(event.getAction()) {
5756e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            case MotionEvent.ACTION_DOWN:
576b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                if (!mInputEnabled) {
577b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                    return true;
578b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                }
579b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein
5806e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                mDownX = eventX;
5816e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                mDownY = eventY;
5826e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
5836e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                mLastValueSelected = -1;
5846e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                mDoingMove = false;
585b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                mDoingTouch = true;
586f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                // If we're showing the AM/PM, check to see if the user is touching it.
587b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                if (!mHideAmPm) {
5886e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    mIsTouchingAmOrPm = mAmPmCirclesView.getIsTouchingAmOrPm(eventX, eventY);
5896e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                } else {
5906e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    mIsTouchingAmOrPm = -1;
5916e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                }
5926e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                if (mIsTouchingAmOrPm == AM || mIsTouchingAmOrPm == PM) {
593f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                    // If the touch is on AM or PM, set it as "touched" after the TAP_TIMEOUT
594f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                    // in case the user moves their finger quickly.
595f2346bc730dc676b6ba6248f5b6949b90d201b9bPaul Sliwowski                    mHapticFeedbackController.tryVibrate();
5966e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    mDownDegrees = -1;
5976e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    mHandler.postDelayed(new Runnable() {
5986e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        @Override
5996e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        public void run() {
6006e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                            mAmPmCirclesView.setAmOrPmPressed(mIsTouchingAmOrPm);
6016e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                            mAmPmCirclesView.invalidate();
6026e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        }
6036e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    }, TAP_TIMEOUT);
6046e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                } else {
605f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                    // If we're in accessibility mode, force the touch to be legal. Otherwise,
606f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                    // it will only register within the given touch target zone.
607b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                    boolean forceLegal = mAccessibilityManager.isTouchExplorationEnabled();
608f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                    // Calculate the degrees that is currently being touched.
609b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                    mDownDegrees = getDegreesFromCoords(eventX, eventY, forceLegal, isInnerCircle);
6106e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    if (mDownDegrees != -1) {
611f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                        // If it's a legal touch, set that number as "selected" after the
612f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                        // TAP_TIMEOUT in case the user moves their finger quickly.
613f2346bc730dc676b6ba6248f5b6949b90d201b9bPaul Sliwowski                        mHapticFeedbackController.tryVibrate();
6146e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        mHandler.postDelayed(new Runnable() {
6156e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                            @Override
6166e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                            public void run() {
6176e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                                mDoingMove = true;
618f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                                int value = reselectSelector(mDownDegrees, isInnerCircle[0],
619f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                                        false, true);
620b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                                mLastValueSelected = value;
6216e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                                mListener.onValueSelected(getCurrentItemShowing(), value, false);
6226e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                            }
6236e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        }, TAP_TIMEOUT);
6246e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    }
6256e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                }
6266e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                return true;
6276e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            case MotionEvent.ACTION_MOVE:
628b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                if (!mInputEnabled) {
629b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                    // We shouldn't be in this state, because input is disabled.
630b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                    Log.e(TAG, "Input was disabled, but received ACTION_MOVE.");
631b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                    return true;
632b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                }
633b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein
6346e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                float dY = Math.abs(eventY - mDownY);
6356e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                float dX = Math.abs(eventX - mDownX);
6366e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
6376e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                if (!mDoingMove && dX <= TOUCH_SLOP && dY <= TOUCH_SLOP) {
6386e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    // Hasn't registered down yet, just slight, accidental movement of finger.
6396e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    break;
6406e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                }
6416e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
6426e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // If we're in the middle of touching down on AM or PM, check if we still are.
6436e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // If so, no-op. If not, remove its pressed state. Either way, no need to check
6446e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // for touches on the other circle.
6456e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                if (mIsTouchingAmOrPm == AM || mIsTouchingAmOrPm == PM) {
6466e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    mHandler.removeCallbacksAndMessages(null);
6476e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    int isTouchingAmOrPm = mAmPmCirclesView.getIsTouchingAmOrPm(eventX, eventY);
6486e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    if (isTouchingAmOrPm != mIsTouchingAmOrPm) {
6496e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        mAmPmCirclesView.setAmOrPmPressed(-1);
6506e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        mAmPmCirclesView.invalidate();
6516e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        mIsTouchingAmOrPm = -1;
6526e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    }
6536e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    break;
6546e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                }
6556e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
6566e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                if (mDownDegrees == -1) {
6576e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    // Original down was illegal, so no movement will register.
6586e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    break;
6596e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                }
6606e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
661f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                // We're doing a move along the circle, so move the selection as appropriate.
6626e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                mDoingMove = true;
6636e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                mHandler.removeCallbacksAndMessages(null);
6646e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                degrees = getDegreesFromCoords(eventX, eventY, true, isInnerCircle);
6656e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                if (degrees != -1) {
666f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                    value = reselectSelector(degrees, isInnerCircle[0], false, true);
6676e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    if (value != mLastValueSelected) {
668f2346bc730dc676b6ba6248f5b6949b90d201b9bPaul Sliwowski                        mHapticFeedbackController.tryVibrate();
6696e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        mLastValueSelected = value;
670b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                        mListener.onValueSelected(getCurrentItemShowing(), value, false);
6716e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    }
6726e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                }
6736e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                return true;
6746e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            case MotionEvent.ACTION_UP:
675b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                if (!mInputEnabled) {
676f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                    // If our touch input was disabled, tell the listener to re-enable us.
677b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                    Log.d(TAG, "Input was disabled, but received ACTION_UP.");
678b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                    mListener.onValueSelected(ENABLE_PICKER_INDEX, 1, false);
679b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                    return true;
680b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                }
681b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein
6826e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                mHandler.removeCallbacksAndMessages(null);
683b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                mDoingTouch = false;
6846e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
685f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                // If we're touching AM or PM, set it as selected, and tell the listener.
6866e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                if (mIsTouchingAmOrPm == AM || mIsTouchingAmOrPm == PM) {
6876e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    int isTouchingAmOrPm = mAmPmCirclesView.getIsTouchingAmOrPm(eventX, eventY);
6886e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    mAmPmCirclesView.setAmOrPmPressed(-1);
6896e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    mAmPmCirclesView.invalidate();
6906e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
6916e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    if (isTouchingAmOrPm == mIsTouchingAmOrPm) {
6926e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        mAmPmCirclesView.setAmOrPm(isTouchingAmOrPm);
6936e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        if (getIsCurrentlyAmOrPm() != isTouchingAmOrPm) {
6946e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                            mListener.onValueSelected(AMPM_INDEX, mIsTouchingAmOrPm, false);
6956e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                            setValueForItem(AMPM_INDEX, isTouchingAmOrPm);
6966e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        }
6976e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    }
6986e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    mIsTouchingAmOrPm = -1;
6996e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    break;
7006e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                }
7016e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
702f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                // If we have a legal degrees selected, set the value and tell the listener.
7036e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                if (mDownDegrees != -1) {
7046e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    degrees = getDegreesFromCoords(eventX, eventY, mDoingMove, isInnerCircle);
7056e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    if (degrees != -1) {
706f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                        value = reselectSelector(degrees, isInnerCircle[0], !mDoingMove, false);
707b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                        if (getCurrentItemShowing() == HOUR_INDEX && !mIs24HourMode) {
7086e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                            int amOrPm = getIsCurrentlyAmOrPm();
7096e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                            if (amOrPm == AM && value == 12) {
7106e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                                value = 0;
7116e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                            } else if (amOrPm == PM && value != 12) {
7126e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                                value += 12;
7136e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                            }
7146e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        }
7156e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        setValueForItem(getCurrentItemShowing(), value);
716b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                        mListener.onValueSelected(getCurrentItemShowing(), value, true);
7176e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    }
7186e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                }
7196e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                mDoingMove = false;
7206e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                return true;
7216e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            default:
7226e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                break;
7236e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
7246e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        return false;
7256e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
7266e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
727f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
728f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Set touch input as enabled or disabled, for use with keyboard mode.
729f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
730b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    public boolean trySettingInputEnabled(boolean inputEnabled) {
731b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        if (mDoingTouch && !inputEnabled) {
732b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            // If we're trying to disable input, but we're in the middle of a touch event,
733b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            // we'll allow the touch event to continue before disabling input.
734b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            return false;
735b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        }
736b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        mInputEnabled = inputEnabled;
737b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        mGrayBox.setVisibility(inputEnabled? View.INVISIBLE : View.VISIBLE);
738b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        return true;
739b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    }
740b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein
741f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
742f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Necessary for accessibility, to ensure we support "scrolling" forward and backward
743f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * in the circle.
744f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
745b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    @Override
746b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
747b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein      super.onInitializeAccessibilityNodeInfo(info);
748b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein      info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
749b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein      info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
750b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    }
751b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein
752f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
753f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Announce the currently-selected time when launched.
754f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
755b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    @Override
756b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
757b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
758f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            // Clear the event's current text so that only the current time will be spoken.
759b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            event.getText().clear();
760b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            Time time = new Time();
761b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            time.hour = getHours();
762b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            time.minute = getMinutes();
763b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            long millis = time.normalize(true);
764b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            int flags = DateUtils.FORMAT_SHOW_TIME;
765b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            if (mIs24HourMode) {
766b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                flags |= DateUtils.FORMAT_24HOUR;
767b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            }
768b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            String timeString = DateUtils.formatDateTime(getContext(), millis, flags);
769b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            event.getText().add(timeString);
770b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            return true;
771b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        }
772b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        return super.dispatchPopulateAccessibilityEvent(event);
773b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    }
774b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein
775f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
776f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * When scroll forward/backward events are received, jump the time to the higher/lower
777f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * discrete, visible value on the circle.
778f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
779b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    @SuppressLint("NewApi")
780b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    @Override
781b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    public boolean performAccessibilityAction(int action, Bundle arguments) {
782b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        if (super.performAccessibilityAction(action, arguments)) {
783b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            return true;
784b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        }
785b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein
786b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        int changeMultiplier = 0;
787b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) {
788b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            changeMultiplier = 1;
789b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        } else if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
790b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            changeMultiplier = -1;
791b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        }
792b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        if (changeMultiplier != 0) {
793b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            int value = getCurrentlyShowingValue();
794b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            int stepSize = 0;
795b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            int currentItemShowing = getCurrentItemShowing();
796b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            if (currentItemShowing == HOUR_INDEX) {
797b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                stepSize = HOUR_VALUE_TO_DEGREES_STEP_SIZE;
798b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                value %= 12;
799b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            } else if (currentItemShowing == MINUTE_INDEX) {
800b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                stepSize = MINUTE_VALUE_TO_DEGREES_STEP_SIZE;
801b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            }
802b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein
803b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            int degrees = value * stepSize;
804f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            degrees = snapOnly30s(degrees, changeMultiplier);
805b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            value = degrees / stepSize;
806b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            int maxValue = 0;
807b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            int minValue = 0;
808b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            if (currentItemShowing == HOUR_INDEX) {
809b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                if (mIs24HourMode) {
810b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                    maxValue = 23;
811b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                } else {
812b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                    maxValue = 12;
813b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                    minValue = 1;
814b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                }
815b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            } else {
816b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                maxValue = 55;
817b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            }
818b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            if (value > maxValue) {
819b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                // If we scrolled forward past the highest number, wrap around to the lowest.
820b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                value = minValue;
821b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            } else if (value < minValue) {
822b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                // If we scrolled backward past the lowest number, wrap around to the highest.
823b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein                value = maxValue;
824b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            }
825b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            setItem(currentItemShowing, value);
826b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            mListener.onValueSelected(currentItemShowing, value, false);
827b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            return true;
828b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        }
829b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein
830b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        return false;
8316e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
8326e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein}
833