• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package android.widget;
18 
19 import android.annotation.IntDef;
20 import android.annotation.IntRange;
21 import android.annotation.NonNull;
22 import android.annotation.TestApi;
23 import android.annotation.Widget;
24 import android.content.Context;
25 import android.content.res.TypedArray;
26 import android.icu.util.Calendar;
27 import android.os.Parcel;
28 import android.os.Parcelable;
29 import android.util.AttributeSet;
30 import android.util.Log;
31 import android.util.MathUtils;
32 import android.view.View;
33 import android.view.ViewStructure;
34 import android.view.accessibility.AccessibilityEvent;
35 import android.view.autofill.AutofillManager;
36 import android.view.autofill.AutofillValue;
37 
38 import com.android.internal.R;
39 
40 import libcore.icu.LocaleData;
41 
42 import java.lang.annotation.Retention;
43 import java.lang.annotation.RetentionPolicy;
44 import java.util.Locale;
45 
46 /**
47  * A widget for selecting the time of day, in either 24-hour or AM/PM mode.
48  * <p>
49  * For a dialog using this view, see {@link android.app.TimePickerDialog}. See
50  * the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a>
51  * guide for more information.
52  *
53  * @attr ref android.R.styleable#TimePicker_timePickerMode
54  */
55 @Widget
56 public class TimePicker extends FrameLayout {
57     private static final String LOG_TAG = TimePicker.class.getSimpleName();
58 
59     /**
60      * Presentation mode for the Holo-style time picker that uses a set of
61      * {@link android.widget.NumberPicker}s.
62      *
63      * @see #getMode()
64      * @hide Visible for testing only.
65      */
66     @TestApi
67     public static final int MODE_SPINNER = 1;
68 
69     /**
70      * Presentation mode for the Material-style time picker that uses a clock
71      * face.
72      *
73      * @see #getMode()
74      * @hide Visible for testing only.
75      */
76     @TestApi
77     public static final int MODE_CLOCK = 2;
78 
79     /** @hide */
80     @IntDef(prefix = { "MODE_" }, value = {
81             MODE_SPINNER,
82             MODE_CLOCK
83     })
84     @Retention(RetentionPolicy.SOURCE)
85     public @interface TimePickerMode {}
86 
87     private final TimePickerDelegate mDelegate;
88 
89     @TimePickerMode
90     private final int mMode;
91 
92     /**
93      * The callback interface used to indicate the time has been adjusted.
94      */
95     public interface OnTimeChangedListener {
96 
97         /**
98          * @param view The view associated with this listener.
99          * @param hourOfDay The current hour.
100          * @param minute The current minute.
101          */
onTimeChanged(TimePicker view, int hourOfDay, int minute)102         void onTimeChanged(TimePicker view, int hourOfDay, int minute);
103     }
104 
TimePicker(Context context)105     public TimePicker(Context context) {
106         this(context, null);
107     }
108 
TimePicker(Context context, AttributeSet attrs)109     public TimePicker(Context context, AttributeSet attrs) {
110         this(context, attrs, R.attr.timePickerStyle);
111     }
112 
TimePicker(Context context, AttributeSet attrs, int defStyleAttr)113     public TimePicker(Context context, AttributeSet attrs, int defStyleAttr) {
114         this(context, attrs, defStyleAttr, 0);
115     }
116 
TimePicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)117     public TimePicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
118         super(context, attrs, defStyleAttr, defStyleRes);
119 
120         // DatePicker is important by default, unless app developer overrode attribute.
121         if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
122             setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
123         }
124 
125         final TypedArray a = context.obtainStyledAttributes(
126                 attrs, R.styleable.TimePicker, defStyleAttr, defStyleRes);
127         final boolean isDialogMode = a.getBoolean(R.styleable.TimePicker_dialogMode, false);
128         final int requestedMode = a.getInt(R.styleable.TimePicker_timePickerMode, MODE_SPINNER);
129         a.recycle();
130 
131         if (requestedMode == MODE_CLOCK && isDialogMode) {
132             // You want MODE_CLOCK? YOU CAN'T HANDLE MODE_CLOCK! Well, maybe
133             // you can depending on your screen size. Let's check...
134             mMode = context.getResources().getInteger(R.integer.time_picker_mode);
135         } else {
136             mMode = requestedMode;
137         }
138 
139         switch (mMode) {
140             case MODE_CLOCK:
141                 mDelegate = new TimePickerClockDelegate(
142                         this, context, attrs, defStyleAttr, defStyleRes);
143                 break;
144             case MODE_SPINNER:
145             default:
146                 mDelegate = new TimePickerSpinnerDelegate(
147                         this, context, attrs, defStyleAttr, defStyleRes);
148                 break;
149         }
150         mDelegate.setAutoFillChangeListener((v, h, m) -> {
151             final AutofillManager afm = context.getSystemService(AutofillManager.class);
152             if (afm != null) {
153                 afm.notifyValueChanged(this);
154             }
155         });
156     }
157 
158     /**
159      * @return the picker's presentation mode, one of {@link #MODE_CLOCK} or
160      *         {@link #MODE_SPINNER}
161      * @attr ref android.R.styleable#TimePicker_timePickerMode
162      * @hide Visible for testing only.
163      */
164     @TimePickerMode
165     @TestApi
getMode()166     public int getMode() {
167         return mMode;
168     }
169 
170     /**
171      * Sets the currently selected hour using 24-hour time.
172      *
173      * @param hour the hour to set, in the range (0-23)
174      * @see #getHour()
175      */
setHour(@ntRangefrom = 0, to = 23) int hour)176     public void setHour(@IntRange(from = 0, to = 23) int hour) {
177         mDelegate.setHour(MathUtils.constrain(hour, 0, 23));
178     }
179 
180     /**
181      * Returns the currently selected hour using 24-hour time.
182      *
183      * @return the currently selected hour, in the range (0-23)
184      * @see #setHour(int)
185      */
getHour()186     public int getHour() {
187         return mDelegate.getHour();
188     }
189 
190     /**
191      * Sets the currently selected minute.
192      *
193      * @param minute the minute to set, in the range (0-59)
194      * @see #getMinute()
195      */
setMinute(@ntRangefrom = 0, to = 59) int minute)196     public void setMinute(@IntRange(from = 0, to = 59) int minute) {
197         mDelegate.setMinute(MathUtils.constrain(minute, 0, 59));
198     }
199 
200     /**
201      * Returns the currently selected minute.
202      *
203      * @return the currently selected minute, in the range (0-59)
204      * @see #setMinute(int)
205      */
getMinute()206     public int getMinute() {
207         return mDelegate.getMinute();
208     }
209 
210     /**
211      * Sets the currently selected hour using 24-hour time.
212      *
213      * @param currentHour the hour to set, in the range (0-23)
214      * @deprecated Use {@link #setHour(int)}
215      */
216     @Deprecated
setCurrentHour(@onNull Integer currentHour)217     public void setCurrentHour(@NonNull Integer currentHour) {
218         setHour(currentHour);
219     }
220 
221     /**
222      * @return the currently selected hour, in the range (0-23)
223      * @deprecated Use {@link #getHour()}
224      */
225     @NonNull
226     @Deprecated
getCurrentHour()227     public Integer getCurrentHour() {
228         return getHour();
229     }
230 
231     /**
232      * Sets the currently selected minute.
233      *
234      * @param currentMinute the minute to set, in the range (0-59)
235      * @deprecated Use {@link #setMinute(int)}
236      */
237     @Deprecated
setCurrentMinute(@onNull Integer currentMinute)238     public void setCurrentMinute(@NonNull Integer currentMinute) {
239         setMinute(currentMinute);
240     }
241 
242     /**
243      * @return the currently selected minute, in the range (0-59)
244      * @deprecated Use {@link #getMinute()}
245      */
246     @NonNull
247     @Deprecated
getCurrentMinute()248     public Integer getCurrentMinute() {
249         return getMinute();
250     }
251 
252     /**
253      * Sets whether this widget displays time in 24-hour mode or 12-hour mode
254      * with an AM/PM picker.
255      *
256      * @param is24HourView {@code true} to display in 24-hour mode,
257      *                     {@code false} for 12-hour mode with AM/PM
258      * @see #is24HourView()
259      */
setIs24HourView(@onNull Boolean is24HourView)260     public void setIs24HourView(@NonNull Boolean is24HourView) {
261         if (is24HourView == null) {
262             return;
263         }
264 
265         mDelegate.setIs24Hour(is24HourView);
266     }
267 
268     /**
269      * @return {@code true} if this widget displays time in 24-hour mode,
270      *         {@code false} otherwise}
271      * @see #setIs24HourView(Boolean)
272      */
is24HourView()273     public boolean is24HourView() {
274         return mDelegate.is24Hour();
275     }
276 
277     /**
278      * Set the callback that indicates the time has been adjusted by the user.
279      *
280      * @param onTimeChangedListener the callback, should not be null.
281      */
setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener)282     public void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener) {
283         mDelegate.setOnTimeChangedListener(onTimeChangedListener);
284     }
285 
286     @Override
setEnabled(boolean enabled)287     public void setEnabled(boolean enabled) {
288         super.setEnabled(enabled);
289         mDelegate.setEnabled(enabled);
290     }
291 
292     @Override
isEnabled()293     public boolean isEnabled() {
294         return mDelegate.isEnabled();
295     }
296 
297     @Override
getBaseline()298     public int getBaseline() {
299         return mDelegate.getBaseline();
300     }
301 
302     /**
303      * Validates whether current input by the user is a valid time based on the locale. TimePicker
304      * will show an error message to the user if the time is not valid.
305      *
306      * @return {@code true} if the input is valid, {@code false} otherwise
307      */
validateInput()308     public boolean validateInput() {
309         return mDelegate.validateInput();
310     }
311 
312     @Override
onSaveInstanceState()313     protected Parcelable onSaveInstanceState() {
314         Parcelable superState = super.onSaveInstanceState();
315         return mDelegate.onSaveInstanceState(superState);
316     }
317 
318     @Override
onRestoreInstanceState(Parcelable state)319     protected void onRestoreInstanceState(Parcelable state) {
320         BaseSavedState ss = (BaseSavedState) state;
321         super.onRestoreInstanceState(ss.getSuperState());
322         mDelegate.onRestoreInstanceState(ss);
323     }
324 
325     @Override
getAccessibilityClassName()326     public CharSequence getAccessibilityClassName() {
327         return TimePicker.class.getName();
328     }
329 
330     /** @hide */
331     @Override
dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event)332     public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
333         return mDelegate.dispatchPopulateAccessibilityEvent(event);
334     }
335 
336     /** @hide */
337     @TestApi
getHourView()338     public View getHourView() {
339         return mDelegate.getHourView();
340     }
341 
342     /** @hide */
343     @TestApi
getMinuteView()344     public View getMinuteView() {
345         return mDelegate.getMinuteView();
346     }
347 
348     /** @hide */
349     @TestApi
getAmView()350     public View getAmView() {
351         return mDelegate.getAmView();
352     }
353 
354     /** @hide */
355     @TestApi
getPmView()356     public View getPmView() {
357         return mDelegate.getPmView();
358     }
359 
360     /**
361      * A delegate interface that defined the public API of the TimePicker. Allows different
362      * TimePicker implementations. This would need to be implemented by the TimePicker delegates
363      * for the real behavior.
364      */
365     interface TimePickerDelegate {
setHour(@ntRangefrom = 0, to = 23) int hour)366         void setHour(@IntRange(from = 0, to = 23) int hour);
getHour()367         int getHour();
368 
setMinute(@ntRangefrom = 0, to = 59) int minute)369         void setMinute(@IntRange(from = 0, to = 59) int minute);
getMinute()370         int getMinute();
371 
setDate(@ntRangefrom = 0, to = 23) int hour, @IntRange(from = 0, to = 59) int minute)372         void setDate(@IntRange(from = 0, to = 23) int hour,
373                 @IntRange(from = 0, to = 59) int minute);
374 
autofill(AutofillValue value)375         void autofill(AutofillValue value);
getAutofillValue()376         AutofillValue getAutofillValue();
377 
setIs24Hour(boolean is24Hour)378         void setIs24Hour(boolean is24Hour);
is24Hour()379         boolean is24Hour();
380 
validateInput()381         boolean validateInput();
382 
setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener)383         void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener);
setAutoFillChangeListener(OnTimeChangedListener autoFillChangeListener)384         void setAutoFillChangeListener(OnTimeChangedListener autoFillChangeListener);
385 
setEnabled(boolean enabled)386         void setEnabled(boolean enabled);
isEnabled()387         boolean isEnabled();
388 
getBaseline()389         int getBaseline();
390 
onSaveInstanceState(Parcelable superState)391         Parcelable onSaveInstanceState(Parcelable superState);
onRestoreInstanceState(Parcelable state)392         void onRestoreInstanceState(Parcelable state);
393 
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)394         boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event);
onPopulateAccessibilityEvent(AccessibilityEvent event)395         void onPopulateAccessibilityEvent(AccessibilityEvent event);
396 
397         /** @hide */
getHourView()398         @TestApi View getHourView();
399 
400         /** @hide */
getMinuteView()401         @TestApi View getMinuteView();
402 
403         /** @hide */
getAmView()404         @TestApi View getAmView();
405 
406         /** @hide */
getPmView()407         @TestApi View getPmView();
408     }
409 
getAmPmStrings(Context context)410     static String[] getAmPmStrings(Context context) {
411         final Locale locale = context.getResources().getConfiguration().locale;
412         final LocaleData d = LocaleData.get(locale);
413 
414         final String[] result = new String[2];
415         result[0] = d.amPm[0].length() > 4 ? d.narrowAm : d.amPm[0];
416         result[1] = d.amPm[1].length() > 4 ? d.narrowPm : d.amPm[1];
417         return result;
418     }
419 
420     /**
421      * An abstract class which can be used as a start for TimePicker implementations
422      */
423     abstract static class AbstractTimePickerDelegate implements TimePickerDelegate {
424         protected final TimePicker mDelegator;
425         protected final Context mContext;
426         protected final Locale mLocale;
427 
428         protected OnTimeChangedListener mOnTimeChangedListener;
429         protected OnTimeChangedListener mAutoFillChangeListener;
430 
431         // The value that was passed to autofill() - it must be stored because it getAutofillValue()
432         // must return the exact same value that was autofilled, otherwise the widget will not be
433         // properly highlighted after autofill().
434         private long mAutofilledValue;
435 
AbstractTimePickerDelegate(@onNull TimePicker delegator, @NonNull Context context)436         public AbstractTimePickerDelegate(@NonNull TimePicker delegator, @NonNull Context context) {
437             mDelegator = delegator;
438             mContext = context;
439             mLocale = context.getResources().getConfiguration().locale;
440         }
441 
442         @Override
setOnTimeChangedListener(OnTimeChangedListener callback)443         public void setOnTimeChangedListener(OnTimeChangedListener callback) {
444             mOnTimeChangedListener = callback;
445         }
446 
447         @Override
setAutoFillChangeListener(OnTimeChangedListener callback)448         public void setAutoFillChangeListener(OnTimeChangedListener callback) {
449             mAutoFillChangeListener = callback;
450         }
451 
452         @Override
autofill(AutofillValue value)453         public final void autofill(AutofillValue value) {
454             if (value == null || !value.isDate()) {
455                 Log.w(LOG_TAG, value + " could not be autofilled into " + this);
456                 return;
457             }
458 
459             final long time = value.getDateValue();
460 
461             final Calendar cal = Calendar.getInstance(mLocale);
462             cal.setTimeInMillis(time);
463             setDate(cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE));
464 
465             // Must set mAutofilledValue *after* calling subclass method to make sure the value
466             // returned by getAutofillValue() matches it.
467             mAutofilledValue = time;
468         }
469 
470         @Override
getAutofillValue()471         public final AutofillValue getAutofillValue() {
472             if (mAutofilledValue != 0) {
473                 return AutofillValue.forDate(mAutofilledValue);
474             }
475 
476             final Calendar cal = Calendar.getInstance(mLocale);
477             cal.set(Calendar.HOUR_OF_DAY, getHour());
478             cal.set(Calendar.MINUTE, getMinute());
479             return AutofillValue.forDate(cal.getTimeInMillis());
480         }
481 
482         /**
483          * This method must be called every time the value of the hour and/or minute is changed by
484          * a subclass method.
485          */
resetAutofilledValue()486         protected void resetAutofilledValue() {
487             mAutofilledValue = 0;
488         }
489 
490         protected static class SavedState extends View.BaseSavedState {
491             private final int mHour;
492             private final int mMinute;
493             private final boolean mIs24HourMode;
494             private final int mCurrentItemShowing;
495 
SavedState(Parcelable superState, int hour, int minute, boolean is24HourMode)496             public SavedState(Parcelable superState, int hour, int minute, boolean is24HourMode) {
497                 this(superState, hour, minute, is24HourMode, 0);
498             }
499 
SavedState(Parcelable superState, int hour, int minute, boolean is24HourMode, int currentItemShowing)500             public SavedState(Parcelable superState, int hour, int minute, boolean is24HourMode,
501                     int currentItemShowing) {
502                 super(superState);
503                 mHour = hour;
504                 mMinute = minute;
505                 mIs24HourMode = is24HourMode;
506                 mCurrentItemShowing = currentItemShowing;
507             }
508 
SavedState(Parcel in)509             private SavedState(Parcel in) {
510                 super(in);
511                 mHour = in.readInt();
512                 mMinute = in.readInt();
513                 mIs24HourMode = (in.readInt() == 1);
514                 mCurrentItemShowing = in.readInt();
515             }
516 
getHour()517             public int getHour() {
518                 return mHour;
519             }
520 
getMinute()521             public int getMinute() {
522                 return mMinute;
523             }
524 
is24HourMode()525             public boolean is24HourMode() {
526                 return mIs24HourMode;
527             }
528 
getCurrentItemShowing()529             public int getCurrentItemShowing() {
530                 return mCurrentItemShowing;
531             }
532 
533             @Override
writeToParcel(Parcel dest, int flags)534             public void writeToParcel(Parcel dest, int flags) {
535                 super.writeToParcel(dest, flags);
536                 dest.writeInt(mHour);
537                 dest.writeInt(mMinute);
538                 dest.writeInt(mIs24HourMode ? 1 : 0);
539                 dest.writeInt(mCurrentItemShowing);
540             }
541 
542             @SuppressWarnings({"unused", "hiding"})
543             public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
544                 public SavedState createFromParcel(Parcel in) {
545                     return new SavedState(in);
546                 }
547 
548                 public SavedState[] newArray(int size) {
549                     return new SavedState[size];
550                 }
551             };
552         }
553     }
554 
555     @Override
dispatchProvideAutofillStructure(ViewStructure structure, int flags)556     public void dispatchProvideAutofillStructure(ViewStructure structure, int flags) {
557         // This view is self-sufficient for autofill, so it needs to call
558         // onProvideAutoFillStructure() to fill itself, but it does not need to call
559         // dispatchProvideAutoFillStructure() to fill its children.
560         structure.setAutofillId(getAutofillId());
561         onProvideAutofillStructure(structure, flags);
562     }
563 
564     @Override
autofill(AutofillValue value)565     public void autofill(AutofillValue value) {
566         if (!isEnabled()) return;
567 
568         mDelegate.autofill(value);
569     }
570 
571     @Override
getAutofillType()572     public @AutofillType int getAutofillType() {
573         return isEnabled() ? AUTOFILL_TYPE_DATE : AUTOFILL_TYPE_NONE;
574     }
575 
576     @Override
getAutofillValue()577     public AutofillValue getAutofillValue() {
578         return isEnabled() ? mDelegate.getAutofillValue() : null;
579     }
580 }
581