DatePicker.java revision f5926962cc665d4a2e6464f9ba9e3e9788496a6f
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
17package android.widget;
18
19import android.annotation.Widget;
20import android.content.Context;
21import android.content.res.Configuration;
22import android.content.res.TypedArray;
23import android.os.Parcel;
24import android.os.Parcelable;
25import android.text.TextUtils;
26import android.text.format.DateFormat;
27import android.text.format.DateUtils;
28import android.util.AttributeSet;
29import android.util.Log;
30import android.util.SparseArray;
31import android.view.LayoutInflater;
32import android.view.accessibility.AccessibilityEvent;
33import android.widget.NumberPicker.OnValueChangeListener;
34
35import com.android.internal.R;
36
37import java.text.ParseException;
38import java.text.SimpleDateFormat;
39import java.util.Arrays;
40import java.util.Calendar;
41import java.util.Locale;
42import java.util.TimeZone;
43
44/**
45 * This class is a widget for selecting a date. The date can be selected by a
46 * year, month, and day spinners or a {@link CalendarView}. The set of spinners
47 * and the calendar view are automatically synchronized. The client can
48 * customize whether only the spinners, or only the calendar view, or both to be
49 * displayed. Also the minimal and maximal date from which dates to be selected
50 * can be customized.
51 * <p>
52 * See the <a href="{@docRoot}resources/tutorials/views/hello-datepicker.html">Date
53 * Picker tutorial</a>.
54 * </p>
55 * <p>
56 * For a dialog using this view, see {@link android.app.DatePickerDialog}.
57 * </p>
58 *
59 * @attr ref android.R.styleable#DatePicker_startYear
60 * @attr ref android.R.styleable#DatePicker_endYear
61 * @attr ref android.R.styleable#DatePicker_maxDate
62 * @attr ref android.R.styleable#DatePicker_minDate
63 * @attr ref android.R.styleable#DatePicker_spinnersShown
64 * @attr ref android.R.styleable#DatePicker_calendarViewShown
65 */
66@Widget
67public class DatePicker extends FrameLayout {
68
69    private static final String LOG_TAG = DatePicker.class.getSimpleName();
70
71    private static final String DATE_FORMAT = "MM/dd/yyyy";
72
73    private static final int DEFAULT_START_YEAR = 1900;
74
75    private static final int DEFAULT_END_YEAR = 2100;
76
77    private static final boolean DEFAULT_CALENDAR_VIEW_SHOWN = true;
78
79    private static final boolean DEFAULT_SPINNERS_SHOWN = true;
80
81    private static final boolean DEFAULT_ENABLED_STATE = true;
82
83    private final NumberPicker mDaySpinner;
84
85    private final LinearLayout mSpinners;
86
87    private final NumberPicker mMonthSpinner;
88
89    private final NumberPicker mYearSpinner;
90
91    private final CalendarView mCalendarView;
92
93    private Locale mCurrentLocale;
94
95    private OnDateChangedListener mOnDateChangedListener;
96
97    private String[] mShortMonths;
98
99    private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT);
100
101    private int mNumberOfMonths;
102
103    private Calendar mTempDate;
104
105    private Calendar mMinDate;
106
107    private Calendar mMaxDate;
108
109    private Calendar mCurrentDate;
110
111    private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
112
113    /**
114     * The callback used to indicate the user changes\d the date.
115     */
116    public interface OnDateChangedListener {
117
118        /**
119         * Called upon a date change.
120         *
121         * @param view The view associated with this listener.
122         * @param year The year that was set.
123         * @param monthOfYear The month that was set (0-11) for compatibility
124         *            with {@link java.util.Calendar}.
125         * @param dayOfMonth The day of the month that was set.
126         */
127        void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth);
128    }
129
130    public DatePicker(Context context) {
131        this(context, null);
132    }
133
134    public DatePicker(Context context, AttributeSet attrs) {
135        this(context, attrs, R.attr.datePickerStyle);
136    }
137
138    public DatePicker(Context context, AttributeSet attrs, int defStyle) {
139        super(context, attrs, defStyle);
140
141        // initialization based on locale
142        setCurrentLocale(Locale.getDefault());
143
144        TypedArray attributesArray = context.obtainStyledAttributes(attrs, R.styleable.DatePicker,
145                defStyle, 0);
146        boolean spinnersShown = attributesArray.getBoolean(R.styleable.DatePicker_spinnersShown,
147                DEFAULT_SPINNERS_SHOWN);
148        boolean calendarViewShown = attributesArray.getBoolean(
149                R.styleable.DatePicker_calendarViewShown, DEFAULT_CALENDAR_VIEW_SHOWN);
150        int startYear = attributesArray.getInt(R.styleable.DatePicker_startYear,
151                DEFAULT_START_YEAR);
152        int endYear = attributesArray.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR);
153        String minDate = attributesArray.getString(R.styleable.DatePicker_minDate);
154        String maxDate = attributesArray.getString(R.styleable.DatePicker_maxDate);
155        int layoutResourceId = attributesArray.getResourceId(R.styleable.DatePicker_layout,
156                R.layout.date_picker);
157        attributesArray.recycle();
158
159        LayoutInflater inflater = (LayoutInflater) context
160                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
161        inflater.inflate(layoutResourceId, this, true);
162
163        OnValueChangeListener onChangeListener = new OnValueChangeListener() {
164            public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
165                mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis());
166                // take care of wrapping of days and months to update greater fields
167                if (picker == mDaySpinner) {
168                    int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH);
169                    if (oldVal == maxDayOfMonth && newVal == 1) {
170                        mTempDate.add(Calendar.DAY_OF_MONTH, 1);
171                    } else if (oldVal == 1 && newVal == maxDayOfMonth) {
172                        mTempDate.add(Calendar.DAY_OF_MONTH, -1);
173                    } else {
174                        mTempDate.add(Calendar.DAY_OF_MONTH, newVal - oldVal);
175                    }
176                } else if (picker == mMonthSpinner) {
177                    if (oldVal == 11 && newVal == 0) {
178                        mTempDate.add(Calendar.MONTH, 1);
179                    } else if (oldVal == 0 && newVal == 11) {
180                        mTempDate.add(Calendar.MONTH, -1);
181                    } else {
182                        mTempDate.add(Calendar.MONTH, newVal - oldVal);
183                    }
184                } else if (picker == mYearSpinner) {
185                    mTempDate.set(Calendar.YEAR, newVal);
186                } else {
187                    throw new IllegalArgumentException();
188                }
189                // now set the date to the adjusted one
190                setDate(mTempDate.get(Calendar.YEAR), mTempDate.get(Calendar.MONTH),
191                        mTempDate.get(Calendar.DAY_OF_MONTH));
192                updateSpinners();
193                updateCalendarView();
194                notifyDateChanged();
195            }
196        };
197
198        mSpinners = (LinearLayout) findViewById(R.id.pickers);
199
200        // calendar view day-picker
201        mCalendarView = (CalendarView) findViewById(R.id.calendar_view);
202        mCalendarView.setOnDateChangeListener(new CalendarView.OnDateChangeListener() {
203            public void onSelectedDayChange(CalendarView view, int year, int month, int monthDay) {
204                setDate(year, month, monthDay);
205                updateSpinners();
206                notifyDateChanged();
207            }
208        });
209
210        // day
211        mDaySpinner = (NumberPicker) findViewById(R.id.day);
212        mDaySpinner.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER);
213        mDaySpinner.setOnLongPressUpdateInterval(100);
214        mDaySpinner.setOnValueChangedListener(onChangeListener);
215
216        // month
217        mMonthSpinner = (NumberPicker) findViewById(R.id.month);
218        mMonthSpinner.setMinValue(0);
219        mMonthSpinner.setMaxValue(mNumberOfMonths - 1);
220        mMonthSpinner.setDisplayedValues(mShortMonths);
221        mMonthSpinner.setOnLongPressUpdateInterval(200);
222        mMonthSpinner.setOnValueChangedListener(onChangeListener);
223
224        // year
225        mYearSpinner = (NumberPicker) findViewById(R.id.year);
226        mYearSpinner.setOnLongPressUpdateInterval(100);
227        mYearSpinner.setOnValueChangedListener(onChangeListener);
228
229        // show only what the user required but make sure we
230        // show something and the spinners have higher priority
231        if (!spinnersShown && !calendarViewShown) {
232            setSpinnersShown(true);
233        } else {
234            setSpinnersShown(spinnersShown);
235            setCalendarViewShown(calendarViewShown);
236        }
237
238        // set the min date giving priority of the minDate over startYear
239        mTempDate.clear();
240        if (!TextUtils.isEmpty(minDate)) {
241            if (!parseDate(minDate, mTempDate)) {
242                mTempDate.set(startYear, 0, 1);
243            }
244        } else {
245            mTempDate.set(startYear, 0, 1);
246        }
247        setMinDate(mTempDate.getTimeInMillis());
248
249        // set the max date giving priority of the maxDate over endYear
250        mTempDate.clear();
251        if (!TextUtils.isEmpty(maxDate)) {
252            if (!parseDate(maxDate, mTempDate)) {
253                mTempDate.set(endYear, 11, 31);
254            }
255        } else {
256            mTempDate.set(endYear, 11, 31);
257        }
258        setMaxDate(mTempDate.getTimeInMillis());
259
260        // initialize to current date
261        mCurrentDate.setTimeInMillis(System.currentTimeMillis());
262        init(mCurrentDate.get(Calendar.YEAR), mCurrentDate.get(Calendar.MONTH), mCurrentDate
263                .get(Calendar.DAY_OF_MONTH), null);
264
265        // re-order the number spinners to match the current date format
266        reorderSpinners();
267    }
268
269    /**
270     * Gets the minimal date supported by this {@link DatePicker} in
271     * milliseconds since January 1, 1970 00:00:00 in
272     * {@link TimeZone#getDefault()} time zone.
273     * <p>
274     * Note: The default minimal date is 01/01/1900.
275     * <p>
276     *
277     * @return The minimal supported date.
278     */
279    public long getMinDate() {
280        return mCalendarView.getMinDate();
281    }
282
283    /**
284     * Sets the minimal date supported by this {@link NumberPicker} in
285     * milliseconds since January 1, 1970 00:00:00 in
286     * {@link TimeZone#getDefault()} time zone.
287     *
288     * @param minDate The minimal supported date.
289     */
290    public void setMinDate(long minDate) {
291        mTempDate.setTimeInMillis(minDate);
292        if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
293                && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) {
294            return;
295        }
296        mMinDate.setTimeInMillis(minDate);
297        mCalendarView.setMinDate(minDate);
298        if (mCurrentDate.before(mMinDate)) {
299            mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
300            updateCalendarView();
301        }
302        updateSpinners();
303    }
304
305    /**
306     * Gets the maximal date supported by this {@link DatePicker} in
307     * milliseconds since January 1, 1970 00:00:00 in
308     * {@link TimeZone#getDefault()} time zone.
309     * <p>
310     * Note: The default maximal date is 12/31/2100.
311     * <p>
312     *
313     * @return The maximal supported date.
314     */
315    public long getMaxDate() {
316        return mCalendarView.getMaxDate();
317    }
318
319    /**
320     * Sets the maximal date supported by this {@link DatePicker} in
321     * milliseconds since January 1, 1970 00:00:00 in
322     * {@link TimeZone#getDefault()} time zone.
323     *
324     * @param maxDate The maximal supported date.
325     */
326    public void setMaxDate(long maxDate) {
327        mTempDate.setTimeInMillis(maxDate);
328        if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
329                && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) {
330            return;
331        }
332        mMaxDate.setTimeInMillis(maxDate);
333        mCalendarView.setMaxDate(maxDate);
334        if (mCurrentDate.after(mMaxDate)) {
335            mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
336            updateCalendarView();
337        }
338        updateSpinners();
339    }
340
341    @Override
342    public void setEnabled(boolean enabled) {
343        if (mIsEnabled == enabled) {
344            return;
345        }
346        super.setEnabled(enabled);
347        mDaySpinner.setEnabled(enabled);
348        mMonthSpinner.setEnabled(enabled);
349        mYearSpinner.setEnabled(enabled);
350        mCalendarView.setEnabled(enabled);
351        mIsEnabled = enabled;
352    }
353
354    @Override
355    public boolean isEnabled() {
356        return mIsEnabled;
357    }
358
359    @Override
360    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
361        super.onPopulateAccessibilityEvent(event);
362
363        final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY
364                | DateUtils.FORMAT_SHOW_YEAR;
365        String selectedDateUtterance = DateUtils.formatDateTime(mContext,
366                mCurrentDate.getTimeInMillis(), flags);
367        event.getText().add(selectedDateUtterance);
368    }
369
370    @Override
371    protected void onConfigurationChanged(Configuration newConfig) {
372        super.onConfigurationChanged(newConfig);
373        setCurrentLocale(newConfig.locale);
374    }
375
376    /**
377     * Gets whether the {@link CalendarView} is shown.
378     *
379     * @return True if the calendar view is shown.
380     * @see #getCalendarView()
381     */
382    public boolean getCalendarViewShown() {
383        return mCalendarView.isShown();
384    }
385
386    /**
387     * Gets the {@link CalendarView}.
388     *
389     * @return The calendar view.
390     * @see #getCalendarViewShown()
391     */
392    public CalendarView getCalendarView () {
393        return mCalendarView;
394    }
395
396    /**
397     * Sets whether the {@link CalendarView} is shown.
398     *
399     * @param shown True if the calendar view is to be shown.
400     */
401    public void setCalendarViewShown(boolean shown) {
402        mCalendarView.setVisibility(shown ? VISIBLE : GONE);
403    }
404
405    /**
406     * Gets whether the spinners are shown.
407     *
408     * @return True if the spinners are shown.
409     */
410    public boolean getSpinnersShown() {
411        return mSpinners.isShown();
412    }
413
414    /**
415     * Sets whether the spinners are shown.
416     *
417     * @param shown True if the spinners are to be shown.
418     */
419    public void setSpinnersShown(boolean shown) {
420        mSpinners.setVisibility(shown ? VISIBLE : GONE);
421    }
422
423    /**
424     * Sets the current locale.
425     *
426     * @param locale The current locale.
427     */
428    private void setCurrentLocale(Locale locale) {
429        if (locale.equals(mCurrentLocale)) {
430            return;
431        }
432
433        mCurrentLocale = locale;
434
435        mTempDate = getCalendarForLocale(mTempDate, locale);
436        mMinDate = getCalendarForLocale(mMinDate, locale);
437        mMaxDate = getCalendarForLocale(mMaxDate, locale);
438        mCurrentDate = getCalendarForLocale(mCurrentDate, locale);
439
440        mNumberOfMonths = mTempDate.getActualMaximum(Calendar.MONTH) + 1;
441        mShortMonths = new String[mNumberOfMonths];
442        for (int i = 0; i < mNumberOfMonths; i++) {
443            mShortMonths[i] = DateUtils.getMonthString(Calendar.JANUARY + i,
444                    DateUtils.LENGTH_MEDIUM);
445        }
446    }
447
448    /**
449     * Gets a calendar for locale bootstrapped with the value of a given calendar.
450     *
451     * @param oldCalendar The old calendar.
452     * @param locale The locale.
453     */
454    private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
455        if (oldCalendar == null) {
456            return Calendar.getInstance(locale);
457        } else {
458            final long currentTimeMillis = oldCalendar.getTimeInMillis();
459            Calendar newCalendar = Calendar.getInstance(locale);
460            newCalendar.setTimeInMillis(currentTimeMillis);
461            return newCalendar;
462        }
463    }
464
465    /**
466     * Reorders the spinners according to the date format that is
467     * explicitly set by the user and if no such is set fall back
468     * to the current locale's default format.
469     */
470    private void reorderSpinners() {
471        mSpinners.removeAllViews();
472        char[] order = DateFormat.getDateFormatOrder(getContext());
473        for (int i = 0; i < order.length; i++) {
474            switch (order[i]) {
475                case DateFormat.DATE:
476                    mSpinners.addView(mDaySpinner);
477                    break;
478                case DateFormat.MONTH:
479                    mSpinners.addView(mMonthSpinner);
480                    break;
481                case DateFormat.YEAR:
482                    mSpinners.addView(mYearSpinner);
483                    break;
484                default:
485                    throw new IllegalArgumentException();
486            }
487        }
488    }
489
490    /**
491     * Updates the current date.
492     *
493     * @param year The year.
494     * @param month The month which is <strong>starting from zero</strong>.
495     * @param dayOfMonth The day of the month.
496     */
497    public void updateDate(int year, int month, int dayOfMonth) {
498        if (!isNewDate(year, month, dayOfMonth)) {
499            return;
500        }
501        setDate(year, month, dayOfMonth);
502        updateSpinners();
503        updateCalendarView();
504        notifyDateChanged();
505    }
506
507    // Override so we are in complete control of save / restore for this widget.
508    @Override
509    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
510        dispatchThawSelfOnly(container);
511    }
512
513    @Override
514    protected Parcelable onSaveInstanceState() {
515        Parcelable superState = super.onSaveInstanceState();
516        return new SavedState(superState, getYear(), getMonth(), getDayOfMonth());
517    }
518
519    @Override
520    protected void onRestoreInstanceState(Parcelable state) {
521        SavedState ss = (SavedState) state;
522        super.onRestoreInstanceState(ss.getSuperState());
523        setDate(ss.mYear, ss.mMonth, ss.mDay);
524        updateSpinners();
525        updateCalendarView();
526    }
527
528    /**
529     * Initialize the state. If the provided values designate an inconsistent
530     * date the values are normalized before updating the spinners.
531     *
532     * @param year The initial year.
533     * @param monthOfYear The initial month <strong>starting from zero</strong>.
534     * @param dayOfMonth The initial day of the month.
535     * @param onDateChangedListener How user is notified date is changed by
536     *            user, can be null.
537     */
538    public void init(int year, int monthOfYear, int dayOfMonth,
539            OnDateChangedListener onDateChangedListener) {
540        setDate(year, monthOfYear, dayOfMonth);
541        updateSpinners();
542        updateCalendarView();
543        mOnDateChangedListener = onDateChangedListener;
544    }
545
546    /**
547     * Parses the given <code>date</code> and in case of success sets the result
548     * to the <code>outDate</code>.
549     *
550     * @return True if the date was parsed.
551     */
552    private boolean parseDate(String date, Calendar outDate) {
553        try {
554            outDate.setTime(mDateFormat.parse(date));
555            return true;
556        } catch (ParseException e) {
557            Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT);
558            return false;
559        }
560    }
561
562    private boolean isNewDate(int year, int month, int dayOfMonth) {
563        return (mCurrentDate.get(Calendar.YEAR) != year
564                || mCurrentDate.get(Calendar.MONTH) != dayOfMonth
565                || mCurrentDate.get(Calendar.DAY_OF_MONTH) != month);
566    }
567
568    private void setDate(int year, int month, int dayOfMonth) {
569        mCurrentDate.set(year, month, dayOfMonth);
570        if (mCurrentDate.before(mMinDate)) {
571            mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
572        } else if (mCurrentDate.after(mMaxDate)) {
573            mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
574        }
575    }
576
577    private void updateSpinners() {
578        // set the spinner ranges respecting the min and max dates
579        if (mCurrentDate.equals(mMinDate)) {
580            mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
581            mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
582            mDaySpinner.setWrapSelectorWheel(false);
583            mMonthSpinner.setDisplayedValues(null);
584            mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH));
585            mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH));
586            mMonthSpinner.setWrapSelectorWheel(false);
587        } else if (mCurrentDate.equals(mMaxDate)) {
588            mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH));
589            mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
590            mDaySpinner.setWrapSelectorWheel(false);
591            mMonthSpinner.setDisplayedValues(null);
592            mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH));
593            mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH));
594            mMonthSpinner.setWrapSelectorWheel(false);
595        } else {
596            mDaySpinner.setMinValue(1);
597            mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
598            mDaySpinner.setWrapSelectorWheel(true);
599            mMonthSpinner.setDisplayedValues(null);
600            mMonthSpinner.setMinValue(0);
601            mMonthSpinner.setMaxValue(11);
602            mMonthSpinner.setWrapSelectorWheel(true);
603        }
604
605        // make sure the month names are a zero based array
606        // with the months in the month spinner
607        String[] displayedValues = Arrays.copyOfRange(mShortMonths,
608                mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1);
609        mMonthSpinner.setDisplayedValues(displayedValues);
610
611        // year spinner range does not change based on the current date
612        mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR));
613        mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR));
614        mYearSpinner.setWrapSelectorWheel(false);
615
616        // set the spinner values
617        mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR));
618        mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH));
619        mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
620    }
621
622    /**
623     * Updates the calendar view with the current date.
624     */
625    private void updateCalendarView() {
626         mCalendarView.setDate(mCurrentDate.getTimeInMillis(), false, false);
627    }
628
629    /**
630     * @return The selected year.
631     */
632    public int getYear() {
633        return mCurrentDate.get(Calendar.YEAR);
634    }
635
636    /**
637     * @return The selected month.
638     */
639    public int getMonth() {
640        return mCurrentDate.get(Calendar.MONTH);
641    }
642
643    /**
644     * @return The selected day of month.
645     */
646    public int getDayOfMonth() {
647        return mCurrentDate.get(Calendar.DAY_OF_MONTH);
648    }
649
650    /**
651     * Notifies the listener, if such, for a change in the selected date.
652     */
653    private void notifyDateChanged() {
654        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
655        if (mOnDateChangedListener != null) {
656            mOnDateChangedListener.onDateChanged(this, getYear(), getMonth(), getDayOfMonth());
657        }
658    }
659
660    /**
661     * Class for managing state storing/restoring.
662     */
663    private static class SavedState extends BaseSavedState {
664
665        private final int mYear;
666
667        private final int mMonth;
668
669        private final int mDay;
670
671        /**
672         * Constructor called from {@link DatePicker#onSaveInstanceState()}
673         */
674        private SavedState(Parcelable superState, int year, int month, int day) {
675            super(superState);
676            mYear = year;
677            mMonth = month;
678            mDay = day;
679        }
680
681        /**
682         * Constructor called from {@link #CREATOR}
683         */
684        private SavedState(Parcel in) {
685            super(in);
686            mYear = in.readInt();
687            mMonth = in.readInt();
688            mDay = in.readInt();
689        }
690
691        @Override
692        public void writeToParcel(Parcel dest, int flags) {
693            super.writeToParcel(dest, flags);
694            dest.writeInt(mYear);
695            dest.writeInt(mMonth);
696            dest.writeInt(mDay);
697        }
698
699        @SuppressWarnings("all")
700        // suppress unused and hiding
701        public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
702
703            public SavedState createFromParcel(Parcel in) {
704                return new SavedState(in);
705            }
706
707            public SavedState[] newArray(int size) {
708                return new SavedState[size];
709            }
710        };
711    }
712}
713
714