Home | History | Annotate | Download | only in widget
      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.UnsupportedAppUsage;
     23 import android.annotation.Widget;
     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          */
    139         void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth);
    140     }
    141 
    142     public DatePicker(Context context) {
    143         this(context, null);
    144     }
    145 
    146     public DatePicker(Context context, AttributeSet attrs) {
    147         this(context, attrs, R.attr.datePickerStyle);
    148     }
    149 
    150     public DatePicker(Context context, AttributeSet attrs, int defStyleAttr) {
    151         this(context, attrs, defStyleAttr, 0);
    152     }
    153 
    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 
    201     private DatePickerDelegate createSpinnerUIDelegate(Context context, AttributeSet attrs,
    202             int defStyleAttr, int defStyleRes) {
    203         return new DatePickerSpinnerDelegate(this, context, attrs, defStyleAttr, defStyleRes);
    204     }
    205 
    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
    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      */
    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      */
    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      */
    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)
    268     public int getYear() {
    269         return mDelegate.getYear();
    270     }
    271 
    272     /**
    273      * @return The selected month.
    274      */
    275     @InspectableProperty(hasAttributeId = false)
    276     public int getMonth() {
    277         return mDelegate.getMonth();
    278     }
    279 
    280     /**
    281      * @return The selected day of month.
    282      */
    283     @InspectableProperty(hasAttributeId = false)
    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
    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      */
    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
    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      */
    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
    347     public void setValidationCallback(@Nullable ValidationCallback callback) {
    348         mDelegate.setValidationCallback(callback);
    349     }
    350 
    351     @Override
    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
    361     public boolean isEnabled() {
    362         return mDelegate.isEnabled();
    363     }
    364 
    365     /** @hide */
    366     @Override
    367     public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
    368         return mDelegate.dispatchPopulateAccessibilityEvent(event);
    369     }
    370 
    371     /** @hide */
    372     @Override
    373     public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
    374         super.onPopulateAccessibilityEventInternal(event);
    375         mDelegate.onPopulateAccessibilityEvent(event);
    376     }
    377 
    378     @Override
    379     public CharSequence getAccessibilityClassName() {
    380         return DatePicker.class.getName();
    381     }
    382 
    383     @Override
    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      */
    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
    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
    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
    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
    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
    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
    511     public void setSpinnersShown(boolean shown) {
    512         mDelegate.setSpinnersShown(shown);
    513     }
    514 
    515     @Override
    516     protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
    517         dispatchThawSelfOnly(container);
    518     }
    519 
    520     @Override
    521     protected Parcelable onSaveInstanceState() {
    522         Parcelable superState = super.onSaveInstanceState();
    523         return mDelegate.onSaveInstanceState(superState);
    524     }
    525 
    526     @Override
    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 {
    541         void init(int year, int monthOfYear, int dayOfMonth,
    542                   OnDateChangedListener onDateChangedListener);
    543 
    544         void setOnDateChangedListener(OnDateChangedListener onDateChangedListener);
    545         void setAutoFillChangeListener(OnDateChangedListener onDateChangedListener);
    546 
    547         void updateDate(int year, int month, int dayOfMonth);
    548 
    549         int getYear();
    550         int getMonth();
    551         int getDayOfMonth();
    552 
    553         void autofill(AutofillValue value);
    554         AutofillValue getAutofillValue();
    555 
    556         void setFirstDayOfWeek(int firstDayOfWeek);
    557         int getFirstDayOfWeek();
    558 
    559         void setMinDate(long minDate);
    560         Calendar getMinDate();
    561 
    562         void setMaxDate(long maxDate);
    563         Calendar getMaxDate();
    564 
    565         void setEnabled(boolean enabled);
    566         boolean isEnabled();
    567 
    568         CalendarView getCalendarView();
    569 
    570         void setCalendarViewShown(boolean shown);
    571         boolean getCalendarViewShown();
    572 
    573         void setSpinnersShown(boolean shown);
    574         boolean getSpinnersShown();
    575 
    576         void setValidationCallback(ValidationCallback callback);
    577 
    578         void onConfigurationChanged(Configuration newConfig);
    579 
    580         Parcelable onSaveInstanceState(Parcelable superState);
    581         void onRestoreInstanceState(Parcelable state);
    582 
    583         boolean dispatchPopulateAccessibilityEvent(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 
    613         public AbstractDatePickerDelegate(DatePicker delegator, Context context) {
    614             mDelegator = delegator;
    615             mContext = context;
    616 
    617             setCurrentLocale(Locale.getDefault());
    618         }
    619 
    620         protected void setCurrentLocale(Locale locale) {
    621             if (!locale.equals(mCurrentLocale)) {
    622                 mCurrentLocale = locale;
    623                 onLocaleChanged(locale);
    624             }
    625         }
    626 
    627         @Override
    628         public void setOnDateChangedListener(OnDateChangedListener callback) {
    629             mOnDateChangedListener = callback;
    630         }
    631 
    632         @Override
    633         public void setAutoFillChangeListener(OnDateChangedListener callback) {
    634             mAutoFillChangeListener = callback;
    635         }
    636 
    637         @Override
    638         public void setValidationCallback(ValidationCallback callback) {
    639             mValidationCallback = callback;
    640         }
    641 
    642         @Override
    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
    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          */
    673         protected void resetAutofilledValue() {
    674             mAutofilledValue = 0;
    675         }
    676 
    677         protected void onValidationChanged(boolean valid) {
    678             if (mValidationCallback != null) {
    679                 mValidationCallback.onValidationChanged(valid);
    680             }
    681         }
    682 
    683         protected void onLocaleChanged(Locale locale) {
    684             // Stub.
    685         }
    686 
    687         @Override
    688         public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
    689             event.getText().add(getFormattedCurrentDate());
    690         }
    691 
    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 
    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              */
    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              */
    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
    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 
    760             public int getSelectedDay() {
    761                 return mSelectedDay;
    762             }
    763 
    764             public int getSelectedMonth() {
    765                 return mSelectedMonth;
    766             }
    767 
    768             public int getSelectedYear() {
    769                 return mSelectedYear;
    770             }
    771 
    772             public long getMinDate() {
    773                 return mMinDate;
    774             }
    775 
    776             public long getMaxDate() {
    777                 return mMaxDate;
    778             }
    779 
    780             public int getCurrentView() {
    781                 return mCurrentView;
    782             }
    783 
    784             public int getListPosition() {
    785                 return mListPosition;
    786             }
    787 
    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 {
    814         void onValidationChanged(boolean valid);
    815     }
    816 
    817     @Override
    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
    827     public void autofill(AutofillValue value) {
    828         if (!isEnabled()) return;
    829 
    830         mDelegate.autofill(value);
    831     }
    832 
    833     @Override
    834     public @AutofillType int getAutofillType() {
    835         return isEnabled() ? AUTOFILL_TYPE_DATE : AUTOFILL_TYPE_NONE;
    836     }
    837 
    838     @Override
    839     public AutofillValue getAutofillValue() {
    840         return isEnabled() ? mDelegate.getAutofillValue() : null;
    841     }
    842 }
    843