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