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