Home | History | Annotate | Download | only in widget
      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 }
    777