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.Nullable;
21 import android.annotation.TestApi;
22 import android.annotation.Widget;
23 import android.compat.annotation.UnsupportedAppUsage;
24 import android.content.Context;
25 import android.content.res.Configuration;
26 import android.content.res.TypedArray;
27 import android.icu.util.Calendar;
28 import android.icu.util.TimeZone;
29 import android.os.Parcel;
30 import android.os.Parcelable;
31 import android.text.format.DateUtils;
32 import android.util.AttributeSet;
33 import android.util.Log;
34 import android.util.SparseArray;
35 import android.view.View;
36 import android.view.ViewStructure;
37 import android.view.accessibility.AccessibilityEvent;
38 import android.view.autofill.AutofillManager;
39 import android.view.autofill.AutofillValue;
40 import android.view.inspector.InspectableProperty;
41 
42 import com.android.internal.R;
43 
44 import java.lang.annotation.Retention;
45 import java.lang.annotation.RetentionPolicy;
46 import java.util.Locale;
47 
48 /**
49  * Provides a widget for selecting a date.
50  * <p>
51  * When the {@link android.R.styleable#DatePicker_datePickerMode} attribute is
52  * set to {@code spinner}, the date can be selected using year, month, and day
53  * spinners or a {@link CalendarView}. The set of spinners and the calendar
54  * view are automatically synchronized. The client can customize whether only
55  * the spinners, or only the calendar view, or both to be displayed.
56  * </p>
57  * <p>
58  * When the {@link android.R.styleable#DatePicker_datePickerMode} attribute is
59  * set to {@code calendar}, the month and day can be selected using a
60  * calendar-style view while the year can be selected separately using a list.
61  * </p>
62  * <p>
63  * See the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a>
64  * guide.
65  * </p>
66  * <p>
67  * For a dialog using this view, see {@link android.app.DatePickerDialog}.
68  * </p>
69  *
70  * @attr ref android.R.styleable#DatePicker_startYear
71  * @attr ref android.R.styleable#DatePicker_endYear
72  * @attr ref android.R.styleable#DatePicker_maxDate
73  * @attr ref android.R.styleable#DatePicker_minDate
74  * @attr ref android.R.styleable#DatePicker_spinnersShown
75  * @attr ref android.R.styleable#DatePicker_calendarViewShown
76  * @attr ref android.R.styleable#DatePicker_dayOfWeekBackground
77  * @attr ref android.R.styleable#DatePicker_dayOfWeekTextAppearance
78  * @attr ref android.R.styleable#DatePicker_headerBackground
79  * @attr ref android.R.styleable#DatePicker_headerMonthTextAppearance
80  * @attr ref android.R.styleable#DatePicker_headerDayOfMonthTextAppearance
81  * @attr ref android.R.styleable#DatePicker_headerYearTextAppearance
82  * @attr ref android.R.styleable#DatePicker_yearListItemTextAppearance
83  * @attr ref android.R.styleable#DatePicker_yearListSelectorColor
84  * @attr ref android.R.styleable#DatePicker_calendarTextColor
85  * @attr ref android.R.styleable#DatePicker_datePickerMode
86  */
87 @Widget
88 public class DatePicker extends FrameLayout {
89     private static final String LOG_TAG = DatePicker.class.getSimpleName();
90 
91     /**
92      * Presentation mode for the Holo-style date picker that uses a set of
93      * {@link android.widget.NumberPicker}s.
94      *
95      * @see #getMode()
96      * @hide Visible for testing only.
97      */
98     @TestApi
99     public static final int MODE_SPINNER = 1;
100 
101     /**
102      * Presentation mode for the Material-style date picker that uses a
103      * calendar.
104      *
105      * @see #getMode()
106      * @hide Visible for testing only.
107      */
108     @TestApi
109     public static final int MODE_CALENDAR = 2;
110 
111     /** @hide */
112     @IntDef(prefix = { "MODE_" }, value = {
113             MODE_SPINNER,
114             MODE_CALENDAR
115     })
116     @Retention(RetentionPolicy.SOURCE)
117     public @interface DatePickerMode {}
118 
119     @UnsupportedAppUsage
120     private final DatePickerDelegate mDelegate;
121 
122     @DatePickerMode
123     private final int mMode;
124 
125     /**
126      * The callback used to indicate the user changed the date.
127      */
128     public interface OnDateChangedListener {
129 
130         /**
131          * Called upon a date change.
132          *
133          * @param view The view associated with this listener.
134          * @param year The year that was set.
135          * @param monthOfYear The month that was set (0-11) for compatibility
136          *            with {@link java.util.Calendar}.
137          * @param dayOfMonth The day of the month that was set.
138          */
onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth)139         void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth);
140     }
141 
DatePicker(Context context)142     public DatePicker(Context context) {
143         this(context, null);
144     }
145 
DatePicker(Context context, AttributeSet attrs)146     public DatePicker(Context context, AttributeSet attrs) {
147         this(context, attrs, R.attr.datePickerStyle);
148     }
149 
DatePicker(Context context, AttributeSet attrs, int defStyleAttr)150     public DatePicker(Context context, AttributeSet attrs, int defStyleAttr) {
151         this(context, attrs, defStyleAttr, 0);
152     }
153 
DatePicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)154     public DatePicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
155         super(context, attrs, defStyleAttr, defStyleRes);
156 
157         // DatePicker is important by default, unless app developer overrode attribute.
158         if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
159             setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
160         }
161 
162         final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DatePicker,
163                 defStyleAttr, defStyleRes);
164         saveAttributeDataForStyleable(context, R.styleable.DatePicker,
165                 attrs, a, defStyleAttr, defStyleRes);
166         final boolean isDialogMode = a.getBoolean(R.styleable.DatePicker_dialogMode, false);
167         final int requestedMode = a.getInt(R.styleable.DatePicker_datePickerMode, MODE_SPINNER);
168         final int firstDayOfWeek = a.getInt(R.styleable.DatePicker_firstDayOfWeek, 0);
169         a.recycle();
170 
171         if (requestedMode == MODE_CALENDAR && isDialogMode) {
172             // You want MODE_CALENDAR? YOU CAN'T HANDLE MODE_CALENDAR! Well,
173             // maybe you can depending on your screen size. Let's check...
174             mMode = context.getResources().getInteger(R.integer.date_picker_mode);
175         } else {
176             mMode = requestedMode;
177         }
178 
179         switch (mMode) {
180             case MODE_CALENDAR:
181                 mDelegate = createCalendarUIDelegate(context, attrs, defStyleAttr, defStyleRes);
182                 break;
183             case MODE_SPINNER:
184             default:
185                 mDelegate = createSpinnerUIDelegate(context, attrs, defStyleAttr, defStyleRes);
186                 break;
187         }
188 
189         if (firstDayOfWeek != 0) {
190             setFirstDayOfWeek(firstDayOfWeek);
191         }
192 
193         mDelegate.setAutoFillChangeListener((v, y, m, d) -> {
194             final AutofillManager afm = context.getSystemService(AutofillManager.class);
195             if (afm != null) {
196                 afm.notifyValueChanged(this);
197             }
198         });
199     }
200 
createSpinnerUIDelegate(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)201     private DatePickerDelegate createSpinnerUIDelegate(Context context, AttributeSet attrs,
202             int defStyleAttr, int defStyleRes) {
203         return new DatePickerSpinnerDelegate(this, context, attrs, defStyleAttr, defStyleRes);
204     }
205 
createCalendarUIDelegate(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)206     private DatePickerDelegate createCalendarUIDelegate(Context context, AttributeSet attrs,
207             int defStyleAttr, int defStyleRes) {
208         return new DatePickerCalendarDelegate(this, context, attrs, defStyleAttr,
209                 defStyleRes);
210     }
211 
212     /**
213      * @return the picker's presentation mode, one of {@link #MODE_CALENDAR} or
214      *         {@link #MODE_SPINNER}
215      * @attr ref android.R.styleable#DatePicker_datePickerMode
216      * @hide Visible for testing only.
217      */
218     @InspectableProperty(name = "datePickerMode", enumMapping = {
219             @InspectableProperty.EnumEntry(value = MODE_SPINNER, name = "spinner"),
220             @InspectableProperty.EnumEntry(value = MODE_CALENDAR, name = "calendar")
221     })
222     @DatePickerMode
223     @TestApi
getMode()224     public int getMode() {
225         return mMode;
226     }
227 
228     /**
229      * Initialize the state. If the provided values designate an inconsistent
230      * date the values are normalized before updating the spinners.
231      *
232      * @param year The initial year.
233      * @param monthOfYear The initial month <strong>starting from zero</strong>.
234      * @param dayOfMonth The initial day of the month.
235      * @param onDateChangedListener How user is notified date is changed by
236      *            user, can be null.
237      */
init(int year, int monthOfYear, int dayOfMonth, OnDateChangedListener onDateChangedListener)238     public void init(int year, int monthOfYear, int dayOfMonth,
239                      OnDateChangedListener onDateChangedListener) {
240         mDelegate.init(year, monthOfYear, dayOfMonth, onDateChangedListener);
241     }
242 
243     /**
244      * Set the callback that indicates the date has been adjusted by the user.
245      *
246      * @param onDateChangedListener How user is notified date is changed by
247      *            user, can be null.
248      */
setOnDateChangedListener(OnDateChangedListener onDateChangedListener)249     public void setOnDateChangedListener(OnDateChangedListener onDateChangedListener) {
250         mDelegate.setOnDateChangedListener(onDateChangedListener);
251     }
252 
253     /**
254      * Update the current date.
255      *
256      * @param year The year.
257      * @param month The month which is <strong>starting from zero</strong>.
258      * @param dayOfMonth The day of the month.
259      */
updateDate(int year, int month, int dayOfMonth)260     public void updateDate(int year, int month, int dayOfMonth) {
261         mDelegate.updateDate(year, month, dayOfMonth);
262     }
263 
264     /**
265      * @return The selected year.
266      */
267     @InspectableProperty(hasAttributeId = false)
getYear()268     public int getYear() {
269         return mDelegate.getYear();
270     }
271 
272     /**
273      * @return The selected month.
274      */
275     @InspectableProperty(hasAttributeId = false)
getMonth()276     public int getMonth() {
277         return mDelegate.getMonth();
278     }
279 
280     /**
281      * @return The selected day of month.
282      */
283     @InspectableProperty(hasAttributeId = false)
getDayOfMonth()284     public int getDayOfMonth() {
285         return mDelegate.getDayOfMonth();
286     }
287 
288     /**
289      * Gets the minimal date supported by this {@link DatePicker} in
290      * milliseconds since January 1, 1970 00:00:00 in
291      * {@link TimeZone#getDefault()} time zone.
292      * <p>
293      * Note: The default minimal date is 01/01/1900.
294      * <p>
295      *
296      * @return The minimal supported date.
297      */
298     @InspectableProperty
getMinDate()299     public long getMinDate() {
300         return mDelegate.getMinDate().getTimeInMillis();
301     }
302 
303     /**
304      * Sets the minimal date supported by this {@link NumberPicker} in
305      * milliseconds since January 1, 1970 00:00:00 in
306      * {@link TimeZone#getDefault()} time zone.
307      *
308      * @param minDate The minimal supported date.
309      */
setMinDate(long minDate)310     public void setMinDate(long minDate) {
311         mDelegate.setMinDate(minDate);
312     }
313 
314     /**
315      * Gets the maximal date supported by this {@link DatePicker} in
316      * milliseconds since January 1, 1970 00:00:00 in
317      * {@link TimeZone#getDefault()} time zone.
318      * <p>
319      * Note: The default maximal date is 12/31/2100.
320      * <p>
321      *
322      * @return The maximal supported date.
323      */
324     @InspectableProperty
getMaxDate()325     public long getMaxDate() {
326         return mDelegate.getMaxDate().getTimeInMillis();
327     }
328 
329     /**
330      * Sets the maximal date supported by this {@link DatePicker} in
331      * milliseconds since January 1, 1970 00:00:00 in
332      * {@link TimeZone#getDefault()} time zone.
333      *
334      * @param maxDate The maximal supported date.
335      */
setMaxDate(long maxDate)336     public void setMaxDate(long maxDate) {
337         mDelegate.setMaxDate(maxDate);
338     }
339 
340     /**
341      * Sets the callback that indicates the current date is valid.
342      *
343      * @param callback the callback, may be null
344      * @hide
345      */
346     @UnsupportedAppUsage
setValidationCallback(@ullable ValidationCallback callback)347     public void setValidationCallback(@Nullable ValidationCallback callback) {
348         mDelegate.setValidationCallback(callback);
349     }
350 
351     @Override
setEnabled(boolean enabled)352     public void setEnabled(boolean enabled) {
353         if (mDelegate.isEnabled() == enabled) {
354             return;
355         }
356         super.setEnabled(enabled);
357         mDelegate.setEnabled(enabled);
358     }
359 
360     @Override
isEnabled()361     public boolean isEnabled() {
362         return mDelegate.isEnabled();
363     }
364 
365     /** @hide */
366     @Override
dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event)367     public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
368         return mDelegate.dispatchPopulateAccessibilityEvent(event);
369     }
370 
371     /** @hide */
372     @Override
onPopulateAccessibilityEventInternal(AccessibilityEvent event)373     public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
374         super.onPopulateAccessibilityEventInternal(event);
375         mDelegate.onPopulateAccessibilityEvent(event);
376     }
377 
378     @Override
getAccessibilityClassName()379     public CharSequence getAccessibilityClassName() {
380         return DatePicker.class.getName();
381     }
382 
383     @Override
onConfigurationChanged(Configuration newConfig)384     protected void onConfigurationChanged(Configuration newConfig) {
385         super.onConfigurationChanged(newConfig);
386         mDelegate.onConfigurationChanged(newConfig);
387     }
388 
389     /**
390      * Sets the first day of week.
391      *
392      * @param firstDayOfWeek The first day of the week conforming to the
393      *            {@link CalendarView} APIs.
394      * @see Calendar#SUNDAY
395      * @see Calendar#MONDAY
396      * @see Calendar#TUESDAY
397      * @see Calendar#WEDNESDAY
398      * @see Calendar#THURSDAY
399      * @see Calendar#FRIDAY
400      * @see Calendar#SATURDAY
401      *
402      * @attr ref android.R.styleable#DatePicker_firstDayOfWeek
403      */
setFirstDayOfWeek(int firstDayOfWeek)404     public void setFirstDayOfWeek(int firstDayOfWeek) {
405         if (firstDayOfWeek < Calendar.SUNDAY || firstDayOfWeek > Calendar.SATURDAY) {
406             throw new IllegalArgumentException("firstDayOfWeek must be between 1 and 7");
407         }
408         mDelegate.setFirstDayOfWeek(firstDayOfWeek);
409     }
410 
411     /**
412      * Gets the first day of week.
413      *
414      * @return The first day of the week conforming to the {@link CalendarView}
415      *         APIs.
416      * @see Calendar#SUNDAY
417      * @see Calendar#MONDAY
418      * @see Calendar#TUESDAY
419      * @see Calendar#WEDNESDAY
420      * @see Calendar#THURSDAY
421      * @see Calendar#FRIDAY
422      * @see Calendar#SATURDAY
423      *
424      * @attr ref android.R.styleable#DatePicker_firstDayOfWeek
425      */
426     @InspectableProperty
getFirstDayOfWeek()427     public int getFirstDayOfWeek() {
428         return mDelegate.getFirstDayOfWeek();
429     }
430 
431     /**
432      * Returns whether the {@link CalendarView} is shown.
433      * <p>
434      * <strong>Note:</strong> This method returns {@code false} when the
435      * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set
436      * to {@code calendar}.
437      *
438      * @return {@code true} if the calendar view is shown
439      * @see #getCalendarView()
440      * @deprecated Not supported by Material-style {@code calendar} mode
441      */
442     @InspectableProperty
443     @Deprecated
getCalendarViewShown()444     public boolean getCalendarViewShown() {
445         return mDelegate.getCalendarViewShown();
446     }
447 
448     /**
449      * Returns the {@link CalendarView} used by this picker.
450      * <p>
451      * <strong>Note:</strong> This method throws an
452      * {@link UnsupportedOperationException} when the
453      * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set
454      * to {@code calendar}.
455      *
456      * @return the calendar view
457      * @see #getCalendarViewShown()
458      * @deprecated Not supported by Material-style {@code calendar} mode
459      * @throws UnsupportedOperationException if called when the picker is
460      *         displayed in {@code calendar} mode
461      */
462     @Deprecated
getCalendarView()463     public CalendarView getCalendarView() {
464         return mDelegate.getCalendarView();
465     }
466 
467     /**
468      * Sets whether the {@link CalendarView} is shown.
469      * <p>
470      * <strong>Note:</strong> Calling this method has no effect when the
471      * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set
472      * to {@code calendar}.
473      *
474      * @param shown {@code true} to show the calendar view, {@code false} to
475      *              hide it
476      * @deprecated Not supported by Material-style {@code calendar} mode
477      */
478     @Deprecated
setCalendarViewShown(boolean shown)479     public void setCalendarViewShown(boolean shown) {
480         mDelegate.setCalendarViewShown(shown);
481     }
482 
483     /**
484      * Returns whether the spinners are shown.
485      * <p>
486      * <strong>Note:</strong> his method returns {@code false} when the
487      * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set
488      * to {@code calendar}.
489      *
490      * @return {@code true} if the spinners are shown
491      * @deprecated Not supported by Material-style {@code calendar} mode
492      */
493     @InspectableProperty
494     @Deprecated
getSpinnersShown()495     public boolean getSpinnersShown() {
496         return mDelegate.getSpinnersShown();
497     }
498 
499     /**
500      * Sets whether the spinners are shown.
501      * <p>
502      * Calling this method has no effect when the
503      * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set
504      * to {@code calendar}.
505      *
506      * @param shown {@code true} to show the spinners, {@code false} to hide
507      *              them
508      * @deprecated Not supported by Material-style {@code calendar} mode
509      */
510     @Deprecated
setSpinnersShown(boolean shown)511     public void setSpinnersShown(boolean shown) {
512         mDelegate.setSpinnersShown(shown);
513     }
514 
515     @Override
dispatchRestoreInstanceState(SparseArray<Parcelable> container)516     protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
517         dispatchThawSelfOnly(container);
518     }
519 
520     @Override
onSaveInstanceState()521     protected Parcelable onSaveInstanceState() {
522         Parcelable superState = super.onSaveInstanceState();
523         return mDelegate.onSaveInstanceState(superState);
524     }
525 
526     @Override
onRestoreInstanceState(Parcelable state)527     protected void onRestoreInstanceState(Parcelable state) {
528         BaseSavedState ss = (BaseSavedState) state;
529         super.onRestoreInstanceState(ss.getSuperState());
530         mDelegate.onRestoreInstanceState(ss);
531     }
532 
533     /**
534      * A delegate interface that defined the public API of the DatePicker. Allows different
535      * DatePicker implementations. This would need to be implemented by the DatePicker delegates
536      * for the real behavior.
537      *
538      * @hide
539      */
540     interface DatePickerDelegate {
init(int year, int monthOfYear, int dayOfMonth, OnDateChangedListener onDateChangedListener)541         void init(int year, int monthOfYear, int dayOfMonth,
542                   OnDateChangedListener onDateChangedListener);
543 
setOnDateChangedListener(OnDateChangedListener onDateChangedListener)544         void setOnDateChangedListener(OnDateChangedListener onDateChangedListener);
setAutoFillChangeListener(OnDateChangedListener onDateChangedListener)545         void setAutoFillChangeListener(OnDateChangedListener onDateChangedListener);
546 
updateDate(int year, int month, int dayOfMonth)547         void updateDate(int year, int month, int dayOfMonth);
548 
getYear()549         int getYear();
getMonth()550         int getMonth();
getDayOfMonth()551         int getDayOfMonth();
552 
autofill(AutofillValue value)553         void autofill(AutofillValue value);
getAutofillValue()554         AutofillValue getAutofillValue();
555 
setFirstDayOfWeek(int firstDayOfWeek)556         void setFirstDayOfWeek(int firstDayOfWeek);
getFirstDayOfWeek()557         int getFirstDayOfWeek();
558 
setMinDate(long minDate)559         void setMinDate(long minDate);
getMinDate()560         Calendar getMinDate();
561 
setMaxDate(long maxDate)562         void setMaxDate(long maxDate);
getMaxDate()563         Calendar getMaxDate();
564 
setEnabled(boolean enabled)565         void setEnabled(boolean enabled);
isEnabled()566         boolean isEnabled();
567 
getCalendarView()568         CalendarView getCalendarView();
569 
setCalendarViewShown(boolean shown)570         void setCalendarViewShown(boolean shown);
getCalendarViewShown()571         boolean getCalendarViewShown();
572 
setSpinnersShown(boolean shown)573         void setSpinnersShown(boolean shown);
getSpinnersShown()574         boolean getSpinnersShown();
575 
setValidationCallback(ValidationCallback callback)576         void setValidationCallback(ValidationCallback callback);
577 
onConfigurationChanged(Configuration newConfig)578         void onConfigurationChanged(Configuration newConfig);
579 
onSaveInstanceState(Parcelable superState)580         Parcelable onSaveInstanceState(Parcelable superState);
onRestoreInstanceState(Parcelable state)581         void onRestoreInstanceState(Parcelable state);
582 
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)583         boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event);
onPopulateAccessibilityEvent(AccessibilityEvent event)584         void onPopulateAccessibilityEvent(AccessibilityEvent event);
585     }
586 
587     /**
588      * An abstract class which can be used as a start for DatePicker implementations
589      */
590     abstract static class AbstractDatePickerDelegate implements DatePickerDelegate {
591         // The delegator
592         protected DatePicker mDelegator;
593 
594         // The context
595         protected Context mContext;
596 
597         // NOTE: when subclasses change this variable, they must call resetAutofilledValue().
598         protected Calendar mCurrentDate;
599 
600         // The current locale
601         protected Locale mCurrentLocale;
602 
603         // Callbacks
604         protected OnDateChangedListener mOnDateChangedListener;
605         protected OnDateChangedListener mAutoFillChangeListener;
606         protected ValidationCallback mValidationCallback;
607 
608         // The value that was passed to autofill() - it must be stored because it getAutofillValue()
609         // must return the exact same value that was autofilled, otherwise the widget will not be
610         // properly highlighted after autofill().
611         private long mAutofilledValue;
612 
AbstractDatePickerDelegate(DatePicker delegator, Context context)613         public AbstractDatePickerDelegate(DatePicker delegator, Context context) {
614             mDelegator = delegator;
615             mContext = context;
616 
617             setCurrentLocale(Locale.getDefault());
618         }
619 
setCurrentLocale(Locale locale)620         protected void setCurrentLocale(Locale locale) {
621             if (!locale.equals(mCurrentLocale)) {
622                 mCurrentLocale = locale;
623                 onLocaleChanged(locale);
624             }
625         }
626 
627         @Override
setOnDateChangedListener(OnDateChangedListener callback)628         public void setOnDateChangedListener(OnDateChangedListener callback) {
629             mOnDateChangedListener = callback;
630         }
631 
632         @Override
setAutoFillChangeListener(OnDateChangedListener callback)633         public void setAutoFillChangeListener(OnDateChangedListener callback) {
634             mAutoFillChangeListener = callback;
635         }
636 
637         @Override
setValidationCallback(ValidationCallback callback)638         public void setValidationCallback(ValidationCallback callback) {
639             mValidationCallback = callback;
640         }
641 
642         @Override
autofill(AutofillValue value)643         public final void autofill(AutofillValue value) {
644             if (value == null || !value.isDate()) {
645                 Log.w(LOG_TAG, value + " could not be autofilled into " + this);
646                 return;
647             }
648 
649             final long time = value.getDateValue();
650 
651             final Calendar cal = Calendar.getInstance(mCurrentLocale);
652             cal.setTimeInMillis(time);
653             updateDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH),
654                     cal.get(Calendar.DAY_OF_MONTH));
655 
656             // Must set mAutofilledValue *after* calling subclass method to make sure the value
657             // returned by getAutofillValue() matches it.
658             mAutofilledValue = time;
659         }
660 
661         @Override
getAutofillValue()662         public final AutofillValue getAutofillValue() {
663             final long time = mAutofilledValue != 0
664                     ? mAutofilledValue
665                     : mCurrentDate.getTimeInMillis();
666             return AutofillValue.forDate(time);
667         }
668 
669         /**
670          * This method must be called every time the value of the year, month, and/or day is
671          * changed by a subclass method.
672          */
resetAutofilledValue()673         protected void resetAutofilledValue() {
674             mAutofilledValue = 0;
675         }
676 
onValidationChanged(boolean valid)677         protected void onValidationChanged(boolean valid) {
678             if (mValidationCallback != null) {
679                 mValidationCallback.onValidationChanged(valid);
680             }
681         }
682 
onLocaleChanged(Locale locale)683         protected void onLocaleChanged(Locale locale) {
684             // Stub.
685         }
686 
687         @Override
onPopulateAccessibilityEvent(AccessibilityEvent event)688         public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
689             event.getText().add(getFormattedCurrentDate());
690         }
691 
getFormattedCurrentDate()692         protected String getFormattedCurrentDate() {
693            return DateUtils.formatDateTime(mContext, mCurrentDate.getTimeInMillis(),
694                    DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR
695                            | DateUtils.FORMAT_SHOW_WEEKDAY);
696         }
697 
698         /**
699          * Class for managing state storing/restoring.
700          */
701         static class SavedState extends View.BaseSavedState {
702             private final int mSelectedYear;
703             private final int mSelectedMonth;
704             private final int mSelectedDay;
705             private final long mMinDate;
706             private final long mMaxDate;
707             private final int mCurrentView;
708             private final int mListPosition;
709             private final int mListPositionOffset;
710 
SavedState(Parcelable superState, int year, int month, int day, long minDate, long maxDate)711             public SavedState(Parcelable superState, int year, int month, int day, long minDate,
712                     long maxDate) {
713                 this(superState, year, month, day, minDate, maxDate, 0, 0, 0);
714             }
715 
716             /**
717              * Constructor called from {@link DatePicker#onSaveInstanceState()}
718              */
SavedState(Parcelable superState, int year, int month, int day, long minDate, long maxDate, int currentView, int listPosition, int listPositionOffset)719             public SavedState(Parcelable superState, int year, int month, int day, long minDate,
720                     long maxDate, int currentView, int listPosition, int listPositionOffset) {
721                 super(superState);
722                 mSelectedYear = year;
723                 mSelectedMonth = month;
724                 mSelectedDay = day;
725                 mMinDate = minDate;
726                 mMaxDate = maxDate;
727                 mCurrentView = currentView;
728                 mListPosition = listPosition;
729                 mListPositionOffset = listPositionOffset;
730             }
731 
732             /**
733              * Constructor called from {@link #CREATOR}
734              */
SavedState(Parcel in)735             private SavedState(Parcel in) {
736                 super(in);
737                 mSelectedYear = in.readInt();
738                 mSelectedMonth = in.readInt();
739                 mSelectedDay = in.readInt();
740                 mMinDate = in.readLong();
741                 mMaxDate = in.readLong();
742                 mCurrentView = in.readInt();
743                 mListPosition = in.readInt();
744                 mListPositionOffset = in.readInt();
745             }
746 
747             @Override
writeToParcel(Parcel dest, int flags)748             public void writeToParcel(Parcel dest, int flags) {
749                 super.writeToParcel(dest, flags);
750                 dest.writeInt(mSelectedYear);
751                 dest.writeInt(mSelectedMonth);
752                 dest.writeInt(mSelectedDay);
753                 dest.writeLong(mMinDate);
754                 dest.writeLong(mMaxDate);
755                 dest.writeInt(mCurrentView);
756                 dest.writeInt(mListPosition);
757                 dest.writeInt(mListPositionOffset);
758             }
759 
getSelectedDay()760             public int getSelectedDay() {
761                 return mSelectedDay;
762             }
763 
getSelectedMonth()764             public int getSelectedMonth() {
765                 return mSelectedMonth;
766             }
767 
getSelectedYear()768             public int getSelectedYear() {
769                 return mSelectedYear;
770             }
771 
getMinDate()772             public long getMinDate() {
773                 return mMinDate;
774             }
775 
getMaxDate()776             public long getMaxDate() {
777                 return mMaxDate;
778             }
779 
getCurrentView()780             public int getCurrentView() {
781                 return mCurrentView;
782             }
783 
getListPosition()784             public int getListPosition() {
785                 return mListPosition;
786             }
787 
getListPositionOffset()788             public int getListPositionOffset() {
789                 return mListPositionOffset;
790             }
791 
792             @SuppressWarnings("all")
793             // suppress unused and hiding
794             public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
795 
796                 public SavedState createFromParcel(Parcel in) {
797                     return new SavedState(in);
798                 }
799 
800                 public SavedState[] newArray(int size) {
801                     return new SavedState[size];
802                 }
803             };
804         }
805     }
806 
807     /**
808      * A callback interface for updating input validity when the date picker
809      * when included into a dialog.
810      *
811      * @hide
812      */
813     public interface ValidationCallback {
onValidationChanged(boolean valid)814         void onValidationChanged(boolean valid);
815     }
816 
817     @Override
dispatchProvideAutofillStructure(ViewStructure structure, int flags)818     public void dispatchProvideAutofillStructure(ViewStructure structure, int flags) {
819         // This view is self-sufficient for autofill, so it needs to call
820         // onProvideAutoFillStructure() to fill itself, but it does not need to call
821         // dispatchProvideAutoFillStructure() to fill its children.
822         structure.setAutofillId(getAutofillId());
823         onProvideAutofillStructure(structure, flags);
824     }
825 
826     @Override
autofill(AutofillValue value)827     public void autofill(AutofillValue value) {
828         if (!isEnabled()) return;
829 
830         mDelegate.autofill(value);
831     }
832 
833     @Override
getAutofillType()834     public @AutofillType int getAutofillType() {
835         return isEnabled() ? AUTOFILL_TYPE_DATE : AUTOFILL_TYPE_NONE;
836     }
837 
838     @Override
getAutofillValue()839     public AutofillValue getAutofillValue() {
840         return isEnabled() ? mDelegate.getAutofillValue() : null;
841     }
842 }
843