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