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.Nullable;
20 import android.annotation.Widget;
21 import android.content.Context;
22 import android.content.res.Configuration;
23 import android.content.res.TypedArray;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.text.TextUtils;
27 import android.text.InputType;
28 import android.text.format.DateFormat;
29 import android.text.format.DateUtils;
30 import android.util.AttributeSet;
31 import android.util.Log;
32 import android.util.SparseArray;
33 import android.view.LayoutInflater;
34 import android.view.View;
35 import android.view.accessibility.AccessibilityEvent;
36 import android.view.accessibility.AccessibilityNodeInfo;
37 import android.view.inputmethod.EditorInfo;
38 import android.view.inputmethod.InputMethodManager;
39 import android.widget.NumberPicker.OnValueChangeListener;
40 
41 import com.android.internal.R;
42 
43 import java.text.DateFormatSymbols;
44 import java.text.ParseException;
45 import java.text.SimpleDateFormat;
46 import java.util.Arrays;
47 import java.util.Calendar;
48 import java.util.Locale;
49 import java.util.TimeZone;
50 
51 import libcore.icu.ICU;
52 
53 /**
54  * Provides a widget for selecting a date.
55  * <p>
56  * When the {@link android.R.styleable#DatePicker_datePickerMode} attribute is
57  * set to {@code spinner}, the date can be selected using year, month, and day
58  * spinners or a {@link CalendarView}. The set of spinners and the calendar
59  * view are automatically synchronized. The client can customize whether only
60  * the spinners, or only the calendar view, or both to be displayed.
61  * </p>
62  * <p>
63  * When the {@link android.R.styleable#DatePicker_datePickerMode} attribute is
64  * set to {@code calendar}, the month and day can be selected using a
65  * calendar-style view while the year can be selected separately using a list.
66  * </p>
67  * <p>
68  * See the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a>
69  * guide.
70  * </p>
71  * <p>
72  * For a dialog using this view, see {@link android.app.DatePickerDialog}.
73  * </p>
74  *
75  * @attr ref android.R.styleable#DatePicker_startYear
76  * @attr ref android.R.styleable#DatePicker_endYear
77  * @attr ref android.R.styleable#DatePicker_maxDate
78  * @attr ref android.R.styleable#DatePicker_minDate
79  * @attr ref android.R.styleable#DatePicker_spinnersShown
80  * @attr ref android.R.styleable#DatePicker_calendarViewShown
81  * @attr ref android.R.styleable#DatePicker_dayOfWeekBackground
82  * @attr ref android.R.styleable#DatePicker_dayOfWeekTextAppearance
83  * @attr ref android.R.styleable#DatePicker_headerBackground
84  * @attr ref android.R.styleable#DatePicker_headerMonthTextAppearance
85  * @attr ref android.R.styleable#DatePicker_headerDayOfMonthTextAppearance
86  * @attr ref android.R.styleable#DatePicker_headerYearTextAppearance
87  * @attr ref android.R.styleable#DatePicker_yearListItemTextAppearance
88  * @attr ref android.R.styleable#DatePicker_yearListSelectorColor
89  * @attr ref android.R.styleable#DatePicker_calendarTextColor
90  * @attr ref android.R.styleable#DatePicker_datePickerMode
91  */
92 @Widget
93 public class DatePicker extends FrameLayout {
94     private static final String LOG_TAG = DatePicker.class.getSimpleName();
95 
96     private static final int MODE_SPINNER = 1;
97     private static final int MODE_CALENDAR = 2;
98 
99     private final DatePickerDelegate mDelegate;
100 
101     /**
102      * The callback used to indicate the user changes\d the date.
103      */
104     public interface OnDateChangedListener {
105 
106         /**
107          * Called upon a date change.
108          *
109          * @param view The view associated with this listener.
110          * @param year The year that was set.
111          * @param monthOfYear The month that was set (0-11) for compatibility
112          *            with {@link java.util.Calendar}.
113          * @param dayOfMonth The day of the month that was set.
114          */
onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth)115         void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth);
116     }
117 
DatePicker(Context context)118     public DatePicker(Context context) {
119         this(context, null);
120     }
121 
DatePicker(Context context, AttributeSet attrs)122     public DatePicker(Context context, AttributeSet attrs) {
123         this(context, attrs, R.attr.datePickerStyle);
124     }
125 
DatePicker(Context context, AttributeSet attrs, int defStyleAttr)126     public DatePicker(Context context, AttributeSet attrs, int defStyleAttr) {
127         this(context, attrs, defStyleAttr, 0);
128     }
129 
DatePicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)130     public DatePicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
131         super(context, attrs, defStyleAttr, defStyleRes);
132 
133         final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DatePicker,
134                 defStyleAttr, defStyleRes);
135         final int mode = a.getInt(R.styleable.DatePicker_datePickerMode, MODE_SPINNER);
136         final int firstDayOfWeek = a.getInt(R.styleable.DatePicker_firstDayOfWeek, 0);
137         a.recycle();
138 
139         switch (mode) {
140             case MODE_CALENDAR:
141                 mDelegate = createCalendarUIDelegate(context, attrs, defStyleAttr, defStyleRes);
142                 break;
143             case MODE_SPINNER:
144             default:
145                 mDelegate = createSpinnerUIDelegate(context, attrs, defStyleAttr, defStyleRes);
146                 break;
147         }
148 
149         if (firstDayOfWeek != 0) {
150             setFirstDayOfWeek(firstDayOfWeek);
151         }
152     }
153 
createSpinnerUIDelegate(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)154     private DatePickerDelegate createSpinnerUIDelegate(Context context, AttributeSet attrs,
155             int defStyleAttr, int defStyleRes) {
156         return new DatePickerSpinnerDelegate(this, context, attrs, defStyleAttr, defStyleRes);
157     }
158 
createCalendarUIDelegate(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)159     private DatePickerDelegate createCalendarUIDelegate(Context context, AttributeSet attrs,
160             int defStyleAttr, int defStyleRes) {
161         return new DatePickerCalendarDelegate(this, context, attrs, defStyleAttr,
162                 defStyleRes);
163     }
164 
165     /**
166      * Initialize the state. If the provided values designate an inconsistent
167      * date the values are normalized before updating the spinners.
168      *
169      * @param year The initial year.
170      * @param monthOfYear The initial month <strong>starting from zero</strong>.
171      * @param dayOfMonth The initial day of the month.
172      * @param onDateChangedListener How user is notified date is changed by
173      *            user, can be null.
174      */
init(int year, int monthOfYear, int dayOfMonth, OnDateChangedListener onDateChangedListener)175     public void init(int year, int monthOfYear, int dayOfMonth,
176                      OnDateChangedListener onDateChangedListener) {
177         mDelegate.init(year, monthOfYear, dayOfMonth, onDateChangedListener);
178     }
179 
180     /**
181      * Update the current date.
182      *
183      * @param year The year.
184      * @param month The month which is <strong>starting from zero</strong>.
185      * @param dayOfMonth The day of the month.
186      */
updateDate(int year, int month, int dayOfMonth)187     public void updateDate(int year, int month, int dayOfMonth) {
188         mDelegate.updateDate(year, month, dayOfMonth);
189     }
190 
191     /**
192      * @return The selected year.
193      */
getYear()194     public int getYear() {
195         return mDelegate.getYear();
196     }
197 
198     /**
199      * @return The selected month.
200      */
getMonth()201     public int getMonth() {
202         return mDelegate.getMonth();
203     }
204 
205     /**
206      * @return The selected day of month.
207      */
getDayOfMonth()208     public int getDayOfMonth() {
209         return mDelegate.getDayOfMonth();
210     }
211 
212     /**
213      * Gets the minimal date supported by this {@link DatePicker} in
214      * milliseconds since January 1, 1970 00:00:00 in
215      * {@link TimeZone#getDefault()} time zone.
216      * <p>
217      * Note: The default minimal date is 01/01/1900.
218      * <p>
219      *
220      * @return The minimal supported date.
221      */
getMinDate()222     public long getMinDate() {
223         return mDelegate.getMinDate().getTimeInMillis();
224     }
225 
226     /**
227      * Sets the minimal date supported by this {@link NumberPicker} in
228      * milliseconds since January 1, 1970 00:00:00 in
229      * {@link TimeZone#getDefault()} time zone.
230      *
231      * @param minDate The minimal supported date.
232      */
setMinDate(long minDate)233     public void setMinDate(long minDate) {
234         mDelegate.setMinDate(minDate);
235     }
236 
237     /**
238      * Gets the maximal date supported by this {@link DatePicker} in
239      * milliseconds since January 1, 1970 00:00:00 in
240      * {@link TimeZone#getDefault()} time zone.
241      * <p>
242      * Note: The default maximal date is 12/31/2100.
243      * <p>
244      *
245      * @return The maximal supported date.
246      */
getMaxDate()247     public long getMaxDate() {
248         return mDelegate.getMaxDate().getTimeInMillis();
249     }
250 
251     /**
252      * Sets the maximal date supported by this {@link DatePicker} in
253      * milliseconds since January 1, 1970 00:00:00 in
254      * {@link TimeZone#getDefault()} time zone.
255      *
256      * @param maxDate The maximal supported date.
257      */
setMaxDate(long maxDate)258     public void setMaxDate(long maxDate) {
259         mDelegate.setMaxDate(maxDate);
260     }
261 
262     /**
263      * Sets the callback that indicates the current date is valid.
264      *
265      * @param callback the callback, may be null
266      * @hide
267      */
setValidationCallback(@ullable ValidationCallback callback)268     public void setValidationCallback(@Nullable ValidationCallback callback) {
269         mDelegate.setValidationCallback(callback);
270     }
271 
272     @Override
setEnabled(boolean enabled)273     public void setEnabled(boolean enabled) {
274         if (mDelegate.isEnabled() == enabled) {
275             return;
276         }
277         super.setEnabled(enabled);
278         mDelegate.setEnabled(enabled);
279     }
280 
281     @Override
isEnabled()282     public boolean isEnabled() {
283         return mDelegate.isEnabled();
284     }
285 
286     @Override
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)287     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
288         return mDelegate.dispatchPopulateAccessibilityEvent(event);
289     }
290 
291     @Override
onPopulateAccessibilityEvent(AccessibilityEvent event)292     public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
293         super.onPopulateAccessibilityEvent(event);
294         mDelegate.onPopulateAccessibilityEvent(event);
295     }
296 
297     @Override
onInitializeAccessibilityEvent(AccessibilityEvent event)298     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
299         super.onInitializeAccessibilityEvent(event);
300         mDelegate.onInitializeAccessibilityEvent(event);
301     }
302 
303     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)304     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
305         super.onInitializeAccessibilityNodeInfo(info);
306         mDelegate.onInitializeAccessibilityNodeInfo(info);
307     }
308 
309     @Override
onConfigurationChanged(Configuration newConfig)310     protected void onConfigurationChanged(Configuration newConfig) {
311         super.onConfigurationChanged(newConfig);
312         mDelegate.onConfigurationChanged(newConfig);
313     }
314 
315     /**
316      * Sets the first day of week.
317      *
318      * @param firstDayOfWeek The first day of the week conforming to the
319      *            {@link CalendarView} APIs.
320      * @see Calendar#SUNDAY
321      * @see Calendar#MONDAY
322      * @see Calendar#TUESDAY
323      * @see Calendar#WEDNESDAY
324      * @see Calendar#THURSDAY
325      * @see Calendar#FRIDAY
326      * @see Calendar#SATURDAY
327      *
328      * @attr ref android.R.styleable#DatePicker_firstDayOfWeek
329      */
setFirstDayOfWeek(int firstDayOfWeek)330     public void setFirstDayOfWeek(int firstDayOfWeek) {
331         if (firstDayOfWeek < Calendar.SUNDAY || firstDayOfWeek > Calendar.SATURDAY) {
332             throw new IllegalArgumentException("firstDayOfWeek must be between 1 and 7");
333         }
334         mDelegate.setFirstDayOfWeek(firstDayOfWeek);
335     }
336 
337     /**
338      * Gets the first day of week.
339      *
340      * @return The first day of the week conforming to the {@link CalendarView}
341      *         APIs.
342      * @see Calendar#SUNDAY
343      * @see Calendar#MONDAY
344      * @see Calendar#TUESDAY
345      * @see Calendar#WEDNESDAY
346      * @see Calendar#THURSDAY
347      * @see Calendar#FRIDAY
348      * @see Calendar#SATURDAY
349      *
350      * @attr ref android.R.styleable#DatePicker_firstDayOfWeek
351      */
getFirstDayOfWeek()352     public int getFirstDayOfWeek() {
353         return mDelegate.getFirstDayOfWeek();
354     }
355 
356     /**
357      * Gets whether the {@link CalendarView} is shown.
358      *
359      * @return True if the calendar view is shown.
360      * @see #getCalendarView()
361      */
getCalendarViewShown()362     public boolean getCalendarViewShown() {
363         return mDelegate.getCalendarViewShown();
364     }
365 
366     /**
367      * Gets the {@link CalendarView}.
368      * <p>
369      * This method returns {@code null} when the
370      * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set
371      * to {@code calendar}.
372      *
373      * @return The calendar view.
374      * @see #getCalendarViewShown()
375      */
getCalendarView()376     public CalendarView getCalendarView() {
377         return mDelegate.getCalendarView();
378     }
379 
380     /**
381      * Sets whether the {@link CalendarView} is shown.
382      * <p>
383      * Calling this method has no effect when the
384      * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set
385      * to {@code calendar}.
386      *
387      * @param shown True if the calendar view is to be shown.
388      */
setCalendarViewShown(boolean shown)389     public void setCalendarViewShown(boolean shown) {
390         mDelegate.setCalendarViewShown(shown);
391     }
392 
393     /**
394      * Gets whether the spinners are shown.
395      *
396      * @return True if the spinners are shown.
397      */
getSpinnersShown()398     public boolean getSpinnersShown() {
399         return mDelegate.getSpinnersShown();
400     }
401 
402     /**
403      * Sets whether the spinners are shown.
404      *
405      * @param shown True if the spinners are to be shown.
406      */
setSpinnersShown(boolean shown)407     public void setSpinnersShown(boolean shown) {
408         mDelegate.setSpinnersShown(shown);
409     }
410 
411     @Override
dispatchRestoreInstanceState(SparseArray<Parcelable> container)412     protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
413         dispatchThawSelfOnly(container);
414     }
415 
416     @Override
onSaveInstanceState()417     protected Parcelable onSaveInstanceState() {
418         Parcelable superState = super.onSaveInstanceState();
419         return mDelegate.onSaveInstanceState(superState);
420     }
421 
422     @Override
onRestoreInstanceState(Parcelable state)423     protected void onRestoreInstanceState(Parcelable state) {
424         BaseSavedState ss = (BaseSavedState) state;
425         super.onRestoreInstanceState(ss.getSuperState());
426         mDelegate.onRestoreInstanceState(ss);
427     }
428 
429     /**
430      * A delegate interface that defined the public API of the DatePicker. Allows different
431      * DatePicker implementations. This would need to be implemented by the DatePicker delegates
432      * for the real behavior.
433      *
434      * @hide
435      */
436     interface DatePickerDelegate {
init(int year, int monthOfYear, int dayOfMonth, OnDateChangedListener onDateChangedListener)437         void init(int year, int monthOfYear, int dayOfMonth,
438                   OnDateChangedListener onDateChangedListener);
439 
updateDate(int year, int month, int dayOfMonth)440         void updateDate(int year, int month, int dayOfMonth);
441 
getYear()442         int getYear();
getMonth()443         int getMonth();
getDayOfMonth()444         int getDayOfMonth();
445 
setFirstDayOfWeek(int firstDayOfWeek)446         void setFirstDayOfWeek(int firstDayOfWeek);
getFirstDayOfWeek()447         int getFirstDayOfWeek();
448 
setMinDate(long minDate)449         void setMinDate(long minDate);
getMinDate()450         Calendar getMinDate();
451 
setMaxDate(long maxDate)452         void setMaxDate(long maxDate);
getMaxDate()453         Calendar getMaxDate();
454 
setEnabled(boolean enabled)455         void setEnabled(boolean enabled);
isEnabled()456         boolean isEnabled();
457 
getCalendarView()458         CalendarView getCalendarView();
459 
setCalendarViewShown(boolean shown)460         void setCalendarViewShown(boolean shown);
getCalendarViewShown()461         boolean getCalendarViewShown();
462 
setSpinnersShown(boolean shown)463         void setSpinnersShown(boolean shown);
getSpinnersShown()464         boolean getSpinnersShown();
465 
setValidationCallback(ValidationCallback callback)466         void setValidationCallback(ValidationCallback callback);
467 
onConfigurationChanged(Configuration newConfig)468         void onConfigurationChanged(Configuration newConfig);
469 
onSaveInstanceState(Parcelable superState)470         Parcelable onSaveInstanceState(Parcelable superState);
onRestoreInstanceState(Parcelable state)471         void onRestoreInstanceState(Parcelable state);
472 
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)473         boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event);
onPopulateAccessibilityEvent(AccessibilityEvent event)474         void onPopulateAccessibilityEvent(AccessibilityEvent event);
onInitializeAccessibilityEvent(AccessibilityEvent event)475         void onInitializeAccessibilityEvent(AccessibilityEvent event);
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)476         void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info);
477     }
478 
479     /**
480      * An abstract class which can be used as a start for DatePicker implementations
481      */
482     abstract static class AbstractDatePickerDelegate implements DatePickerDelegate {
483         // The delegator
484         protected DatePicker mDelegator;
485 
486         // The context
487         protected Context mContext;
488 
489         // The current locale
490         protected Locale mCurrentLocale;
491 
492         // Callbacks
493         protected OnDateChangedListener mOnDateChangedListener;
494         protected ValidationCallback mValidationCallback;
495 
AbstractDatePickerDelegate(DatePicker delegator, Context context)496         public AbstractDatePickerDelegate(DatePicker delegator, Context context) {
497             mDelegator = delegator;
498             mContext = context;
499 
500             // initialization based on locale
501             setCurrentLocale(Locale.getDefault());
502         }
503 
setCurrentLocale(Locale locale)504         protected void setCurrentLocale(Locale locale) {
505             if (locale.equals(mCurrentLocale)) {
506                 return;
507             }
508             mCurrentLocale = locale;
509         }
510 
511         @Override
setValidationCallback(ValidationCallback callback)512         public void setValidationCallback(ValidationCallback callback) {
513             mValidationCallback = callback;
514         }
515 
onValidationChanged(boolean valid)516         protected void onValidationChanged(boolean valid) {
517             if (mValidationCallback != null) {
518                 mValidationCallback.onValidationChanged(valid);
519             }
520         }
521     }
522 
523     /**
524      * A callback interface for updating input validity when the date picker
525      * when included into a dialog.
526      *
527      * @hide
528      */
529     public static interface ValidationCallback {
onValidationChanged(boolean valid)530         void onValidationChanged(boolean valid);
531     }
532 
533     /**
534      * A delegate implementing the basic DatePicker
535      */
536     private static class DatePickerSpinnerDelegate extends AbstractDatePickerDelegate {
537 
538         private static final String DATE_FORMAT = "MM/dd/yyyy";
539 
540         private static final int DEFAULT_START_YEAR = 1900;
541 
542         private static final int DEFAULT_END_YEAR = 2100;
543 
544         private static final boolean DEFAULT_CALENDAR_VIEW_SHOWN = true;
545 
546         private static final boolean DEFAULT_SPINNERS_SHOWN = true;
547 
548         private static final boolean DEFAULT_ENABLED_STATE = true;
549 
550         private final LinearLayout mSpinners;
551 
552         private final NumberPicker mDaySpinner;
553 
554         private final NumberPicker mMonthSpinner;
555 
556         private final NumberPicker mYearSpinner;
557 
558         private final EditText mDaySpinnerInput;
559 
560         private final EditText mMonthSpinnerInput;
561 
562         private final EditText mYearSpinnerInput;
563 
564         private final CalendarView mCalendarView;
565 
566         private String[] mShortMonths;
567 
568         private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT);
569 
570         private int mNumberOfMonths;
571 
572         private Calendar mTempDate;
573 
574         private Calendar mMinDate;
575 
576         private Calendar mMaxDate;
577 
578         private Calendar mCurrentDate;
579 
580         private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
581 
DatePickerSpinnerDelegate(DatePicker delegator, Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)582         DatePickerSpinnerDelegate(DatePicker delegator, Context context, AttributeSet attrs,
583                 int defStyleAttr, int defStyleRes) {
584             super(delegator, context);
585 
586             mDelegator = delegator;
587             mContext = context;
588 
589             // initialization based on locale
590             setCurrentLocale(Locale.getDefault());
591 
592             final TypedArray attributesArray = context.obtainStyledAttributes(attrs,
593                     R.styleable.DatePicker, defStyleAttr, defStyleRes);
594             boolean spinnersShown = attributesArray.getBoolean(R.styleable.DatePicker_spinnersShown,
595                     DEFAULT_SPINNERS_SHOWN);
596             boolean calendarViewShown = attributesArray.getBoolean(
597                     R.styleable.DatePicker_calendarViewShown, DEFAULT_CALENDAR_VIEW_SHOWN);
598             int startYear = attributesArray.getInt(R.styleable.DatePicker_startYear,
599                     DEFAULT_START_YEAR);
600             int endYear = attributesArray.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR);
601             String minDate = attributesArray.getString(R.styleable.DatePicker_minDate);
602             String maxDate = attributesArray.getString(R.styleable.DatePicker_maxDate);
603             int layoutResourceId = attributesArray.getResourceId(
604                     R.styleable.DatePicker_legacyLayout, R.layout.date_picker_legacy);
605             attributesArray.recycle();
606 
607             LayoutInflater inflater = (LayoutInflater) context
608                     .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
609             inflater.inflate(layoutResourceId, mDelegator, true);
610 
611             OnValueChangeListener onChangeListener = new OnValueChangeListener() {
612                 public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
613                     updateInputState();
614                     mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis());
615                     // take care of wrapping of days and months to update greater fields
616                     if (picker == mDaySpinner) {
617                         int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH);
618                         if (oldVal == maxDayOfMonth && newVal == 1) {
619                             mTempDate.add(Calendar.DAY_OF_MONTH, 1);
620                         } else if (oldVal == 1 && newVal == maxDayOfMonth) {
621                             mTempDate.add(Calendar.DAY_OF_MONTH, -1);
622                         } else {
623                             mTempDate.add(Calendar.DAY_OF_MONTH, newVal - oldVal);
624                         }
625                     } else if (picker == mMonthSpinner) {
626                         if (oldVal == 11 && newVal == 0) {
627                             mTempDate.add(Calendar.MONTH, 1);
628                         } else if (oldVal == 0 && newVal == 11) {
629                             mTempDate.add(Calendar.MONTH, -1);
630                         } else {
631                             mTempDate.add(Calendar.MONTH, newVal - oldVal);
632                         }
633                     } else if (picker == mYearSpinner) {
634                         mTempDate.set(Calendar.YEAR, newVal);
635                     } else {
636                         throw new IllegalArgumentException();
637                     }
638                     // now set the date to the adjusted one
639                     setDate(mTempDate.get(Calendar.YEAR), mTempDate.get(Calendar.MONTH),
640                             mTempDate.get(Calendar.DAY_OF_MONTH));
641                     updateSpinners();
642                     updateCalendarView();
643                     notifyDateChanged();
644                 }
645             };
646 
647             mSpinners = (LinearLayout) mDelegator.findViewById(R.id.pickers);
648 
649             // calendar view day-picker
650             mCalendarView = (CalendarView) mDelegator.findViewById(R.id.calendar_view);
651             mCalendarView.setOnDateChangeListener(new CalendarView.OnDateChangeListener() {
652                 public void onSelectedDayChange(CalendarView view, int year, int month, int monthDay) {
653                     setDate(year, month, monthDay);
654                     updateSpinners();
655                     notifyDateChanged();
656                 }
657             });
658 
659             // day
660             mDaySpinner = (NumberPicker) mDelegator.findViewById(R.id.day);
661             mDaySpinner.setFormatter(NumberPicker.getTwoDigitFormatter());
662             mDaySpinner.setOnLongPressUpdateInterval(100);
663             mDaySpinner.setOnValueChangedListener(onChangeListener);
664             mDaySpinnerInput = (EditText) mDaySpinner.findViewById(R.id.numberpicker_input);
665 
666             // month
667             mMonthSpinner = (NumberPicker) mDelegator.findViewById(R.id.month);
668             mMonthSpinner.setMinValue(0);
669             mMonthSpinner.setMaxValue(mNumberOfMonths - 1);
670             mMonthSpinner.setDisplayedValues(mShortMonths);
671             mMonthSpinner.setOnLongPressUpdateInterval(200);
672             mMonthSpinner.setOnValueChangedListener(onChangeListener);
673             mMonthSpinnerInput = (EditText) mMonthSpinner.findViewById(R.id.numberpicker_input);
674 
675             // year
676             mYearSpinner = (NumberPicker) mDelegator.findViewById(R.id.year);
677             mYearSpinner.setOnLongPressUpdateInterval(100);
678             mYearSpinner.setOnValueChangedListener(onChangeListener);
679             mYearSpinnerInput = (EditText) mYearSpinner.findViewById(R.id.numberpicker_input);
680 
681             // show only what the user required but make sure we
682             // show something and the spinners have higher priority
683             if (!spinnersShown && !calendarViewShown) {
684                 setSpinnersShown(true);
685             } else {
686                 setSpinnersShown(spinnersShown);
687                 setCalendarViewShown(calendarViewShown);
688             }
689 
690             // set the min date giving priority of the minDate over startYear
691             mTempDate.clear();
692             if (!TextUtils.isEmpty(minDate)) {
693                 if (!parseDate(minDate, mTempDate)) {
694                     mTempDate.set(startYear, 0, 1);
695                 }
696             } else {
697                 mTempDate.set(startYear, 0, 1);
698             }
699             setMinDate(mTempDate.getTimeInMillis());
700 
701             // set the max date giving priority of the maxDate over endYear
702             mTempDate.clear();
703             if (!TextUtils.isEmpty(maxDate)) {
704                 if (!parseDate(maxDate, mTempDate)) {
705                     mTempDate.set(endYear, 11, 31);
706                 }
707             } else {
708                 mTempDate.set(endYear, 11, 31);
709             }
710             setMaxDate(mTempDate.getTimeInMillis());
711 
712             // initialize to current date
713             mCurrentDate.setTimeInMillis(System.currentTimeMillis());
714             init(mCurrentDate.get(Calendar.YEAR), mCurrentDate.get(Calendar.MONTH), mCurrentDate
715                     .get(Calendar.DAY_OF_MONTH), null);
716 
717             // re-order the number spinners to match the current date format
718             reorderSpinners();
719 
720             // accessibility
721             setContentDescriptions();
722 
723             // If not explicitly specified this view is important for accessibility.
724             if (mDelegator.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
725                 mDelegator.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
726             }
727         }
728 
729         @Override
init(int year, int monthOfYear, int dayOfMonth, OnDateChangedListener onDateChangedListener)730         public void init(int year, int monthOfYear, int dayOfMonth,
731                          OnDateChangedListener onDateChangedListener) {
732             setDate(year, monthOfYear, dayOfMonth);
733             updateSpinners();
734             updateCalendarView();
735             mOnDateChangedListener = onDateChangedListener;
736         }
737 
738         @Override
updateDate(int year, int month, int dayOfMonth)739         public void updateDate(int year, int month, int dayOfMonth) {
740             if (!isNewDate(year, month, dayOfMonth)) {
741                 return;
742             }
743             setDate(year, month, dayOfMonth);
744             updateSpinners();
745             updateCalendarView();
746             notifyDateChanged();
747         }
748 
749         @Override
getYear()750         public int getYear() {
751             return mCurrentDate.get(Calendar.YEAR);
752         }
753 
754         @Override
getMonth()755         public int getMonth() {
756             return mCurrentDate.get(Calendar.MONTH);
757         }
758 
759         @Override
getDayOfMonth()760         public int getDayOfMonth() {
761             return mCurrentDate.get(Calendar.DAY_OF_MONTH);
762         }
763 
764         @Override
setFirstDayOfWeek(int firstDayOfWeek)765         public void setFirstDayOfWeek(int firstDayOfWeek) {
766             mCalendarView.setFirstDayOfWeek(firstDayOfWeek);
767         }
768 
769         @Override
getFirstDayOfWeek()770         public int getFirstDayOfWeek() {
771             return mCalendarView.getFirstDayOfWeek();
772         }
773 
774         @Override
setMinDate(long minDate)775         public void setMinDate(long minDate) {
776             mTempDate.setTimeInMillis(minDate);
777             if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
778                     && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) {
779                 return;
780             }
781             mMinDate.setTimeInMillis(minDate);
782             mCalendarView.setMinDate(minDate);
783             if (mCurrentDate.before(mMinDate)) {
784                 mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
785                 updateCalendarView();
786             }
787             updateSpinners();
788         }
789 
790         @Override
getMinDate()791         public Calendar getMinDate() {
792             final Calendar minDate = Calendar.getInstance();
793             minDate.setTimeInMillis(mCalendarView.getMinDate());
794             return minDate;
795         }
796 
797         @Override
setMaxDate(long maxDate)798         public void setMaxDate(long maxDate) {
799             mTempDate.setTimeInMillis(maxDate);
800             if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
801                     && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) {
802                 return;
803             }
804             mMaxDate.setTimeInMillis(maxDate);
805             mCalendarView.setMaxDate(maxDate);
806             if (mCurrentDate.after(mMaxDate)) {
807                 mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
808                 updateCalendarView();
809             }
810             updateSpinners();
811         }
812 
813         @Override
getMaxDate()814         public Calendar getMaxDate() {
815             final Calendar maxDate = Calendar.getInstance();
816             maxDate.setTimeInMillis(mCalendarView.getMaxDate());
817             return maxDate;
818         }
819 
820         @Override
setEnabled(boolean enabled)821         public void setEnabled(boolean enabled) {
822             mDaySpinner.setEnabled(enabled);
823             mMonthSpinner.setEnabled(enabled);
824             mYearSpinner.setEnabled(enabled);
825             mCalendarView.setEnabled(enabled);
826             mIsEnabled = enabled;
827         }
828 
829         @Override
isEnabled()830         public boolean isEnabled() {
831             return mIsEnabled;
832         }
833 
834         @Override
getCalendarView()835         public CalendarView getCalendarView() {
836             return mCalendarView;
837         }
838 
839         @Override
setCalendarViewShown(boolean shown)840         public void setCalendarViewShown(boolean shown) {
841             mCalendarView.setVisibility(shown ? VISIBLE : GONE);
842         }
843 
844         @Override
getCalendarViewShown()845         public boolean getCalendarViewShown() {
846             return (mCalendarView.getVisibility() == View.VISIBLE);
847         }
848 
849         @Override
setSpinnersShown(boolean shown)850         public void setSpinnersShown(boolean shown) {
851             mSpinners.setVisibility(shown ? VISIBLE : GONE);
852         }
853 
854         @Override
getSpinnersShown()855         public boolean getSpinnersShown() {
856             return mSpinners.isShown();
857         }
858 
859         @Override
onConfigurationChanged(Configuration newConfig)860         public void onConfigurationChanged(Configuration newConfig) {
861             setCurrentLocale(newConfig.locale);
862         }
863 
864         @Override
onSaveInstanceState(Parcelable superState)865         public Parcelable onSaveInstanceState(Parcelable superState) {
866             return new SavedState(superState, getYear(), getMonth(), getDayOfMonth());
867         }
868 
869         @Override
onRestoreInstanceState(Parcelable state)870         public void onRestoreInstanceState(Parcelable state) {
871             SavedState ss = (SavedState) state;
872             setDate(ss.mYear, ss.mMonth, ss.mDay);
873             updateSpinners();
874             updateCalendarView();
875         }
876 
877         @Override
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)878         public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
879             onPopulateAccessibilityEvent(event);
880             return true;
881         }
882 
883         @Override
onPopulateAccessibilityEvent(AccessibilityEvent event)884         public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
885             final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
886             String selectedDateUtterance = DateUtils.formatDateTime(mContext,
887                     mCurrentDate.getTimeInMillis(), flags);
888             event.getText().add(selectedDateUtterance);
889         }
890 
891         @Override
onInitializeAccessibilityEvent(AccessibilityEvent event)892         public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
893             event.setClassName(DatePicker.class.getName());
894         }
895 
896         @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)897         public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
898             info.setClassName(DatePicker.class.getName());
899         }
900 
901         /**
902          * Sets the current locale.
903          *
904          * @param locale The current locale.
905          */
906         @Override
setCurrentLocale(Locale locale)907         protected void setCurrentLocale(Locale locale) {
908             super.setCurrentLocale(locale);
909 
910             mTempDate = getCalendarForLocale(mTempDate, locale);
911             mMinDate = getCalendarForLocale(mMinDate, locale);
912             mMaxDate = getCalendarForLocale(mMaxDate, locale);
913             mCurrentDate = getCalendarForLocale(mCurrentDate, locale);
914 
915             mNumberOfMonths = mTempDate.getActualMaximum(Calendar.MONTH) + 1;
916             mShortMonths = new DateFormatSymbols().getShortMonths();
917 
918             if (usingNumericMonths()) {
919                 // We're in a locale where a date should either be all-numeric, or all-text.
920                 // All-text would require custom NumberPicker formatters for day and year.
921                 mShortMonths = new String[mNumberOfMonths];
922                 for (int i = 0; i < mNumberOfMonths; ++i) {
923                     mShortMonths[i] = String.format("%d", i + 1);
924                 }
925             }
926         }
927 
928         /**
929          * Tests whether the current locale is one where there are no real month names,
930          * such as Chinese, Japanese, or Korean locales.
931          */
usingNumericMonths()932         private boolean usingNumericMonths() {
933             return Character.isDigit(mShortMonths[Calendar.JANUARY].charAt(0));
934         }
935 
936         /**
937          * Gets a calendar for locale bootstrapped with the value of a given calendar.
938          *
939          * @param oldCalendar The old calendar.
940          * @param locale The locale.
941          */
getCalendarForLocale(Calendar oldCalendar, Locale locale)942         private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
943             if (oldCalendar == null) {
944                 return Calendar.getInstance(locale);
945             } else {
946                 final long currentTimeMillis = oldCalendar.getTimeInMillis();
947                 Calendar newCalendar = Calendar.getInstance(locale);
948                 newCalendar.setTimeInMillis(currentTimeMillis);
949                 return newCalendar;
950             }
951         }
952 
953         /**
954          * Reorders the spinners according to the date format that is
955          * explicitly set by the user and if no such is set fall back
956          * to the current locale's default format.
957          */
reorderSpinners()958         private void reorderSpinners() {
959             mSpinners.removeAllViews();
960             // We use numeric spinners for year and day, but textual months. Ask icu4c what
961             // order the user's locale uses for that combination. http://b/7207103.
962             String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), "yyyyMMMdd");
963             char[] order = ICU.getDateFormatOrder(pattern);
964             final int spinnerCount = order.length;
965             for (int i = 0; i < spinnerCount; i++) {
966                 switch (order[i]) {
967                     case 'd':
968                         mSpinners.addView(mDaySpinner);
969                         setImeOptions(mDaySpinner, spinnerCount, i);
970                         break;
971                     case 'M':
972                         mSpinners.addView(mMonthSpinner);
973                         setImeOptions(mMonthSpinner, spinnerCount, i);
974                         break;
975                     case 'y':
976                         mSpinners.addView(mYearSpinner);
977                         setImeOptions(mYearSpinner, spinnerCount, i);
978                         break;
979                     default:
980                         throw new IllegalArgumentException(Arrays.toString(order));
981                 }
982             }
983         }
984 
985         /**
986          * Parses the given <code>date</code> and in case of success sets the result
987          * to the <code>outDate</code>.
988          *
989          * @return True if the date was parsed.
990          */
parseDate(String date, Calendar outDate)991         private boolean parseDate(String date, Calendar outDate) {
992             try {
993                 outDate.setTime(mDateFormat.parse(date));
994                 return true;
995             } catch (ParseException e) {
996                 Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT);
997                 return false;
998             }
999         }
1000 
isNewDate(int year, int month, int dayOfMonth)1001         private boolean isNewDate(int year, int month, int dayOfMonth) {
1002             return (mCurrentDate.get(Calendar.YEAR) != year
1003                     || mCurrentDate.get(Calendar.MONTH) != dayOfMonth
1004                     || mCurrentDate.get(Calendar.DAY_OF_MONTH) != month);
1005         }
1006 
setDate(int year, int month, int dayOfMonth)1007         private void setDate(int year, int month, int dayOfMonth) {
1008             mCurrentDate.set(year, month, dayOfMonth);
1009             if (mCurrentDate.before(mMinDate)) {
1010                 mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
1011             } else if (mCurrentDate.after(mMaxDate)) {
1012                 mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
1013             }
1014         }
1015 
updateSpinners()1016         private void updateSpinners() {
1017             // set the spinner ranges respecting the min and max dates
1018             if (mCurrentDate.equals(mMinDate)) {
1019                 mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
1020                 mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
1021                 mDaySpinner.setWrapSelectorWheel(false);
1022                 mMonthSpinner.setDisplayedValues(null);
1023                 mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH));
1024                 mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH));
1025                 mMonthSpinner.setWrapSelectorWheel(false);
1026             } else if (mCurrentDate.equals(mMaxDate)) {
1027                 mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH));
1028                 mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
1029                 mDaySpinner.setWrapSelectorWheel(false);
1030                 mMonthSpinner.setDisplayedValues(null);
1031                 mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH));
1032                 mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH));
1033                 mMonthSpinner.setWrapSelectorWheel(false);
1034             } else {
1035                 mDaySpinner.setMinValue(1);
1036                 mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
1037                 mDaySpinner.setWrapSelectorWheel(true);
1038                 mMonthSpinner.setDisplayedValues(null);
1039                 mMonthSpinner.setMinValue(0);
1040                 mMonthSpinner.setMaxValue(11);
1041                 mMonthSpinner.setWrapSelectorWheel(true);
1042             }
1043 
1044             // make sure the month names are a zero based array
1045             // with the months in the month spinner
1046             String[] displayedValues = Arrays.copyOfRange(mShortMonths,
1047                     mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1);
1048             mMonthSpinner.setDisplayedValues(displayedValues);
1049 
1050             // year spinner range does not change based on the current date
1051             mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR));
1052             mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR));
1053             mYearSpinner.setWrapSelectorWheel(false);
1054 
1055             // set the spinner values
1056             mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR));
1057             mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH));
1058             mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
1059 
1060             if (usingNumericMonths()) {
1061                 mMonthSpinnerInput.setRawInputType(InputType.TYPE_CLASS_NUMBER);
1062             }
1063         }
1064 
1065         /**
1066          * Updates the calendar view with the current date.
1067          */
updateCalendarView()1068         private void updateCalendarView() {
1069             mCalendarView.setDate(mCurrentDate.getTimeInMillis(), false, false);
1070         }
1071 
1072 
1073         /**
1074          * Notifies the listener, if such, for a change in the selected date.
1075          */
notifyDateChanged()1076         private void notifyDateChanged() {
1077             mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
1078             if (mOnDateChangedListener != null) {
1079                 mOnDateChangedListener.onDateChanged(mDelegator, getYear(), getMonth(),
1080                         getDayOfMonth());
1081             }
1082         }
1083 
1084         /**
1085          * Sets the IME options for a spinner based on its ordering.
1086          *
1087          * @param spinner The spinner.
1088          * @param spinnerCount The total spinner count.
1089          * @param spinnerIndex The index of the given spinner.
1090          */
setImeOptions(NumberPicker spinner, int spinnerCount, int spinnerIndex)1091         private void setImeOptions(NumberPicker spinner, int spinnerCount, int spinnerIndex) {
1092             final int imeOptions;
1093             if (spinnerIndex < spinnerCount - 1) {
1094                 imeOptions = EditorInfo.IME_ACTION_NEXT;
1095             } else {
1096                 imeOptions = EditorInfo.IME_ACTION_DONE;
1097             }
1098             TextView input = (TextView) spinner.findViewById(R.id.numberpicker_input);
1099             input.setImeOptions(imeOptions);
1100         }
1101 
setContentDescriptions()1102         private void setContentDescriptions() {
1103             // Day
1104             trySetContentDescription(mDaySpinner, R.id.increment,
1105                     R.string.date_picker_increment_day_button);
1106             trySetContentDescription(mDaySpinner, R.id.decrement,
1107                     R.string.date_picker_decrement_day_button);
1108             // Month
1109             trySetContentDescription(mMonthSpinner, R.id.increment,
1110                     R.string.date_picker_increment_month_button);
1111             trySetContentDescription(mMonthSpinner, R.id.decrement,
1112                     R.string.date_picker_decrement_month_button);
1113             // Year
1114             trySetContentDescription(mYearSpinner, R.id.increment,
1115                     R.string.date_picker_increment_year_button);
1116             trySetContentDescription(mYearSpinner, R.id.decrement,
1117                     R.string.date_picker_decrement_year_button);
1118         }
1119 
trySetContentDescription(View root, int viewId, int contDescResId)1120         private void trySetContentDescription(View root, int viewId, int contDescResId) {
1121             View target = root.findViewById(viewId);
1122             if (target != null) {
1123                 target.setContentDescription(mContext.getString(contDescResId));
1124             }
1125         }
1126 
updateInputState()1127         private void updateInputState() {
1128             // Make sure that if the user changes the value and the IME is active
1129             // for one of the inputs if this widget, the IME is closed. If the user
1130             // changed the value via the IME and there is a next input the IME will
1131             // be shown, otherwise the user chose another means of changing the
1132             // value and having the IME up makes no sense.
1133             InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
1134             if (inputMethodManager != null) {
1135                 if (inputMethodManager.isActive(mYearSpinnerInput)) {
1136                     mYearSpinnerInput.clearFocus();
1137                     inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
1138                 } else if (inputMethodManager.isActive(mMonthSpinnerInput)) {
1139                     mMonthSpinnerInput.clearFocus();
1140                     inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
1141                 } else if (inputMethodManager.isActive(mDaySpinnerInput)) {
1142                     mDaySpinnerInput.clearFocus();
1143                     inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
1144                 }
1145             }
1146         }
1147     }
1148 
1149     /**
1150      * Class for managing state storing/restoring.
1151      */
1152     private static class SavedState extends BaseSavedState {
1153 
1154         private final int mYear;
1155 
1156         private final int mMonth;
1157 
1158         private final int mDay;
1159 
1160         /**
1161          * Constructor called from {@link DatePicker#onSaveInstanceState()}
1162          */
SavedState(Parcelable superState, int year, int month, int day)1163         private SavedState(Parcelable superState, int year, int month, int day) {
1164             super(superState);
1165             mYear = year;
1166             mMonth = month;
1167             mDay = day;
1168         }
1169 
1170         /**
1171          * Constructor called from {@link #CREATOR}
1172          */
SavedState(Parcel in)1173         private SavedState(Parcel in) {
1174             super(in);
1175             mYear = in.readInt();
1176             mMonth = in.readInt();
1177             mDay = in.readInt();
1178         }
1179 
1180         @Override
writeToParcel(Parcel dest, int flags)1181         public void writeToParcel(Parcel dest, int flags) {
1182             super.writeToParcel(dest, flags);
1183             dest.writeInt(mYear);
1184             dest.writeInt(mMonth);
1185             dest.writeInt(mDay);
1186         }
1187 
1188         @SuppressWarnings("all")
1189         // suppress unused and hiding
1190         public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
1191 
1192             public SavedState createFromParcel(Parcel in) {
1193                 return new SavedState(in);
1194             }
1195 
1196             public SavedState[] newArray(int size) {
1197                 return new SavedState[size];
1198             }
1199         };
1200     }
1201 }
1202