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