OSDN Git Service

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