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