OSDN Git Service

am 0b596de9: am c6f70adf: am c84021ba: Merge "docs: link to sdk manager" into lmp...
[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
196         final int yearSelectedCircleColor = a.getColor(R.styleable.DatePicker_yearListSelectorColor,
197                 defaultHighlightColor);
198         mYearPickerView.setYearSelectedCircleColor(yearSelectedCircleColor);
199
200         final ColorStateList calendarTextColor = a.getColorStateList(
201                 R.styleable.DatePicker_calendarTextColor);
202         final int calendarSelectedTextColor = a.getColor(
203                 R.styleable.DatePicker_calendarSelectedTextColor, defaultHighlightColor);
204         mDayPickerView.setCalendarTextColor(ColorStateList.addFirstIfMissing(
205                 calendarTextColor, R.attr.state_selected, calendarSelectedTextColor));
206
207         mDayPickerDescription = res.getString(R.string.day_picker_description);
208         mSelectDay = res.getString(R.string.select_day);
209         mYearPickerDescription = res.getString(R.string.year_picker_description);
210         mSelectYear = res.getString(R.string.select_year);
211
212         mAnimator = (AccessibleDateAnimator) mainView.findViewById(R.id.animator);
213         mAnimator.addView(mDayPickerView);
214         mAnimator.addView(mYearPickerView);
215         mAnimator.setDateMillis(mCurrentDate.getTimeInMillis());
216
217         final Animation animation = new AlphaAnimation(0.0f, 1.0f);
218         animation.setDuration(ANIMATION_DURATION);
219         mAnimator.setInAnimation(animation);
220
221         final Animation animation2 = new AlphaAnimation(1.0f, 0.0f);
222         animation2.setDuration(ANIMATION_DURATION);
223         mAnimator.setOutAnimation(animation2);
224
225         updateDisplay(false);
226         setCurrentView(MONTH_AND_DAY_VIEW);
227     }
228
229     /**
230      * Gets a calendar for locale bootstrapped with the value of a given calendar.
231      *
232      * @param oldCalendar The old calendar.
233      * @param locale The locale.
234      */
235     private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
236         if (oldCalendar == null) {
237             return Calendar.getInstance(locale);
238         } else {
239             final long currentTimeMillis = oldCalendar.getTimeInMillis();
240             Calendar newCalendar = Calendar.getInstance(locale);
241             newCalendar.setTimeInMillis(currentTimeMillis);
242             return newCalendar;
243         }
244     }
245
246     /**
247      * Compute the array representing the order of Month / Day / Year views in their layout.
248      * Will be used for I18N purpose as the order of them depends on the Locale.
249      */
250     private int[] getMonthDayYearIndexes(String pattern) {
251         int[] result = new int[3];
252
253         final String filteredPattern = pattern.replaceAll("'.*?'", "");
254
255         final int dayIndex = filteredPattern.indexOf('d');
256         final int monthMIndex = filteredPattern.indexOf("M");
257         final int monthIndex = (monthMIndex != -1) ? monthMIndex : filteredPattern.indexOf("L");
258         final int yearIndex = filteredPattern.indexOf("y");
259
260         if (yearIndex < monthIndex) {
261             result[YEAR_INDEX] = 0;
262
263             if (monthIndex < dayIndex) {
264                 result[MONTH_INDEX] = 1;
265                 result[DAY_INDEX] = 2;
266             } else {
267                 result[MONTH_INDEX] = 2;
268                 result[DAY_INDEX] = 1;
269             }
270         } else {
271             result[YEAR_INDEX] = 2;
272
273             if (monthIndex < dayIndex) {
274                 result[MONTH_INDEX] = 0;
275                 result[DAY_INDEX] = 1;
276             } else {
277                 result[MONTH_INDEX] = 1;
278                 result[DAY_INDEX] = 0;
279             }
280         }
281         return result;
282     }
283
284     private void updateDisplay(boolean announce) {
285         if (mDayOfWeekView != null) {
286             mDayOfWeekView.setText(mCurrentDate.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG,
287                     Locale.getDefault()));
288         }
289
290         // Compute indices of Month, Day and Year views
291         final String bestDateTimePattern =
292                 DateFormat.getBestDateTimePattern(mCurrentLocale, "yMMMd");
293         final int[] viewIndices = getMonthDayYearIndexes(bestDateTimePattern);
294
295         // Position the Year and MonthAndDay views within the header.
296         mMonthDayYearLayout.removeAllViews();
297         if (viewIndices[YEAR_INDEX] == 0) {
298             mMonthDayYearLayout.addView(mHeaderYearTextView);
299             mMonthDayYearLayout.addView(mMonthAndDayLayout);
300         } else {
301             mMonthDayYearLayout.addView(mMonthAndDayLayout);
302             mMonthDayYearLayout.addView(mHeaderYearTextView);
303         }
304
305         // Position Day and Month views within the MonthAndDay view.
306         mMonthAndDayLayout.removeAllViews();
307         if (viewIndices[MONTH_INDEX] > viewIndices[DAY_INDEX]) {
308             mMonthAndDayLayout.addView(mHeaderDayOfMonthTextView);
309             mMonthAndDayLayout.addView(mHeaderMonthTextView);
310         } else {
311             mMonthAndDayLayout.addView(mHeaderMonthTextView);
312             mMonthAndDayLayout.addView(mHeaderDayOfMonthTextView);
313         }
314
315         mHeaderMonthTextView.setText(mCurrentDate.getDisplayName(Calendar.MONTH, Calendar.SHORT,
316                 Locale.getDefault()).toUpperCase(Locale.getDefault()));
317         mHeaderDayOfMonthTextView.setText(mDayFormat.format(mCurrentDate.getTime()));
318         mHeaderYearTextView.setText(mYearFormat.format(mCurrentDate.getTime()));
319
320         // Accessibility.
321         long millis = mCurrentDate.getTimeInMillis();
322         mAnimator.setDateMillis(millis);
323         int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR;
324         String monthAndDayText = DateUtils.formatDateTime(mContext, millis, flags);
325         mMonthAndDayLayout.setContentDescription(monthAndDayText);
326
327         if (announce) {
328             flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
329             String fullDateText = DateUtils.formatDateTime(mContext, millis, flags);
330             mAnimator.announceForAccessibility(fullDateText);
331         }
332     }
333
334     private void setCurrentView(final int viewIndex) {
335         long millis = mCurrentDate.getTimeInMillis();
336
337         switch (viewIndex) {
338             case MONTH_AND_DAY_VIEW:
339                 mDayPickerView.setDate(getSelectedDay().getTimeInMillis());
340                 if (mCurrentView != viewIndex) {
341                     mMonthAndDayLayout.setSelected(true);
342                     mHeaderYearTextView.setSelected(false);
343                     mAnimator.setDisplayedChild(MONTH_AND_DAY_VIEW);
344                     mCurrentView = viewIndex;
345                 }
346
347                 final int flags = DateUtils.FORMAT_SHOW_DATE;
348                 final String dayString = DateUtils.formatDateTime(mContext, millis, flags);
349                 mAnimator.setContentDescription(mDayPickerDescription + ": " + dayString);
350                 mAnimator.announceForAccessibility(mSelectDay);
351                 break;
352             case YEAR_VIEW:
353                 mYearPickerView.onDateChanged();
354                 if (mCurrentView != viewIndex) {
355                     mMonthAndDayLayout.setSelected(false);
356                     mHeaderYearTextView.setSelected(true);
357                     mAnimator.setDisplayedChild(YEAR_VIEW);
358                     mCurrentView = viewIndex;
359                 }
360
361                 final CharSequence yearString = mYearFormat.format(millis);
362                 mAnimator.setContentDescription(mYearPickerDescription + ": " + yearString);
363                 mAnimator.announceForAccessibility(mSelectYear);
364                 break;
365         }
366     }
367
368     @Override
369     public void init(int year, int monthOfYear, int dayOfMonth,
370             DatePicker.OnDateChangedListener callBack) {
371         mCurrentDate.set(Calendar.YEAR, year);
372         mCurrentDate.set(Calendar.MONTH, monthOfYear);
373         mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
374
375         mDateChangedListener = callBack;
376
377         onDateChanged(false, false);
378     }
379
380     @Override
381     public void updateDate(int year, int month, int dayOfMonth) {
382         mCurrentDate.set(Calendar.YEAR, year);
383         mCurrentDate.set(Calendar.MONTH, month);
384         mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
385
386         onDateChanged(false, true);
387     }
388
389     private void onDateChanged(boolean fromUser, boolean callbackToClient) {
390         if (callbackToClient && mDateChangedListener != null) {
391             final int year = mCurrentDate.get(Calendar.YEAR);
392             final int monthOfYear = mCurrentDate.get(Calendar.MONTH);
393             final int dayOfMonth = mCurrentDate.get(Calendar.DAY_OF_MONTH);
394             mDateChangedListener.onDateChanged(mDelegator, year, monthOfYear, dayOfMonth);
395         }
396
397         for (OnDateChangedListener listener : mListeners) {
398             listener.onDateChanged();
399         }
400
401         mDayPickerView.setDate(getSelectedDay().getTimeInMillis());
402
403         updateDisplay(fromUser);
404
405         if (fromUser) {
406             tryVibrate();
407         }
408     }
409
410     @Override
411     public int getYear() {
412         return mCurrentDate.get(Calendar.YEAR);
413     }
414
415     @Override
416     public int getMonth() {
417         return mCurrentDate.get(Calendar.MONTH);
418     }
419
420     @Override
421     public int getDayOfMonth() {
422         return mCurrentDate.get(Calendar.DAY_OF_MONTH);
423     }
424
425     @Override
426     public void setMinDate(long minDate) {
427         mTempDate.setTimeInMillis(minDate);
428         if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
429                 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) {
430             return;
431         }
432         if (mCurrentDate.before(mTempDate)) {
433             mCurrentDate.setTimeInMillis(minDate);
434             onDateChanged(false, true);
435         }
436         mMinDate.setTimeInMillis(minDate);
437         mDayPickerView.setMinDate(minDate);
438         mYearPickerView.setRange(mMinDate, mMaxDate);
439     }
440
441     @Override
442     public Calendar getMinDate() {
443         return mMinDate;
444     }
445
446     @Override
447     public void setMaxDate(long maxDate) {
448         mTempDate.setTimeInMillis(maxDate);
449         if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
450                 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) {
451             return;
452         }
453         if (mCurrentDate.after(mTempDate)) {
454             mCurrentDate.setTimeInMillis(maxDate);
455             onDateChanged(false, true);
456         }
457         mMaxDate.setTimeInMillis(maxDate);
458         mDayPickerView.setMaxDate(maxDate);
459         mYearPickerView.setRange(mMinDate, mMaxDate);
460     }
461
462     @Override
463     public Calendar getMaxDate() {
464         return mMaxDate;
465     }
466
467     @Override
468     public void setFirstDayOfWeek(int firstDayOfWeek) {
469         mFirstDayOfWeek = firstDayOfWeek;
470
471         mDayPickerView.setFirstDayOfWeek(firstDayOfWeek);
472     }
473
474     @Override
475     public int getFirstDayOfWeek() {
476         if (mFirstDayOfWeek != USE_LOCALE) {
477             return mFirstDayOfWeek;
478         }
479         return mCurrentDate.getFirstDayOfWeek();
480     }
481
482     @Override
483     public void setEnabled(boolean enabled) {
484         mMonthAndDayLayout.setEnabled(enabled);
485         mHeaderYearTextView.setEnabled(enabled);
486         mAnimator.setEnabled(enabled);
487         mIsEnabled = enabled;
488     }
489
490     @Override
491     public boolean isEnabled() {
492         return mIsEnabled;
493     }
494
495     @Override
496     public CalendarView getCalendarView() {
497         throw new UnsupportedOperationException(
498                 "CalendarView does not exists for the new DatePicker");
499     }
500
501     @Override
502     public void setCalendarViewShown(boolean shown) {
503         // No-op for compatibility with the old DatePicker.
504     }
505
506     @Override
507     public boolean getCalendarViewShown() {
508         return false;
509     }
510
511     @Override
512     public void setSpinnersShown(boolean shown) {
513         // No-op for compatibility with the old DatePicker.
514     }
515
516     @Override
517     public boolean getSpinnersShown() {
518         return false;
519     }
520
521     @Override
522     public void onConfigurationChanged(Configuration newConfig) {
523         mYearFormat = new SimpleDateFormat("y", newConfig.locale);
524         mDayFormat = new SimpleDateFormat("d", newConfig.locale);
525     }
526
527     @Override
528     public Parcelable onSaveInstanceState(Parcelable superState) {
529         final int year = mCurrentDate.get(Calendar.YEAR);
530         final int month = mCurrentDate.get(Calendar.MONTH);
531         final int day = mCurrentDate.get(Calendar.DAY_OF_MONTH);
532
533         int listPosition = -1;
534         int listPositionOffset = -1;
535
536         if (mCurrentView == MONTH_AND_DAY_VIEW) {
537             listPosition = mDayPickerView.getMostVisiblePosition();
538         } else if (mCurrentView == YEAR_VIEW) {
539             listPosition = mYearPickerView.getFirstVisiblePosition();
540             listPositionOffset = mYearPickerView.getFirstPositionOffset();
541         }
542
543         return new SavedState(superState, year, month, day, mMinDate.getTimeInMillis(),
544                 mMaxDate.getTimeInMillis(), mCurrentView, listPosition, listPositionOffset);
545     }
546
547     @Override
548     public void onRestoreInstanceState(Parcelable state) {
549         SavedState ss = (SavedState) state;
550
551         mCurrentDate.set(ss.getSelectedYear(), ss.getSelectedMonth(), ss.getSelectedDay());
552         mCurrentView = ss.getCurrentView();
553         mMinDate.setTimeInMillis(ss.getMinDate());
554         mMaxDate.setTimeInMillis(ss.getMaxDate());
555
556         updateDisplay(false);
557         setCurrentView(mCurrentView);
558
559         final int listPosition = ss.getListPosition();
560         if (listPosition != -1) {
561             if (mCurrentView == MONTH_AND_DAY_VIEW) {
562                 mDayPickerView.postSetSelection(listPosition);
563             } else if (mCurrentView == YEAR_VIEW) {
564                 mYearPickerView.postSetSelectionFromTop(listPosition, ss.getListPositionOffset());
565             }
566         }
567     }
568
569     @Override
570     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
571         onPopulateAccessibilityEvent(event);
572         return true;
573     }
574
575     @Override
576     public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
577         event.getText().add(mCurrentDate.getTime().toString());
578     }
579
580     @Override
581     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
582         event.setClassName(DatePicker.class.getName());
583     }
584
585     @Override
586     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
587         info.setClassName(DatePicker.class.getName());
588     }
589
590     @Override
591     public void onYearSelected(int year) {
592         adjustDayInMonthIfNeeded(mCurrentDate.get(Calendar.MONTH), year);
593         mCurrentDate.set(Calendar.YEAR, year);
594         onDateChanged(true, true);
595
596         // Auto-advance to month and day view.
597         setCurrentView(MONTH_AND_DAY_VIEW);
598     }
599
600     // If the newly selected month / year does not contain the currently selected day number,
601     // change the selected day number to the last day of the selected month or year.
602     //      e.g. Switching from Mar to Apr when Mar 31 is selected -> Apr 30
603     //      e.g. Switching from 2012 to 2013 when Feb 29, 2012 is selected -> Feb 28, 2013
604     private void adjustDayInMonthIfNeeded(int month, int year) {
605         int day = mCurrentDate.get(Calendar.DAY_OF_MONTH);
606         int daysInMonth = getDaysInMonth(month, year);
607         if (day > daysInMonth) {
608             mCurrentDate.set(Calendar.DAY_OF_MONTH, daysInMonth);
609         }
610     }
611
612     public static int getDaysInMonth(int month, int year) {
613         switch (month) {
614             case Calendar.JANUARY:
615             case Calendar.MARCH:
616             case Calendar.MAY:
617             case Calendar.JULY:
618             case Calendar.AUGUST:
619             case Calendar.OCTOBER:
620             case Calendar.DECEMBER:
621                 return 31;
622             case Calendar.APRIL:
623             case Calendar.JUNE:
624             case Calendar.SEPTEMBER:
625             case Calendar.NOVEMBER:
626                 return 30;
627             case Calendar.FEBRUARY:
628                 return (year % 4 == 0) ? 29 : 28;
629             default:
630                 throw new IllegalArgumentException("Invalid Month");
631         }
632     }
633
634     @Override
635     public void registerOnDateChangedListener(OnDateChangedListener listener) {
636         mListeners.add(listener);
637     }
638
639     @Override
640     public Calendar getSelectedDay() {
641         return mCurrentDate;
642     }
643
644     @Override
645     public void tryVibrate() {
646         mDelegator.performHapticFeedback(HapticFeedbackConstants.CALENDAR_DATE);
647     }
648
649     @Override
650     public void onClick(View v) {
651         tryVibrate();
652         if (v.getId() == R.id.date_picker_year) {
653             setCurrentView(YEAR_VIEW);
654         } else if (v.getId() == R.id.date_picker_month_and_day_layout) {
655             setCurrentView(MONTH_AND_DAY_VIEW);
656         }
657     }
658
659     /**
660      * Listener called when the user selects a day in the day picker view.
661      */
662     private final DayPickerView.OnDaySelectedListener
663             mOnDaySelectedListener = new DayPickerView.OnDaySelectedListener() {
664         @Override
665         public void onDaySelected(DayPickerView view, Calendar day) {
666             mCurrentDate.setTimeInMillis(day.getTimeInMillis());
667             onDateChanged(true, true);
668         }
669     };
670
671     /**
672      * Class for managing state storing/restoring.
673      */
674     private static class SavedState extends View.BaseSavedState {
675
676         private final int mSelectedYear;
677         private final int mSelectedMonth;
678         private final int mSelectedDay;
679         private final long mMinDate;
680         private final long mMaxDate;
681         private final int mCurrentView;
682         private final int mListPosition;
683         private final int mListPositionOffset;
684
685         /**
686          * Constructor called from {@link DatePicker#onSaveInstanceState()}
687          */
688         private SavedState(Parcelable superState, int year, int month, int day,
689                 long minDate, long maxDate, int currentView, int listPosition,
690                 int listPositionOffset) {
691             super(superState);
692             mSelectedYear = year;
693             mSelectedMonth = month;
694             mSelectedDay = day;
695             mMinDate = minDate;
696             mMaxDate = maxDate;
697             mCurrentView = currentView;
698             mListPosition = listPosition;
699             mListPositionOffset = listPositionOffset;
700         }
701
702         /**
703          * Constructor called from {@link #CREATOR}
704          */
705         private SavedState(Parcel in) {
706             super(in);
707             mSelectedYear = in.readInt();
708             mSelectedMonth = in.readInt();
709             mSelectedDay = in.readInt();
710             mMinDate = in.readLong();
711             mMaxDate = in.readLong();
712             mCurrentView = in.readInt();
713             mListPosition = in.readInt();
714             mListPositionOffset = in.readInt();
715         }
716
717         @Override
718         public void writeToParcel(Parcel dest, int flags) {
719             super.writeToParcel(dest, flags);
720             dest.writeInt(mSelectedYear);
721             dest.writeInt(mSelectedMonth);
722             dest.writeInt(mSelectedDay);
723             dest.writeLong(mMinDate);
724             dest.writeLong(mMaxDate);
725             dest.writeInt(mCurrentView);
726             dest.writeInt(mListPosition);
727             dest.writeInt(mListPositionOffset);
728         }
729
730         public int getSelectedDay() {
731             return mSelectedDay;
732         }
733
734         public int getSelectedMonth() {
735             return mSelectedMonth;
736         }
737
738         public int getSelectedYear() {
739             return mSelectedYear;
740         }
741
742         public long getMinDate() {
743             return mMinDate;
744         }
745
746         public long getMaxDate() {
747             return mMaxDate;
748         }
749
750         public int getCurrentView() {
751             return mCurrentView;
752         }
753
754         public int getListPosition() {
755             return mListPosition;
756         }
757
758         public int getListPositionOffset() {
759             return mListPositionOffset;
760         }
761
762         @SuppressWarnings("all")
763         // suppress unused and hiding
764         public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
765
766             public SavedState createFromParcel(Parcel in) {
767                 return new SavedState(in);
768             }
769
770             public SavedState[] newArray(int size) {
771                 return new SavedState[size];
772             }
773         };
774     }
775 }