OSDN Git Service

Clean up of the DatePicker
[android-x86/frameworks-base.git] / core / java / android / widget / DatePicker.java
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 com.android.internal.R;
20
21 import android.annotation.Widget;
22 import android.content.Context;
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.format.DateFormat;
28 import android.text.format.DateUtils;
29 import android.util.AttributeSet;
30 import android.util.Log;
31 import android.util.SparseArray;
32 import android.view.LayoutInflater;
33 import android.view.accessibility.AccessibilityEvent;
34 import android.widget.NumberPicker.OnValueChangeListener;
35
36 import java.text.ParseException;
37 import java.text.SimpleDateFormat;
38 import java.util.Arrays;
39 import java.util.Calendar;
40 import java.util.Locale;
41 import 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
66 public 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      */
370     public boolean getCalendarViewShown() {
371         return mCalendarView.isShown();
372     }
373
374     /**
375      * Sets whether the {@link CalendarView} is shown.
376      *
377      * @param shown True if the calendar view is to be shown.
378      */
379     public void setCalendarViewShown(boolean shown) {
380         mCalendarView.setVisibility(shown ? VISIBLE : GONE);
381     }
382
383     /**
384      * Gets whether the spinners are shown.
385      *
386      * @return True if the spinners are shown.
387      */
388     public boolean getSpinnersShown() {
389         return mSpinners.isShown();
390     }
391
392     /**
393      * Sets whether the spinners are shown.
394      *
395      * @param shown True if the spinners are to be shown.
396      */
397     public void setSpinnersShown(boolean shown) {
398         mSpinners.setVisibility(shown ? VISIBLE : GONE);
399     }
400
401     /**
402      * Reorders the spinners according to the date format in the current
403      * {@link Locale}.
404      */
405     private void reorderSpinners() {
406         java.text.DateFormat format;
407         String order;
408
409         /*
410          * If the user is in a locale where the medium date format is still
411          * numeric (Japanese and Czech, for example), respect the date format
412          * order setting. Otherwise, use the order that the locale says is
413          * appropriate for a spelled-out date.
414          */
415
416         if (getShortMonths()[0].startsWith("1")) {
417             format = DateFormat.getDateFormat(getContext());
418         } else {
419             format = DateFormat.getMediumDateFormat(getContext());
420         }
421
422         if (format instanceof SimpleDateFormat) {
423             order = ((SimpleDateFormat) format).toPattern();
424         } else {
425             // Shouldn't happen, but just in case.
426             order = new String(DateFormat.getDateFormatOrder(getContext()));
427         }
428
429         /*
430          * Remove the 3 spinners from their parent and then add them back in the
431          * required order.
432          */
433         LinearLayout parent = mSpinners;
434         parent.removeAllViews();
435
436         boolean quoted = false;
437         boolean didDay = false, didMonth = false, didYear = false;
438
439         for (int i = 0; i < order.length(); i++) {
440             char c = order.charAt(i);
441
442             if (c == '\'') {
443                 quoted = !quoted;
444             }
445
446             if (!quoted) {
447                 if (c == DateFormat.DATE && !didDay) {
448                     parent.addView(mDaySpinner);
449                     didDay = true;
450                 } else if ((c == DateFormat.MONTH || c == 'L') && !didMonth) {
451                     parent.addView(mMonthSpinner);
452                     didMonth = true;
453                 } else if (c == DateFormat.YEAR && !didYear) {
454                     parent.addView(mYearSpinner);
455                     didYear = true;
456                 }
457             }
458         }
459
460         // Shouldn't happen, but just in case.
461         if (!didMonth) {
462             parent.addView(mMonthSpinner);
463         }
464         if (!didDay) {
465             parent.addView(mDaySpinner);
466         }
467         if (!didYear) {
468             parent.addView(mYearSpinner);
469         }
470     }
471
472     /**
473      * Updates the current date.
474      *
475      * @param year The year.
476      * @param month The month which is <strong>starting from zero</strong>.
477      * @param dayOfMonth The day of the month.
478      */
479     public void updateDate(int year, int month, int dayOfMonth) {
480         if (!isNewDate(year, month, dayOfMonth)) {
481             return;
482         }
483         setDate(year, month, dayOfMonth);
484         updateSpinners();
485         updateCalendarView();
486         notifyDateChanged();
487     }
488
489     // Override so we are in complete control of save / restore for this widget.
490     @Override
491     protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
492         dispatchThawSelfOnly(container);
493     }
494
495     @Override
496     protected Parcelable onSaveInstanceState() {
497         Parcelable superState = super.onSaveInstanceState();
498         return new SavedState(superState, getYear(), getMonth(), getDayOfMonth());
499     }
500
501     @Override
502     protected void onRestoreInstanceState(Parcelable state) {
503         SavedState ss = (SavedState) state;
504         super.onRestoreInstanceState(ss.getSuperState());
505         setDate(ss.mYear, ss.mMonth, ss.mDay);
506         updateSpinners();
507         updateCalendarView();
508     }
509
510     /**
511      * Initialize the state. If the provided values designate an inconsistent
512      * date the values are normalized before updating the spinners.
513      *
514      * @param year The initial year.
515      * @param monthOfYear The initial month <strong>starting from zero</strong>.
516      * @param dayOfMonth The initial day of the month.
517      * @param onDateChangedListener How user is notified date is changed by
518      *            user, can be null.
519      */
520     public void init(int year, int monthOfYear, int dayOfMonth,
521             OnDateChangedListener onDateChangedListener) {
522         setDate(year, monthOfYear, dayOfMonth);
523         updateSpinners();
524         updateCalendarView();
525         mOnDateChangedListener = onDateChangedListener;
526     }
527
528     /**
529      * Parses the given <code>date</code> and in case of success sets the result
530      * to the <code>outDate</code>.
531      *
532      * @return True if the date was parsed.
533      */
534     private boolean parseDate(String date, Calendar outDate) {
535         try {
536             outDate.setTime(mDateFormat.parse(date));
537             return true;
538         } catch (ParseException e) {
539             Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT);
540             return false;
541         }
542     }
543
544     /**
545      * @return The short month abbreviations.
546      */
547     private String[] getShortMonths() {
548         final Locale currentLocale = Locale.getDefault();
549         if (currentLocale.equals(mMonthLocale)) {
550             return mShortMonths;
551         } else {
552             for (int i = 0; i < mNumberOfMonths; i++) {
553                 mShortMonths[i] = DateUtils.getMonthString(Calendar.JANUARY + i,
554                         DateUtils.LENGTH_MEDIUM);
555             }
556             mMonthLocale = currentLocale;
557             return mShortMonths;
558         }
559     }
560
561     private boolean isNewDate(int year, int month, int dayOfMonth) {
562         return (mCurrentDate.get(Calendar.YEAR) != year
563                 || mCurrentDate.get(Calendar.MONTH) != dayOfMonth
564                 || mCurrentDate.get(Calendar.DAY_OF_MONTH) != month);
565     }
566
567     private void setDate(int year, int month, int dayOfMonth) {
568         mCurrentDate.set(year, month, dayOfMonth);
569         if (mCurrentDate.before(mMinDate)) {
570             mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
571         } else if (mCurrentDate.after(mMaxDate)) {
572             mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
573         }
574     }
575
576     private void updateSpinners() {
577         // set the spinner ranges respecting the min and max dates
578         if (mCurrentDate.equals(mMinDate)) {
579             mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
580             mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
581             mDaySpinner.setWrapSelectorWheel(false);
582             mMonthSpinner.setDisplayedValues(null);
583             mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH));
584             mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH));
585             mMonthSpinner.setWrapSelectorWheel(false);
586         } else if (mCurrentDate.equals(mMaxDate)) {
587             mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH));
588             mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
589             mDaySpinner.setWrapSelectorWheel(false);
590             mMonthSpinner.setDisplayedValues(null);
591             mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH));
592             mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH));
593             mMonthSpinner.setWrapSelectorWheel(false);
594         } else {
595             mDaySpinner.setMinValue(1);
596             mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
597             mDaySpinner.setWrapSelectorWheel(true);
598             mMonthSpinner.setDisplayedValues(null);
599             mMonthSpinner.setMinValue(0);
600             mMonthSpinner.setMaxValue(11);
601             mMonthSpinner.setWrapSelectorWheel(true);
602         }
603
604         // make sure the month names are a zero based array
605         // with the months in the month spinner
606         String[] displayedValues = Arrays.copyOfRange(getShortMonths(),
607                 mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1);
608         mMonthSpinner.setDisplayedValues(displayedValues);
609
610         // year spinner range does not change based on the current date
611         mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR));
612         mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR));
613         mYearSpinner.setWrapSelectorWheel(false);
614
615         // set the spinner values
616         mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR));
617         mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH));
618         mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
619     }
620
621     /**
622      * Updates the calendar view with the current date.
623      */
624     private void updateCalendarView() {
625          mCalendarView.setDate(mCurrentDate.getTimeInMillis(), false, false);
626     }
627
628     /**
629      * @return The selected year.
630      */
631     public int getYear() {
632         return mCurrentDate.get(Calendar.YEAR);
633     }
634
635     /**
636      * @return The selected month.
637      */
638     public int getMonth() {
639         return mCurrentDate.get(Calendar.MONTH);
640     }
641
642     /**
643      * @return The selected day of month.
644      */
645     public int getDayOfMonth() {
646         return mCurrentDate.get(Calendar.DAY_OF_MONTH);
647     }
648
649     /**
650      * Notifies the listener, if such, for a change in the selected date.
651      */
652     private void notifyDateChanged() {
653         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
654         if (mOnDateChangedListener != null) {
655             mOnDateChangedListener.onDateChanged(this, getYear(), getMonth(), getDayOfMonth());
656         }
657     }
658
659     /**
660      * Class for managing state storing/restoring.
661      */
662     private static class SavedState extends BaseSavedState {
663
664         private final int mYear;
665
666         private final int mMonth;
667
668         private final int mDay;
669
670         /**
671          * Constructor called from {@link DatePicker#onSaveInstanceState()}
672          */
673         private SavedState(Parcelable superState, int year, int month, int day) {
674             super(superState);
675             mYear = year;
676             mMonth = month;
677             mDay = day;
678         }
679
680         /**
681          * Constructor called from {@link #CREATOR}
682          */
683         private SavedState(Parcel in) {
684             super(in);
685             mYear = in.readInt();
686             mMonth = in.readInt();
687             mDay = in.readInt();
688         }
689
690         @Override
691         public void writeToParcel(Parcel dest, int flags) {
692             super.writeToParcel(dest, flags);
693             dest.writeInt(mYear);
694             dest.writeInt(mMonth);
695             dest.writeInt(mDay);
696         }
697
698         @SuppressWarnings("all")
699         // suppress unused and hiding
700         public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
701
702             public SavedState createFromParcel(Parcel in) {
703                 return new SavedState(in);
704             }
705
706             public SavedState[] newArray(int size) {
707                 return new SavedState[size];
708             }
709         };
710     }
711 }