blob: c26cb24c6c1719c2d2f329f68afc8aca9ff8c08f [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2007 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
19import android.annotation.Widget;
20import android.content.Context;
Svetoslav Ganovf5926962011-07-12 12:26:20 -070021import android.content.res.Configuration;
Svetoslav Ganov4243dc32011-01-18 19:39:57 -080022import android.content.res.TypedArray;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.os.Parcel;
24import android.os.Parcelable;
Fabrice Di Meglio64902bd2013-08-15 17:49:49 -070025import android.text.format.DateFormat;
Svetoslav Ganov8a2a8952011-01-27 17:40:13 -080026import android.text.format.DateUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.util.AttributeSet;
28import android.view.LayoutInflater;
29import android.view.View;
Fabrice Di Meglio64902bd2013-08-15 17:49:49 -070030import android.view.ViewGroup;
Svetoslav Ganov8a2a8952011-01-27 17:40:13 -080031import android.view.accessibility.AccessibilityEvent;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080032import android.view.accessibility.AccessibilityNodeInfo;
Svetoslav Ganova53efe92011-09-08 18:08:36 -070033import android.view.inputmethod.EditorInfo;
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -070034import android.view.inputmethod.InputMethodManager;
Svetoslav Ganovcedc4462011-01-19 19:25:46 -080035import android.widget.NumberPicker.OnValueChangeListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036
Svetoslav Ganova53efe92011-09-08 18:08:36 -070037import com.android.internal.R;
38
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039import java.text.DateFormatSymbols;
40import java.util.Calendar;
Svetoslav Ganovf5926962011-07-12 12:26:20 -070041import java.util.Locale;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042
43/**
Svetoslav Ganov4243dc32011-01-18 19:39:57 -080044 * A view for selecting the time of day, in either 24 hour or AM/PM mode. The
45 * hour, each minute digit, and AM/PM (if applicable) can be conrolled by
46 * vertical spinners. The hour can be entered by keyboard input. Entering in two
47 * digit hours can be accomplished by hitting two digits within a timeout of
48 * about a second (e.g. '1' then '2' to select 12). The minutes can be entered
49 * by entering single digits. Under AM/PM mode, the user can hit 'a', 'A", 'p'
50 * or 'P' to pick. For a dialog using this view, see
51 * {@link android.app.TimePickerDialog}.
52 *<p>
Scott Main4c359b72012-07-24 15:51:27 -070053 * See the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a>
54 * guide.
Svetoslav Ganov4243dc32011-01-18 19:39:57 -080055 * </p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056 */
57@Widget
58public class TimePicker extends FrameLayout {
Svetoslav Ganov206316a2010-11-19 00:04:05 -080059
Svetoslav Ganov25f84f32010-12-29 22:39:54 -080060 private static final boolean DEFAULT_ENABLED_STATE = true;
61
Svetoslav Ganov4243dc32011-01-18 19:39:57 -080062 private static final int HOURS_IN_HALF_DAY = 12;
63
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080064 /**
Svetoslav Ganov4243dc32011-01-18 19:39:57 -080065 * A no-op callback used in the constructor to avoid null checks later in
66 * the code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080067 */
68 private static final OnTimeChangedListener NO_OP_CHANGE_LISTENER = new OnTimeChangedListener() {
69 public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
70 }
71 };
Svetoslav Ganov4243dc32011-01-18 19:39:57 -080072
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080073 // state
Svetoslav Ganov4243dc32011-01-18 19:39:57 -080074 private boolean mIs24HourView;
75
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080076 private boolean mIsAm;
77
78 // ui components
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080079 private final NumberPicker mHourSpinner;
Svetoslav Ganov4243dc32011-01-18 19:39:57 -080080
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080081 private final NumberPicker mMinuteSpinner;
Svetoslav Ganov4243dc32011-01-18 19:39:57 -080082
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080083 private final NumberPicker mAmPmSpinner;
Svetoslav Ganov4243dc32011-01-18 19:39:57 -080084
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -070085 private final EditText mHourSpinnerInput;
86
87 private final EditText mMinuteSpinnerInput;
88
89 private final EditText mAmPmSpinnerInput;
90
Svetoslav Ganov206316a2010-11-19 00:04:05 -080091 private final TextView mDivider;
92
Svetoslav Ganov4243dc32011-01-18 19:39:57 -080093 // Note that the legacy implementation of the TimePicker is
94 // using a button for toggling between AM/PM while the new
95 // version uses a NumberPicker spinner. Therefore the code
96 // accommodates these two cases to be backwards compatible.
97 private final Button mAmPmButton;
98
Svetoslav Ganov206316a2010-11-19 00:04:05 -080099 private final String[] mAmPmStrings;
100
Svetoslav Ganov25f84f32010-12-29 22:39:54 -0800101 private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -0800102
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800103 // callbacks
104 private OnTimeChangedListener mOnTimeChangedListener;
105
Svetoslav Ganov8a2a8952011-01-27 17:40:13 -0800106 private Calendar mTempCalendar;
107
Svetoslav Ganovf5926962011-07-12 12:26:20 -0700108 private Locale mCurrentLocale;
109
Fabrice Di Meglio64902bd2013-08-15 17:49:49 -0700110 private boolean mHourWithTwoDigit;
111 private char mHourFormat;
112
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800113 /**
114 * The callback interface used to indicate the time has been adjusted.
115 */
116 public interface OnTimeChangedListener {
117
118 /**
119 * @param view The view associated with this listener.
120 * @param hourOfDay The current hour.
121 * @param minute The current minute.
122 */
123 void onTimeChanged(TimePicker view, int hourOfDay, int minute);
124 }
125
126 public TimePicker(Context context) {
127 this(context, null);
128 }
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800129
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800130 public TimePicker(Context context, AttributeSet attrs) {
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800131 this(context, attrs, R.attr.timePickerStyle);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800132 }
133
134 public TimePicker(Context context, AttributeSet attrs, int defStyle) {
135 super(context, attrs, defStyle);
136
Svetoslav Ganovf5926962011-07-12 12:26:20 -0700137 // initialization based on locale
138 setCurrentLocale(Locale.getDefault());
139
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800140 // process style attributes
141 TypedArray attributesArray = context.obtainStyledAttributes(
142 attrs, R.styleable.TimePicker, defStyle, 0);
143 int layoutResourceId = attributesArray.getResourceId(
Svetoslav Ganov53b948d2012-03-02 14:03:35 -0800144 R.styleable.TimePicker_internalLayout, R.layout.time_picker);
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800145 attributesArray.recycle();
146
147 LayoutInflater inflater = (LayoutInflater) context.getSystemService(
148 Context.LAYOUT_INFLATER_SERVICE);
149 inflater.inflate(layoutResourceId, this, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800150
151 // hour
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800152 mHourSpinner = (NumberPicker) findViewById(R.id.hour);
Svetoslav Ganovcedc4462011-01-19 19:25:46 -0800153 mHourSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800154 public void onValueChange(NumberPicker spinner, int oldVal, int newVal) {
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -0700155 updateInputState();
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800156 if (!is24HourView()) {
Svetoslav Ganovbe17a7f2011-01-31 14:21:56 -0800157 if ((oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY)
158 || (oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1)) {
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800159 mIsAm = !mIsAm;
160 updateAmPmControl();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800161 }
162 }
163 onTimeChanged();
164 }
165 });
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -0700166 mHourSpinnerInput = (EditText) mHourSpinner.findViewById(R.id.numberpicker_input);
167 mHourSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800168
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800169 // divider (only for the new widget style)
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800170 mDivider = (TextView) findViewById(R.id.divider);
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800171 if (mDivider != null) {
Fabrice Di Meglio64902bd2013-08-15 17:49:49 -0700172 setDividerText();
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800173 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800174
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800175 // minute
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800176 mMinuteSpinner = (NumberPicker) findViewById(R.id.minute);
177 mMinuteSpinner.setMinValue(0);
178 mMinuteSpinner.setMaxValue(59);
179 mMinuteSpinner.setOnLongPressUpdateInterval(100);
Fabrice Di Megliod88e3052012-09-21 12:15:23 -0700180 mMinuteSpinner.setFormatter(NumberPicker.getTwoDigitFormatter());
Svetoslav Ganovcedc4462011-01-19 19:25:46 -0800181 mMinuteSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800182 public void onValueChange(NumberPicker spinner, int oldVal, int newVal) {
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -0700183 updateInputState();
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800184 int minValue = mMinuteSpinner.getMinValue();
185 int maxValue = mMinuteSpinner.getMaxValue();
186 if (oldVal == maxValue && newVal == minValue) {
Svetoslav Ganovbe17a7f2011-01-31 14:21:56 -0800187 int newHour = mHourSpinner.getValue() + 1;
188 if (!is24HourView() && newHour == HOURS_IN_HALF_DAY) {
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800189 mIsAm = !mIsAm;
190 updateAmPmControl();
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800191 }
Svetoslav Ganovbe17a7f2011-01-31 14:21:56 -0800192 mHourSpinner.setValue(newHour);
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800193 } else if (oldVal == minValue && newVal == maxValue) {
Svetoslav Ganovbe17a7f2011-01-31 14:21:56 -0800194 int newHour = mHourSpinner.getValue() - 1;
195 if (!is24HourView() && newHour == HOURS_IN_HALF_DAY - 1) {
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800196 mIsAm = !mIsAm;
197 updateAmPmControl();
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800198 }
Svetoslav Ganovbe17a7f2011-01-31 14:21:56 -0800199 mHourSpinner.setValue(newHour);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800200 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800201 onTimeChanged();
202 }
203 });
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -0700204 mMinuteSpinnerInput = (EditText) mMinuteSpinner.findViewById(R.id.numberpicker_input);
205 mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800206
207 /* Get the localized am/pm strings and use them in the spinner */
208 mAmPmStrings = new DateFormatSymbols().getAmPmStrings();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800209
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800210 // am/pm
211 View amPmView = findViewById(R.id.amPm);
212 if (amPmView instanceof Button) {
213 mAmPmSpinner = null;
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -0700214 mAmPmSpinnerInput = null;
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800215 mAmPmButton = (Button) amPmView;
216 mAmPmButton.setOnClickListener(new OnClickListener() {
217 public void onClick(View button) {
218 button.requestFocus();
219 mIsAm = !mIsAm;
220 updateAmPmControl();
SeongJae Parkf821ce72012-01-17 15:20:23 +0900221 onTimeChanged();
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800222 }
223 });
224 } else {
225 mAmPmButton = null;
226 mAmPmSpinner = (NumberPicker) amPmView;
227 mAmPmSpinner.setMinValue(0);
228 mAmPmSpinner.setMaxValue(1);
229 mAmPmSpinner.setDisplayedValues(mAmPmStrings);
Svetoslav Ganovcedc4462011-01-19 19:25:46 -0800230 mAmPmSpinner.setOnValueChangedListener(new OnValueChangeListener() {
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800231 public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -0700232 updateInputState();
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800233 picker.requestFocus();
234 mIsAm = !mIsAm;
235 updateAmPmControl();
SeongJae Parkf821ce72012-01-17 15:20:23 +0900236 onTimeChanged();
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800237 }
238 });
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -0700239 mAmPmSpinnerInput = (EditText) mAmPmSpinner.findViewById(R.id.numberpicker_input);
240 mAmPmSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE);
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800241 }
242
Fabrice Di Meglio64902bd2013-08-15 17:49:49 -0700243 if (isAmPmAtStart()) {
244 // Move the am/pm view to the beginning
245 ViewGroup amPmParent = (ViewGroup) findViewById(R.id.timePickerLayout);
246 amPmParent.removeView(amPmView);
247 amPmParent.addView(amPmView, 0);
248 // Swap layout margins if needed. They may be not symmetrical (Old Standard Theme for
249 // example and not for Holo Theme)
250 ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) amPmView.getLayoutParams();
251 final int startMargin = lp.getMarginStart();
252 final int endMargin = lp.getMarginEnd();
253 if (startMargin != endMargin) {
254 lp.setMarginStart(endMargin);
255 lp.setMarginEnd(startMargin);
256 }
257 }
258
259 getHourFormatData();
260
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800261 // update controls to initial state
262 updateHourControl();
Hyejin Kim6ac46522013-03-20 11:32:44 +0900263 updateMinuteControl();
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800264 updateAmPmControl();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800265
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800266 setOnTimeChangedListener(NO_OP_CHANGE_LISTENER);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800267
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800268 // set to current time
Svetoslav Ganov8a2a8952011-01-27 17:40:13 -0800269 setCurrentHour(mTempCalendar.get(Calendar.HOUR_OF_DAY));
270 setCurrentMinute(mTempCalendar.get(Calendar.MINUTE));
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800271
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800272 if (!isEnabled()) {
273 setEnabled(false);
274 }
Svetoslav Ganov3fec3fe2011-09-01 14:48:37 -0700275
276 // set the content descriptions
Svetoslav Ganov2cdedff2011-10-03 14:18:42 -0700277 setContentDescriptions();
Svetoslav Ganov42138042012-03-20 11:51:39 -0700278
279 // If not explicitly specified this view is important for accessibility.
280 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
281 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
282 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800283 }
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800284
Fabrice Di Meglio64902bd2013-08-15 17:49:49 -0700285 private void getHourFormatData() {
286 final Locale defaultLocale = Locale.getDefault();
287 final String bestDateTimePattern = DateFormat.getBestDateTimePattern(defaultLocale,
288 (mIs24HourView) ? "Hm" : "hm");
289 final int lengthPattern = bestDateTimePattern.length();
290 mHourWithTwoDigit = false;
291 char hourFormat = '\0';
292 // Check if the returned pattern is single or double 'H', 'h', 'K', 'k'. We also save
293 // the hour format that we found.
294 for (int i = 0; i < lengthPattern; i++) {
295 final char c = bestDateTimePattern.charAt(i);
296 if (c == 'H' || c == 'h' || c == 'K' || c == 'k') {
297 mHourFormat = c;
298 if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) {
299 mHourWithTwoDigit = true;
300 }
301 break;
302 }
303 }
304 }
305
306 private boolean isAmPmAtStart() {
307 final Locale defaultLocale = Locale.getDefault();
308 final String bestDateTimePattern = DateFormat.getBestDateTimePattern(defaultLocale,
309 "hm" /* skeleton */);
310
311 return bestDateTimePattern.startsWith("a");
312 }
313
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800314 @Override
315 public void setEnabled(boolean enabled) {
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -0800316 if (mIsEnabled == enabled) {
317 return;
318 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800319 super.setEnabled(enabled);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800320 mMinuteSpinner.setEnabled(enabled);
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800321 if (mDivider != null) {
322 mDivider.setEnabled(enabled);
323 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800324 mHourSpinner.setEnabled(enabled);
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800325 if (mAmPmSpinner != null) {
326 mAmPmSpinner.setEnabled(enabled);
327 } else {
328 mAmPmButton.setEnabled(enabled);
329 }
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -0800330 mIsEnabled = enabled;
331 }
332
333 @Override
334 public boolean isEnabled() {
335 return mIsEnabled;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800336 }
337
Svetoslav Ganovf5926962011-07-12 12:26:20 -0700338 @Override
339 protected void onConfigurationChanged(Configuration newConfig) {
340 super.onConfigurationChanged(newConfig);
341 setCurrentLocale(newConfig.locale);
342 }
343
344 /**
345 * Sets the current locale.
346 *
347 * @param locale The current locale.
348 */
349 private void setCurrentLocale(Locale locale) {
350 if (locale.equals(mCurrentLocale)) {
351 return;
352 }
353 mCurrentLocale = locale;
354 mTempCalendar = Calendar.getInstance(locale);
355 }
356
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800357 /**
358 * Used to save / restore state of time picker
359 */
360 private static class SavedState extends BaseSavedState {
361
362 private final int mHour;
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800363
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800364 private final int mMinute;
365
366 private SavedState(Parcelable superState, int hour, int minute) {
367 super(superState);
368 mHour = hour;
369 mMinute = minute;
370 }
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800371
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800372 private SavedState(Parcel in) {
373 super(in);
374 mHour = in.readInt();
375 mMinute = in.readInt();
376 }
377
378 public int getHour() {
379 return mHour;
380 }
381
382 public int getMinute() {
383 return mMinute;
384 }
385
386 @Override
387 public void writeToParcel(Parcel dest, int flags) {
388 super.writeToParcel(dest, flags);
389 dest.writeInt(mHour);
390 dest.writeInt(mMinute);
391 }
392
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -0700393 @SuppressWarnings({"unused", "hiding"})
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800394 public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800395 public SavedState createFromParcel(Parcel in) {
396 return new SavedState(in);
397 }
398
399 public SavedState[] newArray(int size) {
400 return new SavedState[size];
401 }
402 };
403 }
404
405 @Override
406 protected Parcelable onSaveInstanceState() {
407 Parcelable superState = super.onSaveInstanceState();
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800408 return new SavedState(superState, getCurrentHour(), getCurrentMinute());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800409 }
410
411 @Override
412 protected void onRestoreInstanceState(Parcelable state) {
413 SavedState ss = (SavedState) state;
414 super.onRestoreInstanceState(ss.getSuperState());
415 setCurrentHour(ss.getHour());
416 setCurrentMinute(ss.getMinute());
417 }
418
419 /**
420 * Set the callback that indicates the time has been adjusted by the user.
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800421 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800422 * @param onTimeChangedListener the callback, should not be null.
423 */
424 public void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener) {
425 mOnTimeChangedListener = onTimeChangedListener;
426 }
427
428 /**
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800429 * @return The current hour in the range (0-23).
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800430 */
431 public Integer getCurrentHour() {
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800432 int currentHour = mHourSpinner.getValue();
Svetoslav Ganovbe17a7f2011-01-31 14:21:56 -0800433 if (is24HourView()) {
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800434 return currentHour;
Svetoslav Ganovbe17a7f2011-01-31 14:21:56 -0800435 } else if (mIsAm) {
436 return currentHour % HOURS_IN_HALF_DAY;
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800437 } else {
Svetoslav Ganovbe17a7f2011-01-31 14:21:56 -0800438 return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY;
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800439 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800440 }
441
442 /**
443 * Set the current hour.
444 */
445 public void setCurrentHour(Integer currentHour) {
Fabrice Di Meglio138de8e2013-08-23 19:28:55 -0700446 setCurrentHour(currentHour, true);
447 }
448
449 private void setCurrentHour(Integer currentHour, boolean notifyTimeChanged) {
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800450 // why was Integer used in the first place?
451 if (currentHour == null || currentHour == getCurrentHour()) {
452 return;
453 }
454 if (!is24HourView()) {
455 // convert [0,23] ordinal to wall clock display
Svetoslav Ganovbe17a7f2011-01-31 14:21:56 -0800456 if (currentHour >= HOURS_IN_HALF_DAY) {
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800457 mIsAm = false;
Svetoslav Ganovbe17a7f2011-01-31 14:21:56 -0800458 if (currentHour > HOURS_IN_HALF_DAY) {
459 currentHour = currentHour - HOURS_IN_HALF_DAY;
460 }
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800461 } else {
Svetoslav Ganovbe17a7f2011-01-31 14:21:56 -0800462 mIsAm = true;
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800463 if (currentHour == 0) {
464 currentHour = HOURS_IN_HALF_DAY;
465 }
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800466 }
467 updateAmPmControl();
468 }
469 mHourSpinner.setValue(currentHour);
Fabrice Di Meglio138de8e2013-08-23 19:28:55 -0700470 if (notifyTimeChanged) {
471 onTimeChanged();
472 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800473 }
474
475 /**
476 * Set whether in 24 hour or AM/PM mode.
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800477 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800478 * @param is24HourView True = 24 hour mode. False = AM/PM.
479 */
480 public void setIs24HourView(Boolean is24HourView) {
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800481 if (mIs24HourView == is24HourView) {
482 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800483 }
Fabrice Di Meglio64902bd2013-08-15 17:49:49 -0700484 // cache the current hour since spinner range changes and BEFORE changing mIs24HourView!!
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800485 int currentHour = getCurrentHour();
Fabrice Di Meglio64902bd2013-08-15 17:49:49 -0700486 // Order is important here.
487 mIs24HourView = is24HourView;
488 getHourFormatData();
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800489 updateHourControl();
Fabrice Di Meglio138de8e2013-08-23 19:28:55 -0700490 // set value after spinner range is updated - be aware that because mIs24HourView has
491 // changed then getCurrentHour() is not equal to the currentHour we cached before so
492 // explicitly ask for *not* propagating any onTimeChanged()
493 setCurrentHour(currentHour, false /* no onTimeChanged() */);
Hyejin Kim6ac46522013-03-20 11:32:44 +0900494 updateMinuteControl();
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800495 updateAmPmControl();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800496 }
497
498 /**
499 * @return true if this is in 24 hour view else false.
500 */
501 public boolean is24HourView() {
502 return mIs24HourView;
503 }
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800504
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800505 /**
506 * @return The current minute.
507 */
508 public Integer getCurrentMinute() {
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800509 return mMinuteSpinner.getValue();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800510 }
511
512 /**
513 * Set the current minute (0-59).
514 */
515 public void setCurrentMinute(Integer currentMinute) {
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800516 if (currentMinute == getCurrentMinute()) {
517 return;
518 }
519 mMinuteSpinner.setValue(currentMinute);
520 onTimeChanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800521 }
522
Fabrice Di Meglio64902bd2013-08-15 17:49:49 -0700523 /**
524 * The time separator is defined in the Unicode CLDR and cannot be supposed to be ":".
525 *
526 * See http://unicode.org/cldr/trac/browser/trunk/common/main
527 *
528 * We pass the correct "skeleton" depending on 12 or 24 hours view and then extract the
529 * separator as the character which is just after the hour marker in the returned pattern.
530 */
531 private void setDividerText() {
532 final Locale defaultLocale = Locale.getDefault();
533 final String skeleton = (mIs24HourView) ? "Hm" : "hm";
534 final String bestDateTimePattern = DateFormat.getBestDateTimePattern(defaultLocale,
535 skeleton);
536 final String separatorText;
537 int hourIndex = bestDateTimePattern.lastIndexOf('H');
538 if (hourIndex == -1) {
539 hourIndex = bestDateTimePattern.lastIndexOf('h');
540 }
541 if (hourIndex == -1) {
542 // Default case
543 separatorText = ":";
544 } else {
545 int minuteIndex = bestDateTimePattern.indexOf('m', hourIndex + 1);
546 if (minuteIndex == -1) {
547 separatorText = Character.toString(bestDateTimePattern.charAt(hourIndex + 1));
548 } else {
549 separatorText = bestDateTimePattern.substring(hourIndex + 1, minuteIndex);
550 }
551 }
552 mDivider.setText(separatorText);
553 }
554
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800555 @Override
556 public int getBaseline() {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800557 return mHourSpinner.getBaseline();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800558 }
559
Svetoslav Ganov8a2a8952011-01-27 17:40:13 -0800560 @Override
Svetoslav Ganov3fec3fe2011-09-01 14:48:37 -0700561 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
562 onPopulateAccessibilityEvent(event);
563 return true;
564 }
565
566 @Override
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700567 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
568 super.onPopulateAccessibilityEvent(event);
569
Svetoslav Ganov8a2a8952011-01-27 17:40:13 -0800570 int flags = DateUtils.FORMAT_SHOW_TIME;
571 if (mIs24HourView) {
572 flags |= DateUtils.FORMAT_24HOUR;
573 } else {
574 flags |= DateUtils.FORMAT_12HOUR;
575 }
576 mTempCalendar.set(Calendar.HOUR_OF_DAY, getCurrentHour());
577 mTempCalendar.set(Calendar.MINUTE, getCurrentMinute());
578 String selectedDateUtterance = DateUtils.formatDateTime(mContext,
579 mTempCalendar.getTimeInMillis(), flags);
580 event.getText().add(selectedDateUtterance);
Svetoslav Ganov8a2a8952011-01-27 17:40:13 -0800581 }
582
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800583 @Override
584 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
585 super.onInitializeAccessibilityEvent(event);
586 event.setClassName(TimePicker.class.getName());
587 }
588
589 @Override
590 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
591 super.onInitializeAccessibilityNodeInfo(info);
592 info.setClassName(TimePicker.class.getName());
593 }
594
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800595 private void updateHourControl() {
596 if (is24HourView()) {
Fabrice Di Meglio64902bd2013-08-15 17:49:49 -0700597 // 'k' means 1-24 hour
598 if (mHourFormat == 'k') {
599 mHourSpinner.setMinValue(1);
600 mHourSpinner.setMaxValue(24);
601 } else {
602 mHourSpinner.setMinValue(0);
603 mHourSpinner.setMaxValue(23);
604 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800605 } else {
Fabrice Di Meglio64902bd2013-08-15 17:49:49 -0700606 // 'K' means 0-11 hour
607 if (mHourFormat == 'K') {
608 mHourSpinner.setMinValue(0);
609 mHourSpinner.setMaxValue(11);
610 } else {
611 mHourSpinner.setMinValue(1);
612 mHourSpinner.setMaxValue(12);
613 }
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800614 }
Fabrice Di Meglio64902bd2013-08-15 17:49:49 -0700615 mHourSpinner.setFormatter(mHourWithTwoDigit ? NumberPicker.getTwoDigitFormatter() : null);
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800616 }
617
Hyejin Kim6ac46522013-03-20 11:32:44 +0900618 private void updateMinuteControl() {
619 if (is24HourView()) {
620 mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE);
621 } else {
622 mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
623 }
624 }
625
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800626 private void updateAmPmControl() {
627 if (is24HourView()) {
628 if (mAmPmSpinner != null) {
629 mAmPmSpinner.setVisibility(View.GONE);
630 } else {
631 mAmPmButton.setVisibility(View.GONE);
632 }
633 } else {
634 int index = mIsAm ? Calendar.AM : Calendar.PM;
635 if (mAmPmSpinner != null) {
636 mAmPmSpinner.setValue(index);
637 mAmPmSpinner.setVisibility(View.VISIBLE);
638 } else {
639 mAmPmButton.setText(mAmPmStrings[index]);
640 mAmPmButton.setVisibility(View.VISIBLE);
641 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800642 }
Svetoslav Ganov8a2a8952011-01-27 17:40:13 -0800643 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800644 }
645
646 private void onTimeChanged() {
Svetoslav Ganov8a2a8952011-01-27 17:40:13 -0800647 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800648 if (mOnTimeChangedListener != null) {
649 mOnTimeChangedListener.onTimeChanged(this, getCurrentHour(), getCurrentMinute());
650 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800651 }
Svetoslav Ganov3fec3fe2011-09-01 14:48:37 -0700652
653 private void setContentDescriptions() {
654 // Minute
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700655 trySetContentDescription(mMinuteSpinner, R.id.increment,
656 R.string.time_picker_increment_minute_button);
657 trySetContentDescription(mMinuteSpinner, R.id.decrement,
658 R.string.time_picker_decrement_minute_button);
Svetoslav Ganov3fec3fe2011-09-01 14:48:37 -0700659 // Hour
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700660 trySetContentDescription(mHourSpinner, R.id.increment,
661 R.string.time_picker_increment_hour_button);
662 trySetContentDescription(mHourSpinner, R.id.decrement,
663 R.string.time_picker_decrement_hour_button);
Svetoslav Ganov3fec3fe2011-09-01 14:48:37 -0700664 // AM/PM
Svetoslav Ganov11c91322011-09-14 18:35:44 -0700665 if (mAmPmSpinner != null) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700666 trySetContentDescription(mAmPmSpinner, R.id.increment,
667 R.string.time_picker_increment_set_pm_button);
668 trySetContentDescription(mAmPmSpinner, R.id.decrement,
669 R.string.time_picker_decrement_set_am_button);
670 }
671 }
672
673 private void trySetContentDescription(View root, int viewId, int contDescResId) {
674 View target = root.findViewById(viewId);
675 if (target != null) {
676 target.setContentDescription(mContext.getString(contDescResId));
Svetoslav Ganov11c91322011-09-14 18:35:44 -0700677 }
Svetoslav Ganov3fec3fe2011-09-01 14:48:37 -0700678 }
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -0700679
680 private void updateInputState() {
681 // Make sure that if the user changes the value and the IME is active
682 // for one of the inputs if this widget, the IME is closed. If the user
683 // changed the value via the IME and there is a next input the IME will
684 // be shown, otherwise the user chose another means of changing the
685 // value and having the IME up makes no sense.
686 InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
687 if (inputMethodManager != null) {
688 if (inputMethodManager.isActive(mHourSpinnerInput)) {
689 mHourSpinnerInput.clearFocus();
690 inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
691 } else if (inputMethodManager.isActive(mMinuteSpinnerInput)) {
692 mMinuteSpinnerInput.clearFocus();
693 inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
694 } else if (inputMethodManager.isActive(mAmPmSpinnerInput)) {
695 mAmPmSpinnerInput.clearFocus();
696 inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
697 }
698 }
699 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800700}