blob: 83c86d5ce36bdea468567f75f119139d3a0718aa [file] [log] [blame]
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -07001/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
Aurimas Liutikas99441c52016-10-11 16:48:32 -070019import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
20import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
21
Andrei Stingaceanuf87b0e12016-07-04 17:40:14 +010022import android.annotation.TestApi;
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -070023import android.content.Context;
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -070024import android.content.res.TypedArray;
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -070025import android.os.Parcelable;
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -070026import android.text.format.DateFormat;
Fabrice Di Meglio014b8cf2013-12-18 12:51:22 -080027import android.text.format.DateUtils;
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -070028import android.util.AttributeSet;
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -070029import android.view.LayoutInflater;
30import android.view.View;
31import android.view.ViewGroup;
32import android.view.accessibility.AccessibilityEvent;
Alan Viverettedaf33ed2014-10-23 13:34:17 -070033import android.view.inputmethod.EditorInfo;
34import android.view.inputmethod.InputMethodManager;
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -070035
Aurimas Liutikas99441c52016-10-11 16:48:32 -070036import com.android.internal.R;
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -070037
Alan Viverettedaf33ed2014-10-23 13:34:17 -070038import libcore.icu.LocaleData;
39
Aurimas Liutikas99441c52016-10-11 16:48:32 -070040import java.util.Calendar;
Alan Viverettedaf33ed2014-10-23 13:34:17 -070041
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -070042/**
Alan Viverettedaf33ed2014-10-23 13:34:17 -070043 * A delegate implementing the basic spinner-based TimePicker.
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -070044 */
Alan Viverettedaf33ed2014-10-23 13:34:17 -070045class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate {
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -070046 private static final boolean DEFAULT_ENABLED_STATE = true;
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -070047 private static final int HOURS_IN_HALF_DAY = 12;
48
Alan Viverettedaf33ed2014-10-23 13:34:17 -070049 private final NumberPicker mHourSpinner;
50 private final NumberPicker mMinuteSpinner;
51 private final NumberPicker mAmPmSpinner;
52 private final EditText mHourSpinnerInput;
53 private final EditText mMinuteSpinnerInput;
54 private final EditText mAmPmSpinnerInput;
55 private final TextView mDivider;
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -070056
Alan Viverettedaf33ed2014-10-23 13:34:17 -070057 // Note that the legacy implementation of the TimePicker is
58 // using a button for toggling between AM/PM while the new
59 // version uses a NumberPicker spinner. Therefore the code
60 // accommodates these two cases to be backwards compatible.
61 private final Button mAmPmButton;
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -070062
Alan Viverettedaf33ed2014-10-23 13:34:17 -070063 private final String[] mAmPmStrings;
Alan Viverette26c563b2014-10-17 12:49:16 -070064
Alan Viverette68016a62015-11-19 17:10:54 -050065 private final Calendar mTempCalendar;
66
Alan Viverettedaf33ed2014-10-23 13:34:17 -070067 private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
Alan Viverettedaf33ed2014-10-23 13:34:17 -070068 private boolean mHourWithTwoDigit;
69 private char mHourFormat;
Fabrice Di Meglio014b8cf2013-12-18 12:51:22 -080070
Alan Viverette68016a62015-11-19 17:10:54 -050071 private boolean mIs24HourView;
72 private boolean mIsAm;
73
Chet Haase3053b2f2014-08-06 07:51:50 -070074 public TimePickerSpinnerDelegate(TimePicker delegator, Context context, AttributeSet attrs,
75 int defStyleAttr, int defStyleRes) {
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -070076 super(delegator, context);
77
78 // process style attributes
Alan Viverettedaf33ed2014-10-23 13:34:17 -070079 final TypedArray a = mContext.obtainStyledAttributes(
80 attrs, R.styleable.TimePicker, defStyleAttr, defStyleRes);
81 final int layoutResourceId = a.getResourceId(
82 R.styleable.TimePicker_legacyLayout, R.layout.time_picker_legacy);
Alan Viverette60727e02014-07-28 16:56:32 -070083 a.recycle();
Alan Viverette51344782014-07-16 17:39:27 -070084
Alan Viverettedaf33ed2014-10-23 13:34:17 -070085 final LayoutInflater inflater = LayoutInflater.from(mContext);
Adam Powell43da25c2017-05-23 15:56:59 -070086 final View view = inflater.inflate(layoutResourceId, mDelegator, true);
87 view.setSaveFromParentEnabled(false);
Alan Viverette67945c12014-10-15 13:20:47 -070088
Alan Viverettedaf33ed2014-10-23 13:34:17 -070089 // hour
Alan Viverette8e1a7292017-02-27 10:57:58 -050090 mHourSpinner = delegator.findViewById(R.id.hour);
Alan Viverettedaf33ed2014-10-23 13:34:17 -070091 mHourSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
92 public void onValueChange(NumberPicker spinner, int oldVal, int newVal) {
93 updateInputState();
Alan Viverette4420ae82015-11-16 16:10:56 -050094 if (!is24Hour()) {
Alan Viverettedaf33ed2014-10-23 13:34:17 -070095 if ((oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) ||
96 (oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1)) {
97 mIsAm = !mIsAm;
98 updateAmPmControl();
99 }
100 }
101 onTimeChanged();
102 }
103 });
Alan Viverette8e1a7292017-02-27 10:57:58 -0500104 mHourSpinnerInput = mHourSpinner.findViewById(R.id.numberpicker_input);
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700105 mHourSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700106
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700107 // divider (only for the new widget style)
Alan Viverette8e1a7292017-02-27 10:57:58 -0500108 mDivider = mDelegator.findViewById(R.id.divider);
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700109 if (mDivider != null) {
110 setDividerText();
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700111 }
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700112
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700113 // minute
Alan Viverette8e1a7292017-02-27 10:57:58 -0500114 mMinuteSpinner = mDelegator.findViewById(R.id.minute);
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700115 mMinuteSpinner.setMinValue(0);
116 mMinuteSpinner.setMaxValue(59);
117 mMinuteSpinner.setOnLongPressUpdateInterval(100);
118 mMinuteSpinner.setFormatter(NumberPicker.getTwoDigitFormatter());
119 mMinuteSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
120 public void onValueChange(NumberPicker spinner, int oldVal, int newVal) {
121 updateInputState();
122 int minValue = mMinuteSpinner.getMinValue();
123 int maxValue = mMinuteSpinner.getMaxValue();
124 if (oldVal == maxValue && newVal == minValue) {
125 int newHour = mHourSpinner.getValue() + 1;
Alan Viverette4420ae82015-11-16 16:10:56 -0500126 if (!is24Hour() && newHour == HOURS_IN_HALF_DAY) {
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700127 mIsAm = !mIsAm;
128 updateAmPmControl();
129 }
130 mHourSpinner.setValue(newHour);
131 } else if (oldVal == minValue && newVal == maxValue) {
132 int newHour = mHourSpinner.getValue() - 1;
Alan Viverette4420ae82015-11-16 16:10:56 -0500133 if (!is24Hour() && newHour == HOURS_IN_HALF_DAY - 1) {
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700134 mIsAm = !mIsAm;
135 updateAmPmControl();
136 }
137 mHourSpinner.setValue(newHour);
138 }
139 onTimeChanged();
140 }
141 });
Alan Viverette8e1a7292017-02-27 10:57:58 -0500142 mMinuteSpinnerInput = mMinuteSpinner.findViewById(R.id.numberpicker_input);
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700143 mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
144
145 // Get the localized am/pm strings and use them in the spinner.
146 mAmPmStrings = getAmPmStrings(context);
147
148 // am/pm
149 final View amPmView = mDelegator.findViewById(R.id.amPm);
150 if (amPmView instanceof Button) {
151 mAmPmSpinner = null;
152 mAmPmSpinnerInput = null;
153 mAmPmButton = (Button) amPmView;
154 mAmPmButton.setOnClickListener(new View.OnClickListener() {
155 public void onClick(View button) {
156 button.requestFocus();
157 mIsAm = !mIsAm;
158 updateAmPmControl();
159 onTimeChanged();
160 }
161 });
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700162 } else {
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700163 mAmPmButton = null;
164 mAmPmSpinner = (NumberPicker) amPmView;
165 mAmPmSpinner.setMinValue(0);
166 mAmPmSpinner.setMaxValue(1);
167 mAmPmSpinner.setDisplayedValues(mAmPmStrings);
168 mAmPmSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
169 public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
170 updateInputState();
171 picker.requestFocus();
172 mIsAm = !mIsAm;
173 updateAmPmControl();
174 onTimeChanged();
175 }
176 });
Alan Viverette8e1a7292017-02-27 10:57:58 -0500177 mAmPmSpinnerInput = mAmPmSpinner.findViewById(R.id.numberpicker_input);
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700178 mAmPmSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE);
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700179 }
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700180
181 if (isAmPmAtStart()) {
182 // Move the am/pm view to the beginning
Alan Viverette8e1a7292017-02-27 10:57:58 -0500183 ViewGroup amPmParent = delegator.findViewById(R.id.timePickerLayout);
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700184 amPmParent.removeView(amPmView);
185 amPmParent.addView(amPmView, 0);
186 // Swap layout margins if needed. They may be not symmetrical (Old Standard Theme
187 // for example and not for Holo Theme)
188 ViewGroup.MarginLayoutParams lp =
189 (ViewGroup.MarginLayoutParams) amPmView.getLayoutParams();
190 final int startMargin = lp.getMarginStart();
191 final int endMargin = lp.getMarginEnd();
192 if (startMargin != endMargin) {
193 lp.setMarginStart(endMargin);
194 lp.setMarginEnd(startMargin);
195 }
196 }
197
198 getHourFormatData();
199
200 // update controls to initial state
201 updateHourControl();
202 updateMinuteControl();
203 updateAmPmControl();
204
205 // set to current time
Alan Viverette68016a62015-11-19 17:10:54 -0500206 mTempCalendar = Calendar.getInstance(mLocale);
Alan Viverette4420ae82015-11-16 16:10:56 -0500207 setHour(mTempCalendar.get(Calendar.HOUR_OF_DAY));
208 setMinute(mTempCalendar.get(Calendar.MINUTE));
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700209
210 if (!isEnabled()) {
211 setEnabled(false);
212 }
213
214 // set the content descriptions
215 setContentDescriptions();
216
217 // If not explicitly specified this view is important for accessibility.
218 if (mDelegator.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
219 mDelegator.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
220 }
221 }
222
Aurimas Liutikasab14d822017-01-24 17:46:10 -0800223 @Override
224 public boolean validateInput() {
225 return true;
226 }
227
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700228 private void getHourFormatData() {
Alan Viverette4420ae82015-11-16 16:10:56 -0500229 final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale,
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700230 (mIs24HourView) ? "Hm" : "hm");
231 final int lengthPattern = bestDateTimePattern.length();
232 mHourWithTwoDigit = false;
233 char hourFormat = '\0';
234 // Check if the returned pattern is single or double 'H', 'h', 'K', 'k'. We also save
235 // the hour format that we found.
236 for (int i = 0; i < lengthPattern; i++) {
237 final char c = bestDateTimePattern.charAt(i);
238 if (c == 'H' || c == 'h' || c == 'K' || c == 'k') {
239 mHourFormat = c;
240 if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) {
241 mHourWithTwoDigit = true;
242 }
243 break;
244 }
245 }
246 }
247
248 private boolean isAmPmAtStart() {
Alan Viverette4420ae82015-11-16 16:10:56 -0500249 final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale,
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700250 "hm" /* skeleton */);
251
252 return bestDateTimePattern.startsWith("a");
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700253 }
254
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700255 /**
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700256 * The time separator is defined in the Unicode CLDR and cannot be supposed to be ":".
257 *
258 * See http://unicode.org/cldr/trac/browser/trunk/common/main
259 *
260 * We pass the correct "skeleton" depending on 12 or 24 hours view and then extract the
261 * separator as the character which is just after the hour marker in the returned pattern.
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700262 */
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700263 private void setDividerText() {
264 final String skeleton = (mIs24HourView) ? "Hm" : "hm";
Alan Viverette4420ae82015-11-16 16:10:56 -0500265 final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale,
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700266 skeleton);
267 final String separatorText;
268 int hourIndex = bestDateTimePattern.lastIndexOf('H');
269 if (hourIndex == -1) {
270 hourIndex = bestDateTimePattern.lastIndexOf('h');
271 }
272 if (hourIndex == -1) {
273 // Default case
274 separatorText = ":";
275 } else {
276 int minuteIndex = bestDateTimePattern.indexOf('m', hourIndex + 1);
277 if (minuteIndex == -1) {
278 separatorText = Character.toString(bestDateTimePattern.charAt(hourIndex + 1));
279 } else {
280 separatorText = bestDateTimePattern.substring(hourIndex + 1, minuteIndex);
281 }
282 }
283 mDivider.setText(separatorText);
284 }
285
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700286 @Override
Felipe Lemef480e8c2017-08-10 18:38:44 -0700287 public void setDate(int hour, int minute) {
288 setCurrentHour(hour, false);
289 setCurrentMinute(minute, false);
290
291 onTimeChanged();
292 }
293
294 @Override
Alan Viverette4420ae82015-11-16 16:10:56 -0500295 public void setHour(int hour) {
296 setCurrentHour(hour, true);
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700297 }
298
Alan Viverette646a0f82015-03-18 13:24:07 -0700299 private void setCurrentHour(int currentHour, boolean notifyTimeChanged) {
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700300 // why was Integer used in the first place?
Alan Viverette4420ae82015-11-16 16:10:56 -0500301 if (currentHour == getHour()) {
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700302 return;
303 }
Felipe Lemef480e8c2017-08-10 18:38:44 -0700304 resetAutofilledValue();
Alan Viverette4420ae82015-11-16 16:10:56 -0500305 if (!is24Hour()) {
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700306 // convert [0,23] ordinal to wall clock display
307 if (currentHour >= HOURS_IN_HALF_DAY) {
308 mIsAm = false;
309 if (currentHour > HOURS_IN_HALF_DAY) {
310 currentHour = currentHour - HOURS_IN_HALF_DAY;
311 }
312 } else {
313 mIsAm = true;
314 if (currentHour == 0) {
315 currentHour = HOURS_IN_HALF_DAY;
316 }
317 }
318 updateAmPmControl();
319 }
320 mHourSpinner.setValue(currentHour);
321 if (notifyTimeChanged) {
322 onTimeChanged();
323 }
324 }
325
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700326 @Override
Alan Viverette4420ae82015-11-16 16:10:56 -0500327 public int getHour() {
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700328 int currentHour = mHourSpinner.getValue();
Alan Viverette4420ae82015-11-16 16:10:56 -0500329 if (is24Hour()) {
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700330 return currentHour;
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700331 } else if (mIsAm) {
332 return currentHour % HOURS_IN_HALF_DAY;
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700333 } else {
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700334 return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY;
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700335 }
336 }
337
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700338 @Override
Alan Viverette4420ae82015-11-16 16:10:56 -0500339 public void setMinute(int minute) {
Felipe Lemef480e8c2017-08-10 18:38:44 -0700340 setCurrentMinute(minute, true);
341 }
342
343 private void setCurrentMinute(int minute, boolean notifyTimeChanged) {
Alan Viverette4420ae82015-11-16 16:10:56 -0500344 if (minute == getMinute()) {
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700345 return;
346 }
Felipe Lemef480e8c2017-08-10 18:38:44 -0700347 resetAutofilledValue();
Alan Viverette4420ae82015-11-16 16:10:56 -0500348 mMinuteSpinner.setValue(minute);
Felipe Lemef480e8c2017-08-10 18:38:44 -0700349 if (notifyTimeChanged) {
350 onTimeChanged();
351 }
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700352 }
353
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700354 @Override
Alan Viverette4420ae82015-11-16 16:10:56 -0500355 public int getMinute() {
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700356 return mMinuteSpinner.getValue();
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700357 }
358
Alan Viverette4420ae82015-11-16 16:10:56 -0500359 public void setIs24Hour(boolean is24Hour) {
360 if (mIs24HourView == is24Hour) {
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700361 return;
362 }
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700363 // cache the current hour since spinner range changes and BEFORE changing mIs24HourView!!
Alan Viverette4420ae82015-11-16 16:10:56 -0500364 int currentHour = getHour();
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700365 // Order is important here.
Alan Viverette4420ae82015-11-16 16:10:56 -0500366 mIs24HourView = is24Hour;
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700367 getHourFormatData();
368 updateHourControl();
369 // set value after spinner range is updated
370 setCurrentHour(currentHour, false);
371 updateMinuteControl();
372 updateAmPmControl();
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700373 }
374
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700375 @Override
Alan Viverette4420ae82015-11-16 16:10:56 -0500376 public boolean is24Hour() {
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700377 return mIs24HourView;
378 }
379
380 @Override
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700381 public void setEnabled(boolean enabled) {
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700382 mMinuteSpinner.setEnabled(enabled);
383 if (mDivider != null) {
384 mDivider.setEnabled(enabled);
385 }
386 mHourSpinner.setEnabled(enabled);
387 if (mAmPmSpinner != null) {
388 mAmPmSpinner.setEnabled(enabled);
389 } else {
390 mAmPmButton.setEnabled(enabled);
391 }
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700392 mIsEnabled = enabled;
393 }
394
395 @Override
396 public boolean isEnabled() {
397 return mIsEnabled;
398 }
399
400 @Override
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700401 public int getBaseline() {
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700402 return mHourSpinner.getBaseline();
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700403 }
404
405 @Override
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700406 public Parcelable onSaveInstanceState(Parcelable superState) {
Alan Viverette6b3f85f2016-03-01 16:48:04 -0500407 return new SavedState(superState, getHour(), getMinute(), is24Hour());
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700408 }
409
410 @Override
411 public void onRestoreInstanceState(Parcelable state) {
Alan Viverette6b3f85f2016-03-01 16:48:04 -0500412 if (state instanceof SavedState) {
413 final SavedState ss = (SavedState) state;
414 setHour(ss.getHour());
415 setMinute(ss.getMinute());
416 }
Fabrice Di Meglio014b8cf2013-12-18 12:51:22 -0800417 }
418
419 @Override
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700420 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
Fabrice Di Meglio014b8cf2013-12-18 12:51:22 -0800421 onPopulateAccessibilityEvent(event);
422 return true;
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700423 }
424
425 @Override
426 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
Fabrice Di Meglio014b8cf2013-12-18 12:51:22 -0800427 int flags = DateUtils.FORMAT_SHOW_TIME;
428 if (mIs24HourView) {
429 flags |= DateUtils.FORMAT_24HOUR;
430 } else {
431 flags |= DateUtils.FORMAT_12HOUR;
432 }
Alan Viverette4420ae82015-11-16 16:10:56 -0500433 mTempCalendar.set(Calendar.HOUR_OF_DAY, getHour());
434 mTempCalendar.set(Calendar.MINUTE, getMinute());
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700435 String selectedDateUtterance = DateUtils.formatDateTime(mContext,
Fabrice Di Meglio014b8cf2013-12-18 12:51:22 -0800436 mTempCalendar.getTimeInMillis(), flags);
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700437 event.getText().add(selectedDateUtterance);
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700438 }
439
Andrei Stingaceanuf87b0e12016-07-04 17:40:14 +0100440 /** @hide */
441 @Override
442 @TestApi
443 public View getHourView() {
444 return mHourSpinnerInput;
445 }
446
447 /** @hide */
448 @Override
449 @TestApi
450 public View getMinuteView() {
451 return mMinuteSpinnerInput;
452 }
453
454 /** @hide */
455 @Override
456 @TestApi
457 public View getAmView() {
458 return mAmPmSpinnerInput;
459 }
460
461 /** @hide */
462 @Override
463 @TestApi
464 public View getPmView() {
465 return mAmPmSpinnerInput;
466 }
467
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700468 private void updateInputState() {
469 // Make sure that if the user changes the value and the IME is active
470 // for one of the inputs if this widget, the IME is closed. If the user
471 // changed the value via the IME and there is a next input the IME will
472 // be shown, otherwise the user chose another means of changing the
473 // value and having the IME up makes no sense.
Yohei Yukawa484d4af2018-09-17 16:47:08 -0700474 InputMethodManager inputMethodManager = mContext.getSystemService(InputMethodManager.class);
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700475 if (inputMethodManager != null) {
476 if (inputMethodManager.isActive(mHourSpinnerInput)) {
477 mHourSpinnerInput.clearFocus();
478 inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
479 } else if (inputMethodManager.isActive(mMinuteSpinnerInput)) {
480 mMinuteSpinnerInput.clearFocus();
481 inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
482 } else if (inputMethodManager.isActive(mAmPmSpinnerInput)) {
483 mAmPmSpinnerInput.clearFocus();
484 inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
485 }
486 }
487 }
488
489 private void updateAmPmControl() {
Alan Viverette4420ae82015-11-16 16:10:56 -0500490 if (is24Hour()) {
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700491 if (mAmPmSpinner != null) {
492 mAmPmSpinner.setVisibility(View.GONE);
493 } else {
494 mAmPmButton.setVisibility(View.GONE);
495 }
496 } else {
497 int index = mIsAm ? Calendar.AM : Calendar.PM;
498 if (mAmPmSpinner != null) {
499 mAmPmSpinner.setValue(index);
500 mAmPmSpinner.setVisibility(View.VISIBLE);
501 } else {
502 mAmPmButton.setText(mAmPmStrings[index]);
503 mAmPmButton.setVisibility(View.VISIBLE);
504 }
505 }
506 mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
507 }
508
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700509 private void onTimeChanged() {
510 mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
511 if (mOnTimeChangedListener != null) {
Alan Viverette4420ae82015-11-16 16:10:56 -0500512 mOnTimeChangedListener.onTimeChanged(mDelegator, getHour(),
513 getMinute());
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700514 }
Felipe Leme305b72c2017-02-27 12:46:04 -0800515 if (mAutoFillChangeListener != null) {
516 mAutoFillChangeListener.onTimeChanged(mDelegator, getHour(), getMinute());
517 }
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700518 }
519
520 private void updateHourControl() {
Alan Viverette4420ae82015-11-16 16:10:56 -0500521 if (is24Hour()) {
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700522 // 'k' means 1-24 hour
523 if (mHourFormat == 'k') {
524 mHourSpinner.setMinValue(1);
525 mHourSpinner.setMaxValue(24);
526 } else {
527 mHourSpinner.setMinValue(0);
528 mHourSpinner.setMaxValue(23);
529 }
530 } else {
531 // 'K' means 0-11 hour
532 if (mHourFormat == 'K') {
533 mHourSpinner.setMinValue(0);
534 mHourSpinner.setMaxValue(11);
535 } else {
536 mHourSpinner.setMinValue(1);
537 mHourSpinner.setMaxValue(12);
538 }
539 }
540 mHourSpinner.setFormatter(mHourWithTwoDigit ? NumberPicker.getTwoDigitFormatter() : null);
541 }
542
543 private void updateMinuteControl() {
Alan Viverette4420ae82015-11-16 16:10:56 -0500544 if (is24Hour()) {
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700545 mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE);
546 } else {
547 mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
548 }
549 }
550
551 private void setContentDescriptions() {
552 // Minute
553 trySetContentDescription(mMinuteSpinner, R.id.increment,
554 R.string.time_picker_increment_minute_button);
555 trySetContentDescription(mMinuteSpinner, R.id.decrement,
556 R.string.time_picker_decrement_minute_button);
557 // Hour
558 trySetContentDescription(mHourSpinner, R.id.increment,
559 R.string.time_picker_increment_hour_button);
560 trySetContentDescription(mHourSpinner, R.id.decrement,
561 R.string.time_picker_decrement_hour_button);
562 // AM/PM
563 if (mAmPmSpinner != null) {
564 trySetContentDescription(mAmPmSpinner, R.id.increment,
565 R.string.time_picker_increment_set_pm_button);
566 trySetContentDescription(mAmPmSpinner, R.id.decrement,
567 R.string.time_picker_decrement_set_am_button);
568 }
569 }
570
571 private void trySetContentDescription(View root, int viewId, int contDescResId) {
572 View target = root.findViewById(viewId);
573 if (target != null) {
574 target.setContentDescription(mContext.getString(contDescResId));
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700575 }
576 }
577
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700578 public static String[] getAmPmStrings(Context context) {
579 String[] result = new String[2];
580 LocaleData d = LocaleData.get(context.getResources().getConfiguration().locale);
Narayan Kamath14b50392015-07-08 18:45:39 +0100581 result[0] = d.amPm[0].length() > 4 ? d.narrowAm : d.amPm[0];
582 result[1] = d.amPm[1].length() > 4 ? d.narrowPm : d.amPm[1];
Alan Viverettedaf33ed2014-10-23 13:34:17 -0700583 return result;
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700584 }
Fabrice Di Meglioeeff63a2013-08-05 12:07:24 -0700585}