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.annotation.Nullable;
     20 import android.content.Context;
     21 import android.content.res.ColorStateList;
     22 import android.content.res.Configuration;
     23 import android.content.res.Resources;
     24 import android.content.res.TypedArray;
     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.StateSet;
     31 import android.view.HapticFeedbackConstants;
     32 import android.view.LayoutInflater;
     33 import android.view.View;
     34 import android.view.View.OnClickListener;
     35 import android.view.ViewGroup;
     36 import android.view.accessibility.AccessibilityEvent;
     37 import android.widget.DayPickerView.OnDaySelectedListener;
     38 import android.widget.YearPickerView.OnYearSelectedListener;
     39 
     40 import com.android.internal.R;
     41 
     42 import java.text.SimpleDateFormat;
     43 import java.util.Calendar;
     44 import java.util.Locale;
     45 
     46 /**
     47  * A delegate for picking up a date (day / month / year).
     48  */
     49 class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate {
     50     private static final int USE_LOCALE = 0;
     51 
     52     private static final int UNINITIALIZED = -1;
     53     private static final int VIEW_MONTH_DAY = 0;
     54     private static final int VIEW_YEAR = 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[] ATTRS_TEXT_COLOR = new int[] {
     62             com.android.internal.R.attr.textColor};
     63     private static final int[] ATTRS_DISABLED_ALPHA = new int[] {
     64             com.android.internal.R.attr.disabledAlpha};
     65 
     66     private SimpleDateFormat mYearFormat;
     67     private SimpleDateFormat mMonthDayFormat;
     68 
     69     // Top-level container.
     70     private ViewGroup mContainer;
     71 
     72     // Header views.
     73     private TextView mHeaderYear;
     74     private TextView mHeaderMonthDay;
     75 
     76     // Picker views.
     77     private ViewAnimator mAnimator;
     78     private DayPickerView mDayPickerView;
     79     private YearPickerView mYearPickerView;
     80 
     81     // Accessibility strings.
     82     private String mSelectDay;
     83     private String mSelectYear;
     84 
     85     private DatePicker.OnDateChangedListener mDateChangedListener;
     86 
     87     private int mCurrentView = UNINITIALIZED;
     88 
     89     private final Calendar mCurrentDate;
     90     private final Calendar mTempDate;
     91     private final Calendar mMinDate;
     92     private final Calendar mMaxDate;
     93 
     94     private int mFirstDayOfWeek = USE_LOCALE;
     95 
     96     public DatePickerCalendarDelegate(DatePicker delegator, Context context, AttributeSet attrs,
     97             int defStyleAttr, int defStyleRes) {
     98         super(delegator, context);
     99 
    100         final Locale locale = mCurrentLocale;
    101         mCurrentDate = Calendar.getInstance(locale);
    102         mTempDate = Calendar.getInstance(locale);
    103         mMinDate = Calendar.getInstance(locale);
    104         mMaxDate = Calendar.getInstance(locale);
    105 
    106         mMinDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1);
    107         mMaxDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31);
    108 
    109         final Resources res = mDelegator.getResources();
    110         final TypedArray a = mContext.obtainStyledAttributes(attrs,
    111                 R.styleable.DatePicker, defStyleAttr, defStyleRes);
    112         final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
    113                 Context.LAYOUT_INFLATER_SERVICE);
    114         final int layoutResourceId = a.getResourceId(
    115                 R.styleable.DatePicker_internalLayout, R.layout.date_picker_material);
    116 
    117         // Set up and attach container.
    118         mContainer = (ViewGroup) inflater.inflate(layoutResourceId, mDelegator, false);
    119         mDelegator.addView(mContainer);
    120 
    121         // Set up header views.
    122         final ViewGroup header = (ViewGroup) mContainer.findViewById(R.id.date_picker_header);
    123         mHeaderYear = (TextView) header.findViewById(R.id.date_picker_header_year);
    124         mHeaderYear.setOnClickListener(mOnHeaderClickListener);
    125         mHeaderMonthDay = (TextView) header.findViewById(R.id.date_picker_header_date);
    126         mHeaderMonthDay.setOnClickListener(mOnHeaderClickListener);
    127 
    128         // For the sake of backwards compatibility, attempt to extract the text
    129         // color from the header month text appearance. If it's set, we'll let
    130         // that override the "real" header text color.
    131         ColorStateList headerTextColor = null;
    132 
    133         @SuppressWarnings("deprecation")
    134         final int monthHeaderTextAppearance = a.getResourceId(
    135                 R.styleable.DatePicker_headerMonthTextAppearance, 0);
    136         if (monthHeaderTextAppearance != 0) {
    137             final TypedArray textAppearance = mContext.obtainStyledAttributes(null,
    138                     ATTRS_TEXT_COLOR, 0, monthHeaderTextAppearance);
    139             final ColorStateList legacyHeaderTextColor = textAppearance.getColorStateList(0);
    140             headerTextColor = applyLegacyColorFixes(legacyHeaderTextColor);
    141             textAppearance.recycle();
    142         }
    143 
    144         if (headerTextColor == null) {
    145             headerTextColor = a.getColorStateList(R.styleable.DatePicker_headerTextColor);
    146         }
    147 
    148         if (headerTextColor != null) {
    149             mHeaderYear.setTextColor(headerTextColor);
    150             mHeaderMonthDay.setTextColor(headerTextColor);
    151         }
    152 
    153         // Set up header background, if available.
    154         if (a.hasValueOrEmpty(R.styleable.DatePicker_headerBackground)) {
    155             header.setBackground(a.getDrawable(R.styleable.DatePicker_headerBackground));
    156         }
    157 
    158         a.recycle();
    159 
    160         // Set up picker container.
    161         mAnimator = (ViewAnimator) mContainer.findViewById(R.id.animator);
    162 
    163         // Set up day picker view.
    164         mDayPickerView = (DayPickerView) mAnimator.findViewById(R.id.date_picker_day_picker);
    165         mDayPickerView.setFirstDayOfWeek(mFirstDayOfWeek);
    166         mDayPickerView.setMinDate(mMinDate.getTimeInMillis());
    167         mDayPickerView.setMaxDate(mMaxDate.getTimeInMillis());
    168         mDayPickerView.setDate(mCurrentDate.getTimeInMillis());
    169         mDayPickerView.setOnDaySelectedListener(mOnDaySelectedListener);
    170 
    171         // Set up year picker view.
    172         mYearPickerView = (YearPickerView) mAnimator.findViewById(R.id.date_picker_year_picker);
    173         mYearPickerView.setRange(mMinDate, mMaxDate);
    174         mYearPickerView.setDate(mCurrentDate.getTimeInMillis());
    175         mYearPickerView.setOnYearSelectedListener(mOnYearSelectedListener);
    176 
    177         // Set up content descriptions.
    178         mSelectDay = res.getString(R.string.select_day);
    179         mSelectYear = res.getString(R.string.select_year);
    180 
    181         // Initialize for current locale. This also initializes the date, so no
    182         // need to call onDateChanged.
    183         onLocaleChanged(mCurrentLocale);
    184 
    185         setCurrentView(VIEW_MONTH_DAY);
    186     }
    187 
    188     /**
    189      * The legacy text color might have been poorly defined. Ensures that it
    190      * has an appropriate activated state, using the selected state if one
    191      * exists or modifying the default text color otherwise.
    192      *
    193      * @param color a legacy text color, or {@code null}
    194      * @return a color state list with an appropriate activated state, or
    195      *         {@code null} if a valid activated state could not be generated
    196      */
    197     @Nullable
    198     private ColorStateList applyLegacyColorFixes(@Nullable ColorStateList color) {
    199         if (color == null || color.hasState(R.attr.state_activated)) {
    200             return color;
    201         }
    202 
    203         final int activatedColor;
    204         final int defaultColor;
    205         if (color.hasState(R.attr.state_selected)) {
    206             activatedColor = color.getColorForState(StateSet.get(
    207                     StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_SELECTED), 0);
    208             defaultColor = color.getColorForState(StateSet.get(
    209                     StateSet.VIEW_STATE_ENABLED), 0);
    210         } else {
    211             activatedColor = color.getDefaultColor();
    212 
    213             // Generate a non-activated color using the disabled alpha.
    214             final TypedArray ta = mContext.obtainStyledAttributes(ATTRS_DISABLED_ALPHA);
    215             final float disabledAlpha = ta.getFloat(0, 0.30f);
    216             defaultColor = multiplyAlphaComponent(activatedColor, disabledAlpha);
    217         }
    218 
    219         if (activatedColor == 0 || defaultColor == 0) {
    220             // We somehow failed to obtain the colors.
    221             return null;
    222         }
    223 
    224         final int[][] stateSet = new int[][] {{ R.attr.state_activated }, {}};
    225         final int[] colors = new int[] { activatedColor, defaultColor };
    226         return new ColorStateList(stateSet, colors);
    227     }
    228 
    229     private int multiplyAlphaComponent(int color, float alphaMod) {
    230         final int srcRgb = color & 0xFFFFFF;
    231         final int srcAlpha = (color >> 24) & 0xFF;
    232         final int dstAlpha = (int) (srcAlpha * alphaMod + 0.5f);
    233         return srcRgb | (dstAlpha << 24);
    234     }
    235 
    236     /**
    237      * Listener called when the user selects a day in the day picker view.
    238      */
    239     private final OnDaySelectedListener mOnDaySelectedListener = new OnDaySelectedListener() {
    240         @Override
    241         public void onDaySelected(DayPickerView view, Calendar day) {
    242             mCurrentDate.setTimeInMillis(day.getTimeInMillis());
    243             onDateChanged(true, true);
    244         }
    245     };
    246 
    247     /**
    248      * Listener called when the user selects a year in the year picker view.
    249      */
    250     private final OnYearSelectedListener mOnYearSelectedListener = new OnYearSelectedListener() {
    251         @Override
    252         public void onYearChanged(YearPickerView view, int year) {
    253             // If the newly selected month / year does not contain the
    254             // currently selected day number, change the selected day number
    255             // to the last day of the selected month or year.
    256             // e.g. Switching from Mar to Apr when Mar 31 is selected -> Apr 30
    257             // e.g. Switching from 2012 to 2013 when Feb 29, 2012 is selected -> Feb 28, 2013
    258             final int day = mCurrentDate.get(Calendar.DAY_OF_MONTH);
    259             final int month = mCurrentDate.get(Calendar.MONTH);
    260             final int daysInMonth = getDaysInMonth(month, year);
    261             if (day > daysInMonth) {
    262                 mCurrentDate.set(Calendar.DAY_OF_MONTH, daysInMonth);
    263             }
    264 
    265             mCurrentDate.set(Calendar.YEAR, year);
    266             onDateChanged(true, true);
    267 
    268             // Automatically switch to day picker.
    269             setCurrentView(VIEW_MONTH_DAY);
    270         }
    271     };
    272 
    273     /**
    274      * Listener called when the user clicks on a header item.
    275      */
    276     private final OnClickListener mOnHeaderClickListener = new OnClickListener() {
    277         @Override
    278         public void onClick(View v) {
    279             tryVibrate();
    280 
    281             switch (v.getId()) {
    282                 case R.id.date_picker_header_year:
    283                     setCurrentView(VIEW_YEAR);
    284                     break;
    285                 case R.id.date_picker_header_date:
    286                     setCurrentView(VIEW_MONTH_DAY);
    287                     break;
    288             }
    289         }
    290     };
    291 
    292     @Override
    293     protected void onLocaleChanged(Locale locale) {
    294         final TextView headerYear = mHeaderYear;
    295         if (headerYear == null) {
    296             // Abort, we haven't initialized yet. This method will get called
    297             // again later after everything has been set up.
    298             return;
    299         }
    300 
    301         // Update the date formatter.
    302         final String datePattern = DateFormat.getBestDateTimePattern(locale, "EMMMd");
    303         mMonthDayFormat = new SimpleDateFormat(datePattern, locale);
    304         mYearFormat = new SimpleDateFormat("y", locale);
    305 
    306         // Update the header text.
    307         onCurrentDateChanged(false);
    308     }
    309 
    310     private void onCurrentDateChanged(boolean announce) {
    311         if (mHeaderYear == null) {
    312             // Abort, we haven't initialized yet. This method will get called
    313             // again later after everything has been set up.
    314             return;
    315         }
    316 
    317         final String year = mYearFormat.format(mCurrentDate.getTime());
    318         mHeaderYear.setText(year);
    319 
    320         final String monthDay = mMonthDayFormat.format(mCurrentDate.getTime());
    321         mHeaderMonthDay.setText(monthDay);
    322 
    323         // TODO: This should use live regions.
    324         if (announce) {
    325             final long millis = mCurrentDate.getTimeInMillis();
    326             final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
    327             final String fullDateText = DateUtils.formatDateTime(mContext, millis, flags);
    328             mAnimator.announceForAccessibility(fullDateText);
    329         }
    330     }
    331 
    332     private void setCurrentView(final int viewIndex) {
    333         switch (viewIndex) {
    334             case VIEW_MONTH_DAY:
    335                 mDayPickerView.setDate(mCurrentDate.getTimeInMillis());
    336 
    337                 if (mCurrentView != viewIndex) {
    338                     mHeaderMonthDay.setActivated(true);
    339                     mHeaderYear.setActivated(false);
    340                     mAnimator.setDisplayedChild(VIEW_MONTH_DAY);
    341                     mCurrentView = viewIndex;
    342                 }
    343 
    344                 mAnimator.announceForAccessibility(mSelectDay);
    345                 break;
    346             case VIEW_YEAR:
    347                 mYearPickerView.setDate(mCurrentDate.getTimeInMillis());
    348 
    349                 if (mCurrentView != viewIndex) {
    350                     mHeaderMonthDay.setActivated(false);
    351                     mHeaderYear.setActivated(true);
    352                     mAnimator.setDisplayedChild(VIEW_YEAR);
    353                     mCurrentView = viewIndex;
    354                 }
    355 
    356                 mAnimator.announceForAccessibility(mSelectYear);
    357                 break;
    358         }
    359     }
    360 
    361     @Override
    362     public void init(int year, int monthOfYear, int dayOfMonth,
    363             DatePicker.OnDateChangedListener callBack) {
    364         mCurrentDate.set(Calendar.YEAR, year);
    365         mCurrentDate.set(Calendar.MONTH, monthOfYear);
    366         mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
    367 
    368         mDateChangedListener = callBack;
    369 
    370         onDateChanged(false, false);
    371     }
    372 
    373     @Override
    374     public void updateDate(int year, int month, int dayOfMonth) {
    375         mCurrentDate.set(Calendar.YEAR, year);
    376         mCurrentDate.set(Calendar.MONTH, month);
    377         mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
    378 
    379         onDateChanged(false, true);
    380     }
    381 
    382     private void onDateChanged(boolean fromUser, boolean callbackToClient) {
    383         final int year = mCurrentDate.get(Calendar.YEAR);
    384 
    385         if (callbackToClient && mDateChangedListener != null) {
    386             final int monthOfYear = mCurrentDate.get(Calendar.MONTH);
    387             final int dayOfMonth = mCurrentDate.get(Calendar.DAY_OF_MONTH);
    388             mDateChangedListener.onDateChanged(mDelegator, year, monthOfYear, dayOfMonth);
    389         }
    390 
    391         mDayPickerView.setDate(mCurrentDate.getTimeInMillis());
    392         mYearPickerView.setYear(year);
    393 
    394         onCurrentDateChanged(fromUser);
    395 
    396         if (fromUser) {
    397             tryVibrate();
    398         }
    399     }
    400 
    401     @Override
    402     public int getYear() {
    403         return mCurrentDate.get(Calendar.YEAR);
    404     }
    405 
    406     @Override
    407     public int getMonth() {
    408         return mCurrentDate.get(Calendar.MONTH);
    409     }
    410 
    411     @Override
    412     public int getDayOfMonth() {
    413         return mCurrentDate.get(Calendar.DAY_OF_MONTH);
    414     }
    415 
    416     @Override
    417     public void setMinDate(long minDate) {
    418         mTempDate.setTimeInMillis(minDate);
    419         if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
    420                 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) {
    421             return;
    422         }
    423         if (mCurrentDate.before(mTempDate)) {
    424             mCurrentDate.setTimeInMillis(minDate);
    425             onDateChanged(false, true);
    426         }
    427         mMinDate.setTimeInMillis(minDate);
    428         mDayPickerView.setMinDate(minDate);
    429         mYearPickerView.setRange(mMinDate, mMaxDate);
    430     }
    431 
    432     @Override
    433     public Calendar getMinDate() {
    434         return mMinDate;
    435     }
    436 
    437     @Override
    438     public void setMaxDate(long maxDate) {
    439         mTempDate.setTimeInMillis(maxDate);
    440         if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
    441                 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) {
    442             return;
    443         }
    444         if (mCurrentDate.after(mTempDate)) {
    445             mCurrentDate.setTimeInMillis(maxDate);
    446             onDateChanged(false, true);
    447         }
    448         mMaxDate.setTimeInMillis(maxDate);
    449         mDayPickerView.setMaxDate(maxDate);
    450         mYearPickerView.setRange(mMinDate, mMaxDate);
    451     }
    452 
    453     @Override
    454     public Calendar getMaxDate() {
    455         return mMaxDate;
    456     }
    457 
    458     @Override
    459     public void setFirstDayOfWeek(int firstDayOfWeek) {
    460         mFirstDayOfWeek = firstDayOfWeek;
    461 
    462         mDayPickerView.setFirstDayOfWeek(firstDayOfWeek);
    463     }
    464 
    465     @Override
    466     public int getFirstDayOfWeek() {
    467         if (mFirstDayOfWeek != USE_LOCALE) {
    468             return mFirstDayOfWeek;
    469         }
    470         return mCurrentDate.getFirstDayOfWeek();
    471     }
    472 
    473     @Override
    474     public void setEnabled(boolean enabled) {
    475         mContainer.setEnabled(enabled);
    476         mDayPickerView.setEnabled(enabled);
    477         mYearPickerView.setEnabled(enabled);
    478         mHeaderYear.setEnabled(enabled);
    479         mHeaderMonthDay.setEnabled(enabled);
    480     }
    481 
    482     @Override
    483     public boolean isEnabled() {
    484         return mContainer.isEnabled();
    485     }
    486 
    487     @Override
    488     public CalendarView getCalendarView() {
    489         throw new UnsupportedOperationException("Not supported by calendar-mode DatePicker");
    490     }
    491 
    492     @Override
    493     public void setCalendarViewShown(boolean shown) {
    494         // No-op for compatibility with the old DatePicker.
    495     }
    496 
    497     @Override
    498     public boolean getCalendarViewShown() {
    499         return false;
    500     }
    501 
    502     @Override
    503     public void setSpinnersShown(boolean shown) {
    504         // No-op for compatibility with the old DatePicker.
    505     }
    506 
    507     @Override
    508     public boolean getSpinnersShown() {
    509         return false;
    510     }
    511 
    512     @Override
    513     public void onConfigurationChanged(Configuration newConfig) {
    514         setCurrentLocale(newConfig.locale);
    515     }
    516 
    517     @Override
    518     public Parcelable onSaveInstanceState(Parcelable superState) {
    519         final int year = mCurrentDate.get(Calendar.YEAR);
    520         final int month = mCurrentDate.get(Calendar.MONTH);
    521         final int day = mCurrentDate.get(Calendar.DAY_OF_MONTH);
    522 
    523         int listPosition = -1;
    524         int listPositionOffset = -1;
    525 
    526         if (mCurrentView == VIEW_MONTH_DAY) {
    527             listPosition = mDayPickerView.getMostVisiblePosition();
    528         } else if (mCurrentView == VIEW_YEAR) {
    529             listPosition = mYearPickerView.getFirstVisiblePosition();
    530             listPositionOffset = mYearPickerView.getFirstPositionOffset();
    531         }
    532 
    533         return new SavedState(superState, year, month, day, mMinDate.getTimeInMillis(),
    534                 mMaxDate.getTimeInMillis(), mCurrentView, listPosition, listPositionOffset);
    535     }
    536 
    537     @Override
    538     public void onRestoreInstanceState(Parcelable state) {
    539         final SavedState ss = (SavedState) state;
    540 
    541         // TODO: Move instance state into DayPickerView, YearPickerView.
    542         mCurrentDate.set(ss.getSelectedYear(), ss.getSelectedMonth(), ss.getSelectedDay());
    543         mMinDate.setTimeInMillis(ss.getMinDate());
    544         mMaxDate.setTimeInMillis(ss.getMaxDate());
    545 
    546         onCurrentDateChanged(false);
    547 
    548         final int currentView = ss.getCurrentView();
    549         setCurrentView(currentView);
    550 
    551         final int listPosition = ss.getListPosition();
    552         if (listPosition != -1) {
    553             if (currentView == VIEW_MONTH_DAY) {
    554                 mDayPickerView.setPosition(listPosition);
    555             } else if (currentView == VIEW_YEAR) {
    556                 final int listPositionOffset = ss.getListPositionOffset();
    557                 mYearPickerView.setSelectionFromTop(listPosition, listPositionOffset);
    558             }
    559         }
    560     }
    561 
    562     @Override
    563     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
    564         onPopulateAccessibilityEvent(event);
    565         return true;
    566     }
    567 
    568     @Override
    569     public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
    570         event.getText().add(mCurrentDate.getTime().toString());
    571     }
    572 
    573     public CharSequence getAccessibilityClassName() {
    574         return DatePicker.class.getName();
    575     }
    576 
    577     public static int getDaysInMonth(int month, int year) {
    578         switch (month) {
    579             case Calendar.JANUARY:
    580             case Calendar.MARCH:
    581             case Calendar.MAY:
    582             case Calendar.JULY:
    583             case Calendar.AUGUST:
    584             case Calendar.OCTOBER:
    585             case Calendar.DECEMBER:
    586                 return 31;
    587             case Calendar.APRIL:
    588             case Calendar.JUNE:
    589             case Calendar.SEPTEMBER:
    590             case Calendar.NOVEMBER:
    591                 return 30;
    592             case Calendar.FEBRUARY:
    593                 return (year % 4 == 0) ? 29 : 28;
    594             default:
    595                 throw new IllegalArgumentException("Invalid Month");
    596         }
    597     }
    598 
    599     private void tryVibrate() {
    600         mDelegator.performHapticFeedback(HapticFeedbackConstants.CALENDAR_DATE);
    601     }
    602 
    603     /**
    604      * Class for managing state storing/restoring.
    605      */
    606     private static class SavedState extends View.BaseSavedState {
    607         private final int mSelectedYear;
    608         private final int mSelectedMonth;
    609         private final int mSelectedDay;
    610         private final long mMinDate;
    611         private final long mMaxDate;
    612         private final int mCurrentView;
    613         private final int mListPosition;
    614         private final int mListPositionOffset;
    615 
    616         /**
    617          * Constructor called from {@link DatePicker#onSaveInstanceState()}
    618          */
    619         private SavedState(Parcelable superState, int year, int month, int day,
    620                 long minDate, long maxDate, int currentView, int listPosition,
    621                 int listPositionOffset) {
    622             super(superState);
    623             mSelectedYear = year;
    624             mSelectedMonth = month;
    625             mSelectedDay = day;
    626             mMinDate = minDate;
    627             mMaxDate = maxDate;
    628             mCurrentView = currentView;
    629             mListPosition = listPosition;
    630             mListPositionOffset = listPositionOffset;
    631         }
    632 
    633         /**
    634          * Constructor called from {@link #CREATOR}
    635          */
    636         private SavedState(Parcel in) {
    637             super(in);
    638             mSelectedYear = in.readInt();
    639             mSelectedMonth = in.readInt();
    640             mSelectedDay = in.readInt();
    641             mMinDate = in.readLong();
    642             mMaxDate = in.readLong();
    643             mCurrentView = in.readInt();
    644             mListPosition = in.readInt();
    645             mListPositionOffset = in.readInt();
    646         }
    647 
    648         @Override
    649         public void writeToParcel(Parcel dest, int flags) {
    650             super.writeToParcel(dest, flags);
    651             dest.writeInt(mSelectedYear);
    652             dest.writeInt(mSelectedMonth);
    653             dest.writeInt(mSelectedDay);
    654             dest.writeLong(mMinDate);
    655             dest.writeLong(mMaxDate);
    656             dest.writeInt(mCurrentView);
    657             dest.writeInt(mListPosition);
    658             dest.writeInt(mListPositionOffset);
    659         }
    660 
    661         public int getSelectedDay() {
    662             return mSelectedDay;
    663         }
    664 
    665         public int getSelectedMonth() {
    666             return mSelectedMonth;
    667         }
    668 
    669         public int getSelectedYear() {
    670             return mSelectedYear;
    671         }
    672 
    673         public long getMinDate() {
    674             return mMinDate;
    675         }
    676 
    677         public long getMaxDate() {
    678             return mMaxDate;
    679         }
    680 
    681         public int getCurrentView() {
    682             return mCurrentView;
    683         }
    684 
    685         public int getListPosition() {
    686             return mListPosition;
    687         }
    688 
    689         public int getListPositionOffset() {
    690             return mListPositionOffset;
    691         }
    692 
    693         @SuppressWarnings("all")
    694         // suppress unused and hiding
    695         public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
    696 
    697             public SavedState createFromParcel(Parcel in) {
    698                 return new SavedState(in);
    699             }
    700 
    701             public SavedState[] newArray(int size) {
    702                 return new SavedState[size];
    703             }
    704         };
    705     }
    706 }
    707