OSDN Git Service

am 5c9d0de1: am 545dd853: am 9a425aa7: am 159aa735: am d4031809: am 48c95c43: Fix...
[android-x86/frameworks-base.git] / core / java / android / widget / DatePickerCalendarDelegate.java
1 /*
2  * Copyright (C) 2014 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.content.Context;
20 import android.content.res.ColorStateList;
21 import android.content.res.Configuration;
22 import android.content.res.Resources;
23 import android.content.res.TypedArray;
24 import android.graphics.drawable.Drawable;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 import android.text.format.DateFormat;
28 import android.text.format.DateUtils;
29 import android.util.AttributeSet;
30 import android.util.SparseArray;
31 import android.view.HapticFeedbackConstants;
32 import android.view.LayoutInflater;
33 import android.view.View;
34 import android.view.accessibility.AccessibilityEvent;
35 import android.view.accessibility.AccessibilityNodeInfo;
36 import android.view.animation.AlphaAnimation;
37 import android.view.animation.Animation;
38
39 import com.android.internal.R;
40 import com.android.internal.widget.AccessibleDateAnimator;
41
42 import java.text.SimpleDateFormat;
43 import java.util.Calendar;
44 import java.util.HashSet;
45 import java.util.Locale;
46
47 /**
48  * A delegate for picking up a date (day / month / year).
49  */
50 class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate implements
51         View.OnClickListener, DatePickerController {
52     private static final int USE_LOCALE = 0;
53
54     private static final int UNINITIALIZED = -1;
55     private static final int MONTH_AND_DAY_VIEW = 0;
56     private static final int YEAR_VIEW = 1;
57
58     private static final int DEFAULT_START_YEAR = 1900;
59     private static final int DEFAULT_END_YEAR = 2100;
60
61     private static final int ANIMATION_DURATION = 300;
62
63     private static final int MONTH_INDEX = 0;
64     private static final int DAY_INDEX = 1;
65     private static final int YEAR_INDEX = 2;
66
67     private SimpleDateFormat mYearFormat = new SimpleDateFormat("y", Locale.getDefault());
68     private SimpleDateFormat mDayFormat = new SimpleDateFormat("d", Locale.getDefault());
69
70     private TextView mDayOfWeekView;
71
72     /** Layout that contains the current month, day, and year. */
73     private LinearLayout mMonthDayYearLayout;
74
75     /** Clickable layout that contains the current day and year. */
76     private LinearLayout mMonthAndDayLayout;
77
78     private TextView mHeaderMonthTextView;
79     private TextView mHeaderDayOfMonthTextView;
80     private TextView mHeaderYearTextView;
81     private DayPickerView mDayPickerView;
82     private YearPickerView mYearPickerView;
83
84     private boolean mIsEnabled = true;
85
86     // Accessibility strings.
87     private String mDayPickerDescription;
88     private String mSelectDay;
89     private String mYearPickerDescription;
90     private String mSelectYear;
91
92     private AccessibleDateAnimator mAnimator;
93
94     private DatePicker.OnDateChangedListener mDateChangedListener;
95
96     private int mCurrentView = UNINITIALIZED;
97
98     private Calendar mCurrentDate;
99     private Calendar mTempDate;
100     private Calendar mMinDate;
101     private Calendar mMaxDate;
102
103     private int mFirstDayOfWeek = USE_LOCALE;
104
105     private HashSet<OnDateChangedListener> mListeners = new HashSet<OnDateChangedListener>();
106
107     public DatePickerCalendarDelegate(DatePicker delegator, Context context, AttributeSet attrs,
108             int defStyleAttr, int defStyleRes) {
109         super(delegator, context);
110
111         final Locale locale = Locale.getDefault();
112         mMinDate = getCalendarForLocale(mMinDate, locale);
113         mMaxDate = getCalendarForLocale(mMaxDate, locale);
114         mTempDate = getCalendarForLocale(mMaxDate, locale);
115         mCurrentDate = getCalendarForLocale(mCurrentDate, locale);
116
117         mMinDate.set(DEFAULT_START_YEAR, 1, 1);
118         mMaxDate.set(DEFAULT_END_YEAR, 12, 31);
119
120         final Resources res = mDelegator.getResources();
121         final TypedArray a = mContext.obtainStyledAttributes(attrs,
122                 R.styleable.DatePicker, defStyleAttr, defStyleRes);
123         final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
124                 Context.LAYOUT_INFLATER_SERVICE);
125         final int layoutResourceId = a.getResourceId(
126                 R.styleable.DatePicker_internalLayout, R.layout.date_picker_holo);
127         final View mainView = inflater.inflate(layoutResourceId, null);
128         mDelegator.addView(mainView);
129
130         mDayOfWeekView = (TextView) mainView.findViewById(R.id.date_picker_header);
131
132         // Layout that contains the current date and day name header.
133         final LinearLayout dateLayout = (LinearLayout) mainView.findViewById(
134                 R.id.day_picker_selector_layout);
135         mMonthDayYearLayout = (LinearLayout) mainView.findViewById(
136                 R.id.date_picker_month_day_year_layout);
137         mMonthAndDayLayout = (LinearLayout) mainView.findViewById(
138                 R.id.date_picker_month_and_day_layout);
139         mMonthAndDayLayout.setOnClickListener(this);
140         mHeaderMonthTextView = (TextView) mainView.findViewById(R.id.date_picker_month);
141         mHeaderDayOfMonthTextView = (TextView) mainView.findViewById(R.id.date_picker_day);
142         mHeaderYearTextView = (TextView) mainView.findViewById(R.id.date_picker_year);
143         mHeaderYearTextView.setOnClickListener(this);
144
145         // Obtain default highlight color from the theme.
146         final int defaultHighlightColor = mHeaderYearTextView.getHighlightColor();
147
148         // Use Theme attributes if possible
149         final int dayOfWeekTextAppearanceResId = a.getResourceId(
150                 R.styleable.DatePicker_dayOfWeekTextAppearance, -1);
151         if (dayOfWeekTextAppearanceResId != -1) {
152             mDayOfWeekView.setTextAppearance(context, dayOfWeekTextAppearanceResId);
153         }
154
155         mDayOfWeekView.setBackground(a.getDrawable(R.styleable.DatePicker_dayOfWeekBackground));
156
157         dateLayout.setBackground(a.getDrawable(R.styleable.DatePicker_headerBackground));
158
159         final int headerSelectedTextColor = a.getColor(
160                 R.styleable.DatePicker_headerSelectedTextColor, defaultHighlightColor);
161         final int monthTextAppearanceResId = a.getResourceId(
162                 R.styleable.DatePicker_headerMonthTextAppearance, -1);
163         if (monthTextAppearanceResId != -1) {
164             mHeaderMonthTextView.setTextAppearance(context, monthTextAppearanceResId);
165         }
166         mHeaderMonthTextView.setTextColor(ColorStateList.addFirstIfMissing(
167                 mHeaderMonthTextView.getTextColors(), R.attr.state_selected,
168                 headerSelectedTextColor));
169
170         final int dayOfMonthTextAppearanceResId = a.getResourceId(
171                 R.styleable.DatePicker_headerDayOfMonthTextAppearance, -1);
172         if (dayOfMonthTextAppearanceResId != -1) {
173             mHeaderDayOfMonthTextView.setTextAppearance(context, dayOfMonthTextAppearanceResId);
174         }
175         mHeaderDayOfMonthTextView.setTextColor(ColorStateList.addFirstIfMissing(
176                 mHeaderDayOfMonthTextView.getTextColors(), R.attr.state_selected,
177                 headerSelectedTextColor));
178
179         final int yearTextAppearanceResId = a.getResourceId(
180                 R.styleable.DatePicker_headerYearTextAppearance, -1);
181         if (yearTextAppearanceResId != -1) {
182             mHeaderYearTextView.setTextAppearance(context, yearTextAppearanceResId);
183         }
184         mHeaderYearTextView.setTextColor(ColorStateList.addFirstIfMissing(
185                 mHeaderYearTextView.getTextColors(), R.attr.state_selected,
186                 headerSelectedTextColor));
187
188         mDayPickerView = new DayPickerView(mContext, this);
189         mYearPickerView = new YearPickerView(mContext);
190         mYearPickerView.init(this);
191
192         final int yearSelectedCircleColor = a.getColor(R.styleable.DatePicker_yearListSelectorColor,
193                 defaultHighlightColor);
194         mYearPickerView.setYearSelectedCircleColor(yearSelectedCircleColor);
195
196         final ColorStateList calendarTextColor = a.getColorStateList(
197                 R.styleable.DatePicker_calendarTextColor);
198         final int calendarSelectedTextColor = a.getColor(
199                 R.styleable.DatePicker_calendarSelectedTextColor, defaultHighlightColor);
200         mDayPickerView.setCalendarTextColor(ColorStateList.addFirstIfMissing(
201                 calendarTextColor, R.attr.state_selected, calendarSelectedTextColor));
202
203         mDayPickerDescription = res.getString(R.string.day_picker_description);
204         mSelectDay = res.getString(R.string.select_day);
205         mYearPickerDescription = res.getString(R.string.year_picker_description);
206         mSelectYear = res.getString(R.string.select_year);
207
208         mAnimator = (AccessibleDateAnimator) mainView.findViewById(R.id.animator);
209         mAnimator.addView(mDayPickerView);
210         mAnimator.addView(mYearPickerView);
211         mAnimator.setDateMillis(mCurrentDate.getTimeInMillis());
212
213         final Animation animation = new AlphaAnimation(0.0f, 1.0f);
214         animation.setDuration(ANIMATION_DURATION);
215         mAnimator.setInAnimation(animation);
216
217         final Animation animation2 = new AlphaAnimation(1.0f, 0.0f);
218         animation2.setDuration(ANIMATION_DURATION);
219         mAnimator.setOutAnimation(animation2);
220
221         updateDisplay(false);
222         setCurrentView(MONTH_AND_DAY_VIEW);
223     }
224
225     /**
226      * Gets a calendar for locale bootstrapped with the value of a given calendar.
227      *
228      * @param oldCalendar The old calendar.
229      * @param locale The locale.
230      */
231     private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
232         if (oldCalendar == null) {
233             return Calendar.getInstance(locale);
234         } else {
235             final long currentTimeMillis = oldCalendar.getTimeInMillis();
236             Calendar newCalendar = Calendar.getInstance(locale);
237             newCalendar.setTimeInMillis(currentTimeMillis);
238             return newCalendar;
239         }
240     }
241
242     /**
243      * Compute the array representing the order of Month / Day / Year views in their layout.
244      * Will be used for I18N purpose as the order of them depends on the Locale.
245      */
246     private int[] getMonthDayYearIndexes(String pattern) {
247         int[] result = new int[3];
248
249         final String filteredPattern = pattern.replaceAll("'.*?'", "");
250
251         final int dayIndex = filteredPattern.indexOf('d');
252         final int monthMIndex = filteredPattern.indexOf("M");
253         final int monthIndex = (monthMIndex != -1) ? monthMIndex : filteredPattern.indexOf("L");
254         final int yearIndex = filteredPattern.indexOf("y");
255
256         if (yearIndex < monthIndex) {
257             result[YEAR_INDEX] = 0;
258
259             if (monthIndex < dayIndex) {
260                 result[MONTH_INDEX] = 1;
261                 result[DAY_INDEX] = 2;
262             } else {
263                 result[MONTH_INDEX] = 2;
264                 result[DAY_INDEX] = 1;
265             }
266         } else {
267             result[YEAR_INDEX] = 2;
268
269             if (monthIndex < dayIndex) {
270                 result[MONTH_INDEX] = 0;
271                 result[DAY_INDEX] = 1;
272             } else {
273                 result[MONTH_INDEX] = 1;
274                 result[DAY_INDEX] = 0;
275             }
276         }
277         return result;
278     }
279
280     private void updateDisplay(boolean announce) {
281         if (mDayOfWeekView != null) {
282             mDayOfWeekView.setText(mCurrentDate.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG,
283                     Locale.getDefault()));
284         }
285
286         // Compute indices of Month, Day and Year views
287         final String bestDateTimePattern =
288                 DateFormat.getBestDateTimePattern(mCurrentLocale, "yMMMd");
289         final int[] viewIndices = getMonthDayYearIndexes(bestDateTimePattern);
290
291         // Position the Year and MonthAndDay views within the header.
292         mMonthDayYearLayout.removeAllViews();
293         if (viewIndices[YEAR_INDEX] == 0) {
294             mMonthDayYearLayout.addView(mHeaderYearTextView);
295             mMonthDayYearLayout.addView(mMonthAndDayLayout);
296         } else {
297             mMonthDayYearLayout.addView(mMonthAndDayLayout);
298             mMonthDayYearLayout.addView(mHeaderYearTextView);
299         }
300
301         // Position Day and Month views within the MonthAndDay view.
302         mMonthAndDayLayout.removeAllViews();
303         if (viewIndices[MONTH_INDEX] > viewIndices[DAY_INDEX]) {
304             mMonthAndDayLayout.addView(mHeaderDayOfMonthTextView);
305             mMonthAndDayLayout.addView(mHeaderMonthTextView);
306         } else {
307             mMonthAndDayLayout.addView(mHeaderMonthTextView);
308             mMonthAndDayLayout.addView(mHeaderDayOfMonthTextView);
309         }
310
311         mHeaderMonthTextView.setText(mCurrentDate.getDisplayName(Calendar.MONTH, Calendar.SHORT,
312                 Locale.getDefault()).toUpperCase(Locale.getDefault()));
313         mHeaderDayOfMonthTextView.setText(mDayFormat.format(mCurrentDate.getTime()));
314         mHeaderYearTextView.setText(mYearFormat.format(mCurrentDate.getTime()));
315
316         // Accessibility.
317         long millis = mCurrentDate.getTimeInMillis();
318         mAnimator.setDateMillis(millis);
319         int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR;
320         String monthAndDayText = DateUtils.formatDateTime(mContext, millis, flags);
321         mMonthAndDayLayout.setContentDescription(monthAndDayText);
322
323         if (announce) {
324             flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
325             String fullDateText = DateUtils.formatDateTime(mContext, millis, flags);
326             mAnimator.announceForAccessibility(fullDateText);
327         }
328         updatePickers();
329     }
330
331     private void setCurrentView(final int viewIndex) {
332         long millis = mCurrentDate.getTimeInMillis();
333
334         switch (viewIndex) {
335             case MONTH_AND_DAY_VIEW:
336                 mDayPickerView.onDateChanged();
337                 if (mCurrentView != viewIndex) {
338                     mMonthAndDayLayout.setSelected(true);
339                     mHeaderYearTextView.setSelected(false);
340                     mAnimator.setDisplayedChild(MONTH_AND_DAY_VIEW);
341                     mCurrentView = viewIndex;
342                 }
343
344                 final int flags = DateUtils.FORMAT_SHOW_DATE;
345                 final String dayString = DateUtils.formatDateTime(mContext, millis, flags);
346                 mAnimator.setContentDescription(mDayPickerDescription + ": " + dayString);
347                 mAnimator.announceForAccessibility(mSelectDay);
348                 break;
349             case YEAR_VIEW:
350                 mYearPickerView.onDateChanged();
351                 if (mCurrentView != viewIndex) {
352                     mMonthAndDayLayout.setSelected(false);
353                     mHeaderYearTextView.setSelected(true);
354                     mAnimator.setDisplayedChild(YEAR_VIEW);
355                     mCurrentView = viewIndex;
356                 }
357
358                 final CharSequence yearString = mYearFormat.format(millis);
359                 mAnimator.setContentDescription(mYearPickerDescription + ": " + yearString);
360                 mAnimator.announceForAccessibility(mSelectYear);
361                 break;
362         }
363     }
364
365     @Override
366     public void init(int year, int monthOfYear, int dayOfMonth,
367             DatePicker.OnDateChangedListener callBack) {
368         mDateChangedListener = callBack;
369         mCurrentDate.set(Calendar.YEAR, year);
370         mCurrentDate.set(Calendar.MONTH, monthOfYear);
371         mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
372         updateDisplay(false);
373     }
374
375     @Override
376     public void updateDate(int year, int month, int dayOfMonth) {
377         mCurrentDate.set(Calendar.YEAR, year);
378         mCurrentDate.set(Calendar.MONTH, month);
379         mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
380         if (mDateChangedListener != null) {
381             mDateChangedListener.onDateChanged(mDelegator, year, month, dayOfMonth);
382         }
383         updateDisplay(false);
384     }
385
386     @Override
387     public int getYear() {
388         return mCurrentDate.get(Calendar.YEAR);
389     }
390
391     @Override
392     public int getMonth() {
393         return mCurrentDate.get(Calendar.MONTH);
394     }
395
396     @Override
397     public int getDayOfMonth() {
398         return mCurrentDate.get(Calendar.DAY_OF_MONTH);
399     }
400
401     @Override
402     public void setMinDate(long minDate) {
403         mTempDate.setTimeInMillis(minDate);
404         if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
405                 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) {
406             return;
407         }
408         if (mCurrentDate.before(mTempDate)) {
409             mCurrentDate.setTimeInMillis(minDate);
410             updatePickers();
411             updateDisplay(false);
412         }
413         mMinDate.setTimeInMillis(minDate);
414         mDayPickerView.goTo(getSelectedDay(), false, true, true);
415     }
416
417     @Override
418     public Calendar getMinDate() {
419         return mMinDate;
420     }
421
422     @Override
423     public void setMaxDate(long maxDate) {
424         mTempDate.setTimeInMillis(maxDate);
425         if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
426                 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) {
427             return;
428         }
429         if (mCurrentDate.after(mTempDate)) {
430             mCurrentDate.setTimeInMillis(maxDate);
431             updatePickers();
432             updateDisplay(false);
433         }
434         mMaxDate.setTimeInMillis(maxDate);
435         mDayPickerView.goTo(getSelectedDay(), false, true, true);
436     }
437
438     @Override
439     public Calendar getMaxDate() {
440         return mMaxDate;
441     }
442
443     @Override
444     public void setFirstDayOfWeek(int firstDayOfWeek) {
445         mFirstDayOfWeek = firstDayOfWeek;
446     }
447
448     @Override
449     public int getFirstDayOfWeek() {
450         if (mFirstDayOfWeek != USE_LOCALE) {
451             return mFirstDayOfWeek;
452         }
453         return mCurrentDate.getFirstDayOfWeek();
454     }
455
456     @Override
457     public int getMinYear() {
458         return mMinDate.get(Calendar.YEAR);
459     }
460
461     @Override
462     public int getMaxYear() {
463         return mMaxDate.get(Calendar.YEAR);
464     }
465
466     @Override
467     public int getMinMonth() {
468         return mMinDate.get(Calendar.MONTH);
469     }
470
471     @Override
472     public int getMaxMonth() {
473         return mMaxDate.get(Calendar.MONTH);
474     }
475
476     @Override
477     public int getMinDay() {
478         return mMinDate.get(Calendar.DAY_OF_MONTH);
479     }
480
481     @Override
482     public int getMaxDay() {
483         return mMaxDate.get(Calendar.DAY_OF_MONTH);
484     }
485
486     @Override
487     public void setEnabled(boolean enabled) {
488         mMonthAndDayLayout.setEnabled(enabled);
489         mHeaderYearTextView.setEnabled(enabled);
490         mAnimator.setEnabled(enabled);
491         mIsEnabled = enabled;
492     }
493
494     @Override
495     public boolean isEnabled() {
496         return mIsEnabled;
497     }
498
499     @Override
500     public CalendarView getCalendarView() {
501         throw new UnsupportedOperationException(
502                 "CalendarView does not exists for the new DatePicker");
503     }
504
505     @Override
506     public void setCalendarViewShown(boolean shown) {
507         // No-op for compatibility with the old DatePicker.
508     }
509
510     @Override
511     public boolean getCalendarViewShown() {
512         return false;
513     }
514
515     @Override
516     public void setSpinnersShown(boolean shown) {
517         // No-op for compatibility with the old DatePicker.
518     }
519
520     @Override
521     public boolean getSpinnersShown() {
522         return false;
523     }
524
525     @Override
526     public void onConfigurationChanged(Configuration newConfig) {
527         mYearFormat = new SimpleDateFormat("y", newConfig.locale);
528         mDayFormat = new SimpleDateFormat("d", newConfig.locale);
529     }
530
531     @Override
532     public Parcelable onSaveInstanceState(Parcelable superState) {
533         final int year = mCurrentDate.get(Calendar.YEAR);
534         final int month = mCurrentDate.get(Calendar.MONTH);
535         final int day = mCurrentDate.get(Calendar.DAY_OF_MONTH);
536
537         int listPosition = -1;
538         int listPositionOffset = -1;
539
540         if (mCurrentView == MONTH_AND_DAY_VIEW) {
541             listPosition = mDayPickerView.getMostVisiblePosition();
542         } else if (mCurrentView == YEAR_VIEW) {
543             listPosition = mYearPickerView.getFirstVisiblePosition();
544             listPositionOffset = mYearPickerView.getFirstPositionOffset();
545         }
546
547         return new SavedState(superState, year, month, day, mMinDate.getTimeInMillis(),
548                 mMaxDate.getTimeInMillis(), mCurrentView, listPosition, listPositionOffset);
549     }
550
551     @Override
552     public void onRestoreInstanceState(Parcelable state) {
553         SavedState ss = (SavedState) state;
554
555         mCurrentDate.set(ss.getSelectedYear(), ss.getSelectedMonth(), ss.getSelectedDay());
556         mCurrentView = ss.getCurrentView();
557         mMinDate.setTimeInMillis(ss.getMinDate());
558         mMaxDate.setTimeInMillis(ss.getMaxDate());
559
560         updateDisplay(false);
561         setCurrentView(mCurrentView);
562
563         final int listPosition = ss.getListPosition();
564         if (listPosition != -1) {
565             if (mCurrentView == MONTH_AND_DAY_VIEW) {
566                 mDayPickerView.postSetSelection(listPosition);
567             } else if (mCurrentView == YEAR_VIEW) {
568                 mYearPickerView.postSetSelectionFromTop(listPosition, ss.getListPositionOffset());
569             }
570         }
571     }
572
573     @Override
574     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
575         onPopulateAccessibilityEvent(event);
576         return true;
577     }
578
579     @Override
580     public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
581         event.getText().add(mCurrentDate.getTime().toString());
582     }
583
584     @Override
585     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
586         event.setClassName(DatePicker.class.getName());
587     }
588
589     @Override
590     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
591         info.setClassName(DatePicker.class.getName());
592     }
593
594     @Override
595     public void onYearSelected(int year) {
596         adjustDayInMonthIfNeeded(mCurrentDate.get(Calendar.MONTH), year);
597         mCurrentDate.set(Calendar.YEAR, year);
598         updatePickers();
599         setCurrentView(MONTH_AND_DAY_VIEW);
600         updateDisplay(true);
601     }
602
603     // If the newly selected month / year does not contain the currently selected day number,
604     // change the selected day number to the last day of the selected month or year.
605     //      e.g. Switching from Mar to Apr when Mar 31 is selected -> Apr 30
606     //      e.g. Switching from 2012 to 2013 when Feb 29, 2012 is selected -> Feb 28, 2013
607     private void adjustDayInMonthIfNeeded(int month, int year) {
608         int day = mCurrentDate.get(Calendar.DAY_OF_MONTH);
609         int daysInMonth = getDaysInMonth(month, year);
610         if (day > daysInMonth) {
611             mCurrentDate.set(Calendar.DAY_OF_MONTH, daysInMonth);
612         }
613     }
614
615     public static int getDaysInMonth(int month, int year) {
616         switch (month) {
617             case Calendar.JANUARY:
618             case Calendar.MARCH:
619             case Calendar.MAY:
620             case Calendar.JULY:
621             case Calendar.AUGUST:
622             case Calendar.OCTOBER:
623             case Calendar.DECEMBER:
624                 return 31;
625             case Calendar.APRIL:
626             case Calendar.JUNE:
627             case Calendar.SEPTEMBER:
628             case Calendar.NOVEMBER:
629                 return 30;
630             case Calendar.FEBRUARY:
631                 return (year % 4 == 0) ? 29 : 28;
632             default:
633                 throw new IllegalArgumentException("Invalid Month");
634         }
635     }
636
637     @Override
638     public void onDayOfMonthSelected(int year, int month, int day) {
639         mCurrentDate.set(Calendar.YEAR, year);
640         mCurrentDate.set(Calendar.MONTH, month);
641         mCurrentDate.set(Calendar.DAY_OF_MONTH, day);
642         updatePickers();
643         updateDisplay(true);
644     }
645
646     private void updatePickers() {
647         for (OnDateChangedListener listener : mListeners) {
648             listener.onDateChanged();
649         }
650     }
651
652     @Override
653     public void registerOnDateChangedListener(OnDateChangedListener listener) {
654         mListeners.add(listener);
655     }
656
657     @Override
658     public void unregisterOnDateChangedListener(OnDateChangedListener listener) {
659         mListeners.remove(listener);
660     }
661
662     @Override
663     public Calendar getSelectedDay() {
664         return mCurrentDate;
665     }
666
667     @Override
668     public void tryVibrate() {
669         mDelegator.performHapticFeedback(HapticFeedbackConstants.CALENDAR_DATE);
670     }
671
672     @Override
673     public void onClick(View v) {
674         tryVibrate();
675         if (v.getId() == R.id.date_picker_year) {
676             setCurrentView(YEAR_VIEW);
677         } else if (v.getId() == R.id.date_picker_month_and_day_layout) {
678             setCurrentView(MONTH_AND_DAY_VIEW);
679         }
680     }
681
682     /**
683      * Class for managing state storing/restoring.
684      */
685     private static class SavedState extends View.BaseSavedState {
686
687         private final int mSelectedYear;
688         private final int mSelectedMonth;
689         private final int mSelectedDay;
690         private final long mMinDate;
691         private final long mMaxDate;
692         private final int mCurrentView;
693         private final int mListPosition;
694         private final int mListPositionOffset;
695
696         /**
697          * Constructor called from {@link DatePicker#onSaveInstanceState()}
698          */
699         private SavedState(Parcelable superState, int year, int month, int day,
700                 long minDate, long maxDate, int currentView, int listPosition,
701                 int listPositionOffset) {
702             super(superState);
703             mSelectedYear = year;
704             mSelectedMonth = month;
705             mSelectedDay = day;
706             mMinDate = minDate;
707             mMaxDate = maxDate;
708             mCurrentView = currentView;
709             mListPosition = listPosition;
710             mListPositionOffset = listPositionOffset;
711         }
712
713         /**
714          * Constructor called from {@link #CREATOR}
715          */
716         private SavedState(Parcel in) {
717             super(in);
718             mSelectedYear = in.readInt();
719             mSelectedMonth = in.readInt();
720             mSelectedDay = in.readInt();
721             mMinDate = in.readLong();
722             mMaxDate = in.readLong();
723             mCurrentView = in.readInt();
724             mListPosition = in.readInt();
725             mListPositionOffset = in.readInt();
726         }
727
728         @Override
729         public void writeToParcel(Parcel dest, int flags) {
730             super.writeToParcel(dest, flags);
731             dest.writeInt(mSelectedYear);
732             dest.writeInt(mSelectedMonth);
733             dest.writeInt(mSelectedDay);
734             dest.writeLong(mMinDate);
735             dest.writeLong(mMaxDate);
736             dest.writeInt(mCurrentView);
737             dest.writeInt(mListPosition);
738             dest.writeInt(mListPositionOffset);
739         }
740
741         public int getSelectedDay() {
742             return mSelectedDay;
743         }
744
745         public int getSelectedMonth() {
746             return mSelectedMonth;
747         }
748
749         public int getSelectedYear() {
750             return mSelectedYear;
751         }
752
753         public long getMinDate() {
754             return mMinDate;
755         }
756
757         public long getMaxDate() {
758             return mMaxDate;
759         }
760
761         public int getCurrentView() {
762             return mCurrentView;
763         }
764
765         public int getListPosition() {
766             return mListPosition;
767         }
768
769         public int getListPositionOffset() {
770             return mListPositionOffset;
771         }
772
773         @SuppressWarnings("all")
774         // suppress unused and hiding
775         public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
776
777             public SavedState createFromParcel(Parcel in) {
778                 return new SavedState(in);
779             }
780
781             public SavedState[] newArray(int size) {
782                 return new SavedState[size];
783             }
784         };
785     }
786 }