Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2007 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.Widget;
     20 import android.content.Context;
     21 import android.content.res.Configuration;
     22 import android.content.res.TypedArray;
     23 import android.os.Parcel;
     24 import android.os.Parcelable;
     25 import android.text.TextUtils;
     26 import android.text.format.DateFormat;
     27 import android.text.format.DateUtils;
     28 import android.util.AttributeSet;
     29 import android.util.Log;
     30 import android.util.SparseArray;
     31 import android.view.LayoutInflater;
     32 import android.view.View;
     33 import android.view.accessibility.AccessibilityEvent;
     34 import android.view.accessibility.AccessibilityNodeInfo;
     35 import android.view.inputmethod.EditorInfo;
     36 import android.view.inputmethod.InputMethodManager;
     37 import android.widget.NumberPicker.OnValueChangeListener;
     38 
     39 import com.android.internal.R;
     40 
     41 import java.text.ParseException;
     42 import java.text.SimpleDateFormat;
     43 import java.util.Arrays;
     44 import java.util.Calendar;
     45 import java.util.Locale;
     46 import java.util.TimeZone;
     47 
     48 /**
     49  * This class is a widget for selecting a date. The date can be selected by a
     50  * year, month, and day spinners or a {@link CalendarView}. The set of spinners
     51  * and the calendar view are automatically synchronized. The client can
     52  * customize whether only the spinners, or only the calendar view, or both to be
     53  * displayed. Also the minimal and maximal date from which dates to be selected
     54  * can be customized.
     55  * <p>
     56  * See the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a>
     57  * guide.
     58  * </p>
     59  * <p>
     60  * For a dialog using this view, see {@link android.app.DatePickerDialog}.
     61  * </p>
     62  *
     63  * @attr ref android.R.styleable#DatePicker_startYear
     64  * @attr ref android.R.styleable#DatePicker_endYear
     65  * @attr ref android.R.styleable#DatePicker_maxDate
     66  * @attr ref android.R.styleable#DatePicker_minDate
     67  * @attr ref android.R.styleable#DatePicker_spinnersShown
     68  * @attr ref android.R.styleable#DatePicker_calendarViewShown
     69  */
     70 @Widget
     71 public class DatePicker extends FrameLayout {
     72 
     73     private static final String LOG_TAG = DatePicker.class.getSimpleName();
     74 
     75     private static final String DATE_FORMAT = "MM/dd/yyyy";
     76 
     77     private static final int DEFAULT_START_YEAR = 1900;
     78 
     79     private static final int DEFAULT_END_YEAR = 2100;
     80 
     81     private static final boolean DEFAULT_CALENDAR_VIEW_SHOWN = true;
     82 
     83     private static final boolean DEFAULT_SPINNERS_SHOWN = true;
     84 
     85     private static final boolean DEFAULT_ENABLED_STATE = true;
     86 
     87     private final LinearLayout mSpinners;
     88 
     89     private final NumberPicker mDaySpinner;
     90 
     91     private final NumberPicker mMonthSpinner;
     92 
     93     private final NumberPicker mYearSpinner;
     94 
     95     private final EditText mDaySpinnerInput;
     96 
     97     private final EditText mMonthSpinnerInput;
     98 
     99     private final EditText mYearSpinnerInput;
    100 
    101     private final CalendarView mCalendarView;
    102 
    103     private Locale mCurrentLocale;
    104 
    105     private OnDateChangedListener mOnDateChangedListener;
    106 
    107     private String[] mShortMonths;
    108 
    109     private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT);
    110 
    111     private int mNumberOfMonths;
    112 
    113     private Calendar mTempDate;
    114 
    115     private Calendar mMinDate;
    116 
    117     private Calendar mMaxDate;
    118 
    119     private Calendar mCurrentDate;
    120 
    121     private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
    122 
    123     /**
    124      * The callback used to indicate the user changes\d the date.
    125      */
    126     public interface OnDateChangedListener {
    127 
    128         /**
    129          * Called upon a date change.
    130          *
    131          * @param view The view associated with this listener.
    132          * @param year The year that was set.
    133          * @param monthOfYear The month that was set (0-11) for compatibility
    134          *            with {@link java.util.Calendar}.
    135          * @param dayOfMonth The day of the month that was set.
    136          */
    137         void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth);
    138     }
    139 
    140     public DatePicker(Context context) {
    141         this(context, null);
    142     }
    143 
    144     public DatePicker(Context context, AttributeSet attrs) {
    145         this(context, attrs, R.attr.datePickerStyle);
    146     }
    147 
    148     public DatePicker(Context context, AttributeSet attrs, int defStyle) {
    149         super(context, attrs, defStyle);
    150 
    151         // initialization based on locale
    152         setCurrentLocale(Locale.getDefault());
    153 
    154         TypedArray attributesArray = context.obtainStyledAttributes(attrs, R.styleable.DatePicker,
    155                 defStyle, 0);
    156         boolean spinnersShown = attributesArray.getBoolean(R.styleable.DatePicker_spinnersShown,
    157                 DEFAULT_SPINNERS_SHOWN);
    158         boolean calendarViewShown = attributesArray.getBoolean(
    159                 R.styleable.DatePicker_calendarViewShown, DEFAULT_CALENDAR_VIEW_SHOWN);
    160         int startYear = attributesArray.getInt(R.styleable.DatePicker_startYear,
    161                 DEFAULT_START_YEAR);
    162         int endYear = attributesArray.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR);
    163         String minDate = attributesArray.getString(R.styleable.DatePicker_minDate);
    164         String maxDate = attributesArray.getString(R.styleable.DatePicker_maxDate);
    165         int layoutResourceId = attributesArray.getResourceId(R.styleable.DatePicker_internalLayout,
    166                 R.layout.date_picker);
    167         attributesArray.recycle();
    168 
    169         LayoutInflater inflater = (LayoutInflater) context
    170                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    171         inflater.inflate(layoutResourceId, this, true);
    172 
    173         OnValueChangeListener onChangeListener = new OnValueChangeListener() {
    174             public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
    175                 updateInputState();
    176                 mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis());
    177                 // take care of wrapping of days and months to update greater fields
    178                 if (picker == mDaySpinner) {
    179                     int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH);
    180                     if (oldVal == maxDayOfMonth && newVal == 1) {
    181                         mTempDate.add(Calendar.DAY_OF_MONTH, 1);
    182                     } else if (oldVal == 1 && newVal == maxDayOfMonth) {
    183                         mTempDate.add(Calendar.DAY_OF_MONTH, -1);
    184                     } else {
    185                         mTempDate.add(Calendar.DAY_OF_MONTH, newVal - oldVal);
    186                     }
    187                 } else if (picker == mMonthSpinner) {
    188                     if (oldVal == 11 && newVal == 0) {
    189                         mTempDate.add(Calendar.MONTH, 1);
    190                     } else if (oldVal == 0 && newVal == 11) {
    191                         mTempDate.add(Calendar.MONTH, -1);
    192                     } else {
    193                         mTempDate.add(Calendar.MONTH, newVal - oldVal);
    194                     }
    195                 } else if (picker == mYearSpinner) {
    196                     mTempDate.set(Calendar.YEAR, newVal);
    197                 } else {
    198                     throw new IllegalArgumentException();
    199                 }
    200                 // now set the date to the adjusted one
    201                 setDate(mTempDate.get(Calendar.YEAR), mTempDate.get(Calendar.MONTH),
    202                         mTempDate.get(Calendar.DAY_OF_MONTH));
    203                 updateSpinners();
    204                 updateCalendarView();
    205                 notifyDateChanged();
    206             }
    207         };
    208 
    209         mSpinners = (LinearLayout) findViewById(R.id.pickers);
    210 
    211         // calendar view day-picker
    212         mCalendarView = (CalendarView) findViewById(R.id.calendar_view);
    213         mCalendarView.setOnDateChangeListener(new CalendarView.OnDateChangeListener() {
    214             public void onSelectedDayChange(CalendarView view, int year, int month, int monthDay) {
    215                 setDate(year, month, monthDay);
    216                 updateSpinners();
    217                 notifyDateChanged();
    218             }
    219         });
    220 
    221         // day
    222         mDaySpinner = (NumberPicker) findViewById(R.id.day);
    223         mDaySpinner.setFormatter(NumberPicker.getTwoDigitFormatter());
    224         mDaySpinner.setOnLongPressUpdateInterval(100);
    225         mDaySpinner.setOnValueChangedListener(onChangeListener);
    226         mDaySpinnerInput = (EditText) mDaySpinner.findViewById(R.id.numberpicker_input);
    227 
    228         // month
    229         mMonthSpinner = (NumberPicker) findViewById(R.id.month);
    230         mMonthSpinner.setMinValue(0);
    231         mMonthSpinner.setMaxValue(mNumberOfMonths - 1);
    232         mMonthSpinner.setDisplayedValues(mShortMonths);
    233         mMonthSpinner.setOnLongPressUpdateInterval(200);
    234         mMonthSpinner.setOnValueChangedListener(onChangeListener);
    235         mMonthSpinnerInput = (EditText) mMonthSpinner.findViewById(R.id.numberpicker_input);
    236 
    237         // year
    238         mYearSpinner = (NumberPicker) findViewById(R.id.year);
    239         mYearSpinner.setOnLongPressUpdateInterval(100);
    240         mYearSpinner.setOnValueChangedListener(onChangeListener);
    241         mYearSpinnerInput = (EditText) mYearSpinner.findViewById(R.id.numberpicker_input);
    242 
    243         // show only what the user required but make sure we
    244         // show something and the spinners have higher priority
    245         if (!spinnersShown && !calendarViewShown) {
    246             setSpinnersShown(true);
    247         } else {
    248             setSpinnersShown(spinnersShown);
    249             setCalendarViewShown(calendarViewShown);
    250         }
    251 
    252         // set the min date giving priority of the minDate over startYear
    253         mTempDate.clear();
    254         if (!TextUtils.isEmpty(minDate)) {
    255             if (!parseDate(minDate, mTempDate)) {
    256                 mTempDate.set(startYear, 0, 1);
    257             }
    258         } else {
    259             mTempDate.set(startYear, 0, 1);
    260         }
    261         setMinDate(mTempDate.getTimeInMillis());
    262 
    263         // set the max date giving priority of the maxDate over endYear
    264         mTempDate.clear();
    265         if (!TextUtils.isEmpty(maxDate)) {
    266             if (!parseDate(maxDate, mTempDate)) {
    267                 mTempDate.set(endYear, 11, 31);
    268             }
    269         } else {
    270             mTempDate.set(endYear, 11, 31);
    271         }
    272         setMaxDate(mTempDate.getTimeInMillis());
    273 
    274         // initialize to current date
    275         mCurrentDate.setTimeInMillis(System.currentTimeMillis());
    276         init(mCurrentDate.get(Calendar.YEAR), mCurrentDate.get(Calendar.MONTH), mCurrentDate
    277                 .get(Calendar.DAY_OF_MONTH), null);
    278 
    279         // re-order the number spinners to match the current date format
    280         reorderSpinners();
    281 
    282         // accessibility
    283         setContentDescriptions();
    284 
    285         // If not explicitly specified this view is important for accessibility.
    286         if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
    287             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
    288         }
    289     }
    290 
    291     /**
    292      * Gets the minimal date supported by this {@link DatePicker} in
    293      * milliseconds since January 1, 1970 00:00:00 in
    294      * {@link TimeZone#getDefault()} time zone.
    295      * <p>
    296      * Note: The default minimal date is 01/01/1900.
    297      * <p>
    298      *
    299      * @return The minimal supported date.
    300      */
    301     public long getMinDate() {
    302         return mCalendarView.getMinDate();
    303     }
    304 
    305     /**
    306      * Sets the minimal date supported by this {@link NumberPicker} in
    307      * milliseconds since January 1, 1970 00:00:00 in
    308      * {@link TimeZone#getDefault()} time zone.
    309      *
    310      * @param minDate The minimal supported date.
    311      */
    312     public void setMinDate(long minDate) {
    313         mTempDate.setTimeInMillis(minDate);
    314         if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
    315                 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) {
    316             return;
    317         }
    318         mMinDate.setTimeInMillis(minDate);
    319         mCalendarView.setMinDate(minDate);
    320         if (mCurrentDate.before(mMinDate)) {
    321             mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
    322             updateCalendarView();
    323         }
    324         updateSpinners();
    325     }
    326 
    327     /**
    328      * Gets the maximal date supported by this {@link DatePicker} in
    329      * milliseconds since January 1, 1970 00:00:00 in
    330      * {@link TimeZone#getDefault()} time zone.
    331      * <p>
    332      * Note: The default maximal date is 12/31/2100.
    333      * <p>
    334      *
    335      * @return The maximal supported date.
    336      */
    337     public long getMaxDate() {
    338         return mCalendarView.getMaxDate();
    339     }
    340 
    341     /**
    342      * Sets the maximal date supported by this {@link DatePicker} in
    343      * milliseconds since January 1, 1970 00:00:00 in
    344      * {@link TimeZone#getDefault()} time zone.
    345      *
    346      * @param maxDate The maximal supported date.
    347      */
    348     public void setMaxDate(long maxDate) {
    349         mTempDate.setTimeInMillis(maxDate);
    350         if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
    351                 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) {
    352             return;
    353         }
    354         mMaxDate.setTimeInMillis(maxDate);
    355         mCalendarView.setMaxDate(maxDate);
    356         if (mCurrentDate.after(mMaxDate)) {
    357             mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
    358             updateCalendarView();
    359         }
    360         updateSpinners();
    361     }
    362 
    363     @Override
    364     public void setEnabled(boolean enabled) {
    365         if (mIsEnabled == enabled) {
    366             return;
    367         }
    368         super.setEnabled(enabled);
    369         mDaySpinner.setEnabled(enabled);
    370         mMonthSpinner.setEnabled(enabled);
    371         mYearSpinner.setEnabled(enabled);
    372         mCalendarView.setEnabled(enabled);
    373         mIsEnabled = enabled;
    374     }
    375 
    376     @Override
    377     public boolean isEnabled() {
    378         return mIsEnabled;
    379     }
    380 
    381     @Override
    382     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
    383         onPopulateAccessibilityEvent(event);
    384         return true;
    385     }
    386 
    387     @Override
    388     public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
    389         super.onPopulateAccessibilityEvent(event);
    390 
    391         final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
    392         String selectedDateUtterance = DateUtils.formatDateTime(mContext,
    393                 mCurrentDate.getTimeInMillis(), flags);
    394         event.getText().add(selectedDateUtterance);
    395     }
    396 
    397     @Override
    398     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
    399         super.onInitializeAccessibilityEvent(event);
    400         event.setClassName(DatePicker.class.getName());
    401     }
    402 
    403     @Override
    404     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
    405         super.onInitializeAccessibilityNodeInfo(info);
    406         info.setClassName(DatePicker.class.getName());
    407     }
    408 
    409     @Override
    410     protected void onConfigurationChanged(Configuration newConfig) {
    411         super.onConfigurationChanged(newConfig);
    412         setCurrentLocale(newConfig.locale);
    413     }
    414 
    415     /**
    416      * Gets whether the {@link CalendarView} is shown.
    417      *
    418      * @return True if the calendar view is shown.
    419      * @see #getCalendarView()
    420      */
    421     public boolean getCalendarViewShown() {
    422         return (mCalendarView.getVisibility() == View.VISIBLE);
    423     }
    424 
    425     /**
    426      * Gets the {@link CalendarView}.
    427      *
    428      * @return The calendar view.
    429      * @see #getCalendarViewShown()
    430      */
    431     public CalendarView getCalendarView () {
    432         return mCalendarView;
    433     }
    434 
    435     /**
    436      * Sets whether the {@link CalendarView} is shown.
    437      *
    438      * @param shown True if the calendar view is to be shown.
    439      */
    440     public void setCalendarViewShown(boolean shown) {
    441         mCalendarView.setVisibility(shown ? VISIBLE : GONE);
    442     }
    443 
    444     /**
    445      * Gets whether the spinners are shown.
    446      *
    447      * @return True if the spinners are shown.
    448      */
    449     public boolean getSpinnersShown() {
    450         return mSpinners.isShown();
    451     }
    452 
    453     /**
    454      * Sets whether the spinners are shown.
    455      *
    456      * @param shown True if the spinners are to be shown.
    457      */
    458     public void setSpinnersShown(boolean shown) {
    459         mSpinners.setVisibility(shown ? VISIBLE : GONE);
    460     }
    461 
    462     /**
    463      * Sets the current locale.
    464      *
    465      * @param locale The current locale.
    466      */
    467     private void setCurrentLocale(Locale locale) {
    468         if (locale.equals(mCurrentLocale)) {
    469             return;
    470         }
    471 
    472         mCurrentLocale = locale;
    473 
    474         mTempDate = getCalendarForLocale(mTempDate, locale);
    475         mMinDate = getCalendarForLocale(mMinDate, locale);
    476         mMaxDate = getCalendarForLocale(mMaxDate, locale);
    477         mCurrentDate = getCalendarForLocale(mCurrentDate, locale);
    478 
    479         mNumberOfMonths = mTempDate.getActualMaximum(Calendar.MONTH) + 1;
    480         mShortMonths = new String[mNumberOfMonths];
    481         for (int i = 0; i < mNumberOfMonths; i++) {
    482             mShortMonths[i] = DateUtils.getMonthString(Calendar.JANUARY + i,
    483                     DateUtils.LENGTH_MEDIUM);
    484         }
    485     }
    486 
    487     /**
    488      * Gets a calendar for locale bootstrapped with the value of a given calendar.
    489      *
    490      * @param oldCalendar The old calendar.
    491      * @param locale The locale.
    492      */
    493     private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
    494         if (oldCalendar == null) {
    495             return Calendar.getInstance(locale);
    496         } else {
    497             final long currentTimeMillis = oldCalendar.getTimeInMillis();
    498             Calendar newCalendar = Calendar.getInstance(locale);
    499             newCalendar.setTimeInMillis(currentTimeMillis);
    500             return newCalendar;
    501         }
    502     }
    503 
    504     /**
    505      * Reorders the spinners according to the date format that is
    506      * explicitly set by the user and if no such is set fall back
    507      * to the current locale's default format.
    508      */
    509     private void reorderSpinners() {
    510         mSpinners.removeAllViews();
    511         char[] order = DateFormat.getDateFormatOrder(getContext());
    512         final int spinnerCount = order.length;
    513         for (int i = 0; i < spinnerCount; i++) {
    514             switch (order[i]) {
    515                 case DateFormat.DATE:
    516                     mSpinners.addView(mDaySpinner);
    517                     setImeOptions(mDaySpinner, spinnerCount, i);
    518                     break;
    519                 case DateFormat.MONTH:
    520                     mSpinners.addView(mMonthSpinner);
    521                     setImeOptions(mMonthSpinner, spinnerCount, i);
    522                     break;
    523                 case DateFormat.YEAR:
    524                     mSpinners.addView(mYearSpinner);
    525                     setImeOptions(mYearSpinner, spinnerCount, i);
    526                     break;
    527                 default:
    528                     throw new IllegalArgumentException();
    529             }
    530         }
    531     }
    532 
    533     /**
    534      * Updates the current date.
    535      *
    536      * @param year The year.
    537      * @param month The month which is <strong>starting from zero</strong>.
    538      * @param dayOfMonth The day of the month.
    539      */
    540     public void updateDate(int year, int month, int dayOfMonth) {
    541         if (!isNewDate(year, month, dayOfMonth)) {
    542             return;
    543         }
    544         setDate(year, month, dayOfMonth);
    545         updateSpinners();
    546         updateCalendarView();
    547         notifyDateChanged();
    548     }
    549 
    550     // Override so we are in complete control of save / restore for this widget.
    551     @Override
    552     protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
    553         dispatchThawSelfOnly(container);
    554     }
    555 
    556     @Override
    557     protected Parcelable onSaveInstanceState() {
    558         Parcelable superState = super.onSaveInstanceState();
    559         return new SavedState(superState, getYear(), getMonth(), getDayOfMonth());
    560     }
    561 
    562     @Override
    563     protected void onRestoreInstanceState(Parcelable state) {
    564         SavedState ss = (SavedState) state;
    565         super.onRestoreInstanceState(ss.getSuperState());
    566         setDate(ss.mYear, ss.mMonth, ss.mDay);
    567         updateSpinners();
    568         updateCalendarView();
    569     }
    570 
    571     /**
    572      * Initialize the state. If the provided values designate an inconsistent
    573      * date the values are normalized before updating the spinners.
    574      *
    575      * @param year The initial year.
    576      * @param monthOfYear The initial month <strong>starting from zero</strong>.
    577      * @param dayOfMonth The initial day of the month.
    578      * @param onDateChangedListener How user is notified date is changed by
    579      *            user, can be null.
    580      */
    581     public void init(int year, int monthOfYear, int dayOfMonth,
    582             OnDateChangedListener onDateChangedListener) {
    583         setDate(year, monthOfYear, dayOfMonth);
    584         updateSpinners();
    585         updateCalendarView();
    586         mOnDateChangedListener = onDateChangedListener;
    587     }
    588 
    589     /**
    590      * Parses the given <code>date</code> and in case of success sets the result
    591      * to the <code>outDate</code>.
    592      *
    593      * @return True if the date was parsed.
    594      */
    595     private boolean parseDate(String date, Calendar outDate) {
    596         try {
    597             outDate.setTime(mDateFormat.parse(date));
    598             return true;
    599         } catch (ParseException e) {
    600             Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT);
    601             return false;
    602         }
    603     }
    604 
    605     private boolean isNewDate(int year, int month, int dayOfMonth) {
    606         return (mCurrentDate.get(Calendar.YEAR) != year
    607                 || mCurrentDate.get(Calendar.MONTH) != dayOfMonth
    608                 || mCurrentDate.get(Calendar.DAY_OF_MONTH) != month);
    609     }
    610 
    611     private void setDate(int year, int month, int dayOfMonth) {
    612         mCurrentDate.set(year, month, dayOfMonth);
    613         if (mCurrentDate.before(mMinDate)) {
    614             mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
    615         } else if (mCurrentDate.after(mMaxDate)) {
    616             mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
    617         }
    618     }
    619 
    620     private void updateSpinners() {
    621         // set the spinner ranges respecting the min and max dates
    622         if (mCurrentDate.equals(mMinDate)) {
    623             mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
    624             mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
    625             mDaySpinner.setWrapSelectorWheel(false);
    626             mMonthSpinner.setDisplayedValues(null);
    627             mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH));
    628             mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH));
    629             mMonthSpinner.setWrapSelectorWheel(false);
    630         } else if (mCurrentDate.equals(mMaxDate)) {
    631             mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH));
    632             mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
    633             mDaySpinner.setWrapSelectorWheel(false);
    634             mMonthSpinner.setDisplayedValues(null);
    635             mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH));
    636             mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH));
    637             mMonthSpinner.setWrapSelectorWheel(false);
    638         } else {
    639             mDaySpinner.setMinValue(1);
    640             mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
    641             mDaySpinner.setWrapSelectorWheel(true);
    642             mMonthSpinner.setDisplayedValues(null);
    643             mMonthSpinner.setMinValue(0);
    644             mMonthSpinner.setMaxValue(11);
    645             mMonthSpinner.setWrapSelectorWheel(true);
    646         }
    647 
    648         // make sure the month names are a zero based array
    649         // with the months in the month spinner
    650         String[] displayedValues = Arrays.copyOfRange(mShortMonths,
    651                 mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1);
    652         mMonthSpinner.setDisplayedValues(displayedValues);
    653 
    654         // year spinner range does not change based on the current date
    655         mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR));
    656         mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR));
    657         mYearSpinner.setWrapSelectorWheel(false);
    658 
    659         // set the spinner values
    660         mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR));
    661         mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH));
    662         mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
    663     }
    664 
    665     /**
    666      * Updates the calendar view with the current date.
    667      */
    668     private void updateCalendarView() {
    669          mCalendarView.setDate(mCurrentDate.getTimeInMillis(), false, false);
    670     }
    671 
    672     /**
    673      * @return The selected year.
    674      */
    675     public int getYear() {
    676         return mCurrentDate.get(Calendar.YEAR);
    677     }
    678 
    679     /**
    680      * @return The selected month.
    681      */
    682     public int getMonth() {
    683         return mCurrentDate.get(Calendar.MONTH);
    684     }
    685 
    686     /**
    687      * @return The selected day of month.
    688      */
    689     public int getDayOfMonth() {
    690         return mCurrentDate.get(Calendar.DAY_OF_MONTH);
    691     }
    692 
    693     /**
    694      * Notifies the listener, if such, for a change in the selected date.
    695      */
    696     private void notifyDateChanged() {
    697         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
    698         if (mOnDateChangedListener != null) {
    699             mOnDateChangedListener.onDateChanged(this, getYear(), getMonth(), getDayOfMonth());
    700         }
    701     }
    702 
    703     /**
    704      * Sets the IME options for a spinner based on its ordering.
    705      *
    706      * @param spinner The spinner.
    707      * @param spinnerCount The total spinner count.
    708      * @param spinnerIndex The index of the given spinner.
    709      */
    710     private void setImeOptions(NumberPicker spinner, int spinnerCount, int spinnerIndex) {
    711         final int imeOptions;
    712         if (spinnerIndex < spinnerCount - 1) {
    713             imeOptions = EditorInfo.IME_ACTION_NEXT;
    714         } else {
    715             imeOptions = EditorInfo.IME_ACTION_DONE;
    716         }
    717         TextView input = (TextView) spinner.findViewById(R.id.numberpicker_input);
    718         input.setImeOptions(imeOptions);
    719     }
    720 
    721     private void setContentDescriptions() {
    722         // Day
    723         trySetContentDescription(mDaySpinner, R.id.increment,
    724                 R.string.date_picker_increment_day_button);
    725         trySetContentDescription(mDaySpinner, R.id.decrement,
    726                 R.string.date_picker_decrement_day_button);
    727         // Month
    728         trySetContentDescription(mMonthSpinner, R.id.increment,
    729                 R.string.date_picker_increment_month_button);
    730         trySetContentDescription(mMonthSpinner, R.id.decrement,
    731                 R.string.date_picker_decrement_month_button);
    732         // Year
    733         trySetContentDescription(mYearSpinner, R.id.increment,
    734                 R.string.date_picker_increment_year_button);
    735         trySetContentDescription(mYearSpinner, R.id.decrement,
    736                 R.string.date_picker_decrement_year_button);
    737     }
    738 
    739     private void trySetContentDescription(View root, int viewId, int contDescResId) {
    740         View target = root.findViewById(viewId);
    741         if (target != null) {
    742             target.setContentDescription(mContext.getString(contDescResId));
    743         }
    744     }
    745 
    746     private void updateInputState() {
    747         // Make sure that if the user changes the value and the IME is active
    748         // for one of the inputs if this widget, the IME is closed. If the user
    749         // changed the value via the IME and there is a next input the IME will
    750         // be shown, otherwise the user chose another means of changing the
    751         // value and having the IME up makes no sense.
    752         InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
    753         if (inputMethodManager != null) {
    754             if (inputMethodManager.isActive(mYearSpinnerInput)) {
    755                 mYearSpinnerInput.clearFocus();
    756                 inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
    757             } else if (inputMethodManager.isActive(mMonthSpinnerInput)) {
    758                 mMonthSpinnerInput.clearFocus();
    759                 inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
    760             } else if (inputMethodManager.isActive(mDaySpinnerInput)) {
    761                 mDaySpinnerInput.clearFocus();
    762                 inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
    763             }
    764         }
    765     }
    766 
    767     /**
    768      * Class for managing state storing/restoring.
    769      */
    770     private static class SavedState extends BaseSavedState {
    771 
    772         private final int mYear;
    773 
    774         private final int mMonth;
    775 
    776         private final int mDay;
    777 
    778         /**
    779          * Constructor called from {@link DatePicker#onSaveInstanceState()}
    780          */
    781         private SavedState(Parcelable superState, int year, int month, int day) {
    782             super(superState);
    783             mYear = year;
    784             mMonth = month;
    785             mDay = day;
    786         }
    787 
    788         /**
    789          * Constructor called from {@link #CREATOR}
    790          */
    791         private SavedState(Parcel in) {
    792             super(in);
    793             mYear = in.readInt();
    794             mMonth = in.readInt();
    795             mDay = in.readInt();
    796         }
    797 
    798         @Override
    799         public void writeToParcel(Parcel dest, int flags) {
    800             super.writeToParcel(dest, flags);
    801             dest.writeInt(mYear);
    802             dest.writeInt(mMonth);
    803             dest.writeInt(mDay);
    804         }
    805 
    806         @SuppressWarnings("all")
    807         // suppress unused and hiding
    808         public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
    809 
    810             public SavedState createFromParcel(Parcel in) {
    811                 return new SavedState(in);
    812             }
    813 
    814             public SavedState[] newArray(int size) {
    815                 return new SavedState[size];
    816             }
    817         };
    818     }
    819 }
    820