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