Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.widget;
     18 
     19 import android.content.Context;
     20 import android.content.res.Configuration;
     21 import android.content.res.TypedArray;
     22 import android.icu.util.Calendar;
     23 import android.os.Parcelable;
     24 import android.text.InputType;
     25 import android.text.TextUtils;
     26 import android.text.format.DateFormat;
     27 import android.util.AttributeSet;
     28 import android.view.LayoutInflater;
     29 import android.view.View;
     30 import android.view.accessibility.AccessibilityEvent;
     31 import android.view.inputmethod.EditorInfo;
     32 import android.view.inputmethod.InputMethodManager;
     33 import android.widget.DatePicker.AbstractDatePickerDelegate;
     34 import android.widget.NumberPicker.OnValueChangeListener;
     35 
     36 import libcore.icu.ICU;
     37 
     38 import java.text.DateFormatSymbols;
     39 import java.text.ParseException;
     40 import java.text.SimpleDateFormat;
     41 import java.util.Arrays;
     42 import java.util.Locale;
     43 
     44 /**
     45  * A delegate implementing the basic DatePicker
     46  */
     47 class DatePickerSpinnerDelegate extends AbstractDatePickerDelegate {
     48 
     49     private static final String DATE_FORMAT = "MM/dd/yyyy";
     50 
     51     private static final int DEFAULT_START_YEAR = 1900;
     52 
     53     private static final int DEFAULT_END_YEAR = 2100;
     54 
     55     private static final boolean DEFAULT_CALENDAR_VIEW_SHOWN = true;
     56 
     57     private static final boolean DEFAULT_SPINNERS_SHOWN = true;
     58 
     59     private static final boolean DEFAULT_ENABLED_STATE = true;
     60 
     61     private final LinearLayout mSpinners;
     62 
     63     private final NumberPicker mDaySpinner;
     64 
     65     private final NumberPicker mMonthSpinner;
     66 
     67     private final NumberPicker mYearSpinner;
     68 
     69     private final EditText mDaySpinnerInput;
     70 
     71     private final EditText mMonthSpinnerInput;
     72 
     73     private final EditText mYearSpinnerInput;
     74 
     75     private final CalendarView mCalendarView;
     76 
     77     private String[] mShortMonths;
     78 
     79     private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT);
     80 
     81     private int mNumberOfMonths;
     82 
     83     private Calendar mTempDate;
     84 
     85     private Calendar mMinDate;
     86 
     87     private Calendar mMaxDate;
     88 
     89     private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
     90 
     91     DatePickerSpinnerDelegate(DatePicker delegator, Context context, AttributeSet attrs,
     92             int defStyleAttr, int defStyleRes) {
     93         super(delegator, context);
     94 
     95         mDelegator = delegator;
     96         mContext = context;
     97 
     98         // initialization based on locale
     99         setCurrentLocale(Locale.getDefault());
    100 
    101         final TypedArray attributesArray = context.obtainStyledAttributes(attrs,
    102                 com.android.internal.R.styleable.DatePicker, defStyleAttr, defStyleRes);
    103         boolean spinnersShown = attributesArray.getBoolean(com.android.internal.R.styleable.DatePicker_spinnersShown,
    104                 DEFAULT_SPINNERS_SHOWN);
    105         boolean calendarViewShown = attributesArray.getBoolean(
    106                 com.android.internal.R.styleable.DatePicker_calendarViewShown, DEFAULT_CALENDAR_VIEW_SHOWN);
    107         int startYear = attributesArray.getInt(com.android.internal.R.styleable.DatePicker_startYear,
    108                 DEFAULT_START_YEAR);
    109         int endYear = attributesArray.getInt(com.android.internal.R.styleable.DatePicker_endYear, DEFAULT_END_YEAR);
    110         String minDate = attributesArray.getString(com.android.internal.R.styleable.DatePicker_minDate);
    111         String maxDate = attributesArray.getString(com.android.internal.R.styleable.DatePicker_maxDate);
    112         int layoutResourceId = attributesArray.getResourceId(
    113                 com.android.internal.R.styleable.DatePicker_legacyLayout, com.android.internal.R.layout.date_picker_legacy);
    114         attributesArray.recycle();
    115 
    116         LayoutInflater inflater = (LayoutInflater) context
    117                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    118         final View view = inflater.inflate(layoutResourceId, mDelegator, true);
    119         view.setSaveFromParentEnabled(false);
    120 
    121         OnValueChangeListener onChangeListener = new OnValueChangeListener() {
    122             public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
    123                 updateInputState();
    124                 mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis());
    125                 // take care of wrapping of days and months to update greater fields
    126                 if (picker == mDaySpinner) {
    127                     int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH);
    128                     if (oldVal == maxDayOfMonth && newVal == 1) {
    129                         mTempDate.add(Calendar.DAY_OF_MONTH, 1);
    130                     } else if (oldVal == 1 && newVal == maxDayOfMonth) {
    131                         mTempDate.add(Calendar.DAY_OF_MONTH, -1);
    132                     } else {
    133                         mTempDate.add(Calendar.DAY_OF_MONTH, newVal - oldVal);
    134                     }
    135                 } else if (picker == mMonthSpinner) {
    136                     if (oldVal == 11 && newVal == 0) {
    137                         mTempDate.add(Calendar.MONTH, 1);
    138                     } else if (oldVal == 0 && newVal == 11) {
    139                         mTempDate.add(Calendar.MONTH, -1);
    140                     } else {
    141                         mTempDate.add(Calendar.MONTH, newVal - oldVal);
    142                     }
    143                 } else if (picker == mYearSpinner) {
    144                     mTempDate.set(Calendar.YEAR, newVal);
    145                 } else {
    146                     throw new IllegalArgumentException();
    147                 }
    148                 // now set the date to the adjusted one
    149                 setDate(mTempDate.get(Calendar.YEAR), mTempDate.get(Calendar.MONTH),
    150                         mTempDate.get(Calendar.DAY_OF_MONTH));
    151                 updateSpinners();
    152                 updateCalendarView();
    153                 notifyDateChanged();
    154             }
    155         };
    156 
    157         mSpinners = (LinearLayout) mDelegator.findViewById(com.android.internal.R.id.pickers);
    158 
    159         // calendar view day-picker
    160         mCalendarView = (CalendarView) mDelegator.findViewById(com.android.internal.R.id.calendar_view);
    161         mCalendarView.setOnDateChangeListener(new CalendarView.OnDateChangeListener() {
    162             public void onSelectedDayChange(CalendarView view, int year, int month, int monthDay) {
    163                 setDate(year, month, monthDay);
    164                 updateSpinners();
    165                 notifyDateChanged();
    166             }
    167         });
    168 
    169         // day
    170         mDaySpinner = (NumberPicker) mDelegator.findViewById(com.android.internal.R.id.day);
    171         mDaySpinner.setFormatter(NumberPicker.getTwoDigitFormatter());
    172         mDaySpinner.setOnLongPressUpdateInterval(100);
    173         mDaySpinner.setOnValueChangedListener(onChangeListener);
    174         mDaySpinnerInput = (EditText) mDaySpinner.findViewById(com.android.internal.R.id.numberpicker_input);
    175 
    176         // month
    177         mMonthSpinner = (NumberPicker) mDelegator.findViewById(com.android.internal.R.id.month);
    178         mMonthSpinner.setMinValue(0);
    179         mMonthSpinner.setMaxValue(mNumberOfMonths - 1);
    180         mMonthSpinner.setDisplayedValues(mShortMonths);
    181         mMonthSpinner.setOnLongPressUpdateInterval(200);
    182         mMonthSpinner.setOnValueChangedListener(onChangeListener);
    183         mMonthSpinnerInput = (EditText) mMonthSpinner.findViewById(com.android.internal.R.id.numberpicker_input);
    184 
    185         // year
    186         mYearSpinner = (NumberPicker) mDelegator.findViewById(com.android.internal.R.id.year);
    187         mYearSpinner.setOnLongPressUpdateInterval(100);
    188         mYearSpinner.setOnValueChangedListener(onChangeListener);
    189         mYearSpinnerInput = (EditText) mYearSpinner.findViewById(com.android.internal.R.id.numberpicker_input);
    190 
    191         // show only what the user required but make sure we
    192         // show something and the spinners have higher priority
    193         if (!spinnersShown && !calendarViewShown) {
    194             setSpinnersShown(true);
    195         } else {
    196             setSpinnersShown(spinnersShown);
    197             setCalendarViewShown(calendarViewShown);
    198         }
    199 
    200         // set the min date giving priority of the minDate over startYear
    201         mTempDate.clear();
    202         if (!TextUtils.isEmpty(minDate)) {
    203             if (!parseDate(minDate, mTempDate)) {
    204                 mTempDate.set(startYear, 0, 1);
    205             }
    206         } else {
    207             mTempDate.set(startYear, 0, 1);
    208         }
    209         setMinDate(mTempDate.getTimeInMillis());
    210 
    211         // set the max date giving priority of the maxDate over endYear
    212         mTempDate.clear();
    213         if (!TextUtils.isEmpty(maxDate)) {
    214             if (!parseDate(maxDate, mTempDate)) {
    215                 mTempDate.set(endYear, 11, 31);
    216             }
    217         } else {
    218             mTempDate.set(endYear, 11, 31);
    219         }
    220         setMaxDate(mTempDate.getTimeInMillis());
    221 
    222         // initialize to current date
    223         mCurrentDate.setTimeInMillis(System.currentTimeMillis());
    224         init(mCurrentDate.get(Calendar.YEAR), mCurrentDate.get(Calendar.MONTH), mCurrentDate
    225                 .get(Calendar.DAY_OF_MONTH), null);
    226 
    227         // re-order the number spinners to match the current date format
    228         reorderSpinners();
    229 
    230         // accessibility
    231         setContentDescriptions();
    232 
    233         // If not explicitly specified this view is important for accessibility.
    234         if (mDelegator.getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
    235             mDelegator.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
    236         }
    237     }
    238 
    239     @Override
    240     public void init(int year, int monthOfYear, int dayOfMonth,
    241                      DatePicker.OnDateChangedListener onDateChangedListener) {
    242         setDate(year, monthOfYear, dayOfMonth);
    243         updateSpinners();
    244         updateCalendarView();
    245 
    246         mOnDateChangedListener = onDateChangedListener;
    247     }
    248 
    249     @Override
    250     public void updateDate(int year, int month, int dayOfMonth) {
    251         if (!isNewDate(year, month, dayOfMonth)) {
    252             return;
    253         }
    254         setDate(year, month, dayOfMonth);
    255         updateSpinners();
    256         updateCalendarView();
    257         notifyDateChanged();
    258     }
    259 
    260     @Override
    261     public int getYear() {
    262         return mCurrentDate.get(Calendar.YEAR);
    263     }
    264 
    265     @Override
    266     public int getMonth() {
    267         return mCurrentDate.get(Calendar.MONTH);
    268     }
    269 
    270     @Override
    271     public int getDayOfMonth() {
    272         return mCurrentDate.get(Calendar.DAY_OF_MONTH);
    273     }
    274 
    275     @Override
    276     public void setFirstDayOfWeek(int firstDayOfWeek) {
    277         mCalendarView.setFirstDayOfWeek(firstDayOfWeek);
    278     }
    279 
    280     @Override
    281     public int getFirstDayOfWeek() {
    282         return mCalendarView.getFirstDayOfWeek();
    283     }
    284 
    285     @Override
    286     public void setMinDate(long minDate) {
    287         mTempDate.setTimeInMillis(minDate);
    288         if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
    289                 && mTempDate.get(Calendar.DAY_OF_YEAR) == mMinDate.get(Calendar.DAY_OF_YEAR)) {
    290             // Same day, no-op.
    291             return;
    292         }
    293         mMinDate.setTimeInMillis(minDate);
    294         mCalendarView.setMinDate(minDate);
    295         if (mCurrentDate.before(mMinDate)) {
    296             mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
    297             updateCalendarView();
    298         }
    299         updateSpinners();
    300     }
    301 
    302     @Override
    303     public Calendar getMinDate() {
    304         final Calendar minDate = Calendar.getInstance();
    305         minDate.setTimeInMillis(mCalendarView.getMinDate());
    306         return minDate;
    307     }
    308 
    309     @Override
    310     public void setMaxDate(long maxDate) {
    311         mTempDate.setTimeInMillis(maxDate);
    312         if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
    313                 && mTempDate.get(Calendar.DAY_OF_YEAR) == mMaxDate.get(Calendar.DAY_OF_YEAR)) {
    314             // Same day, no-op.
    315             return;
    316         }
    317         mMaxDate.setTimeInMillis(maxDate);
    318         mCalendarView.setMaxDate(maxDate);
    319         if (mCurrentDate.after(mMaxDate)) {
    320             mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
    321             updateCalendarView();
    322         }
    323         updateSpinners();
    324     }
    325 
    326     @Override
    327     public Calendar getMaxDate() {
    328         final Calendar maxDate = Calendar.getInstance();
    329         maxDate.setTimeInMillis(mCalendarView.getMaxDate());
    330         return maxDate;
    331     }
    332 
    333     @Override
    334     public void setEnabled(boolean enabled) {
    335         mDaySpinner.setEnabled(enabled);
    336         mMonthSpinner.setEnabled(enabled);
    337         mYearSpinner.setEnabled(enabled);
    338         mCalendarView.setEnabled(enabled);
    339         mIsEnabled = enabled;
    340     }
    341 
    342     @Override
    343     public boolean isEnabled() {
    344         return mIsEnabled;
    345     }
    346 
    347     @Override
    348     public CalendarView getCalendarView() {
    349         return mCalendarView;
    350     }
    351 
    352     @Override
    353     public void setCalendarViewShown(boolean shown) {
    354         mCalendarView.setVisibility(shown ? View.VISIBLE : View.GONE);
    355     }
    356 
    357     @Override
    358     public boolean getCalendarViewShown() {
    359         return (mCalendarView.getVisibility() == View.VISIBLE);
    360     }
    361 
    362     @Override
    363     public void setSpinnersShown(boolean shown) {
    364         mSpinners.setVisibility(shown ? View.VISIBLE : View.GONE);
    365     }
    366 
    367     @Override
    368     public boolean getSpinnersShown() {
    369         return mSpinners.isShown();
    370     }
    371 
    372     @Override
    373     public void onConfigurationChanged(Configuration newConfig) {
    374         setCurrentLocale(newConfig.locale);
    375     }
    376 
    377     @Override
    378     public Parcelable onSaveInstanceState(Parcelable superState) {
    379         return new SavedState(superState, getYear(), getMonth(), getDayOfMonth(),
    380                 getMinDate().getTimeInMillis(), getMaxDate().getTimeInMillis());
    381     }
    382 
    383     @Override
    384     public void onRestoreInstanceState(Parcelable state) {
    385         if (state instanceof SavedState) {
    386             final SavedState ss = (SavedState) state;
    387             setDate(ss.getSelectedYear(), ss.getSelectedMonth(), ss.getSelectedDay());
    388             updateSpinners();
    389             updateCalendarView();
    390         }
    391     }
    392 
    393     @Override
    394     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
    395         onPopulateAccessibilityEvent(event);
    396         return true;
    397     }
    398 
    399     /**
    400      * Sets the current locale.
    401      *
    402      * @param locale The current locale.
    403      */
    404     @Override
    405     protected void setCurrentLocale(Locale locale) {
    406         super.setCurrentLocale(locale);
    407 
    408         mTempDate = getCalendarForLocale(mTempDate, locale);
    409         mMinDate = getCalendarForLocale(mMinDate, locale);
    410         mMaxDate = getCalendarForLocale(mMaxDate, locale);
    411         mCurrentDate = getCalendarForLocale(mCurrentDate, locale);
    412 
    413         mNumberOfMonths = mTempDate.getActualMaximum(Calendar.MONTH) + 1;
    414         mShortMonths = new DateFormatSymbols().getShortMonths();
    415 
    416         if (usingNumericMonths()) {
    417             // We're in a locale where a date should either be all-numeric, or all-text.
    418             // All-text would require custom NumberPicker formatters for day and year.
    419             mShortMonths = new String[mNumberOfMonths];
    420             for (int i = 0; i < mNumberOfMonths; ++i) {
    421                 mShortMonths[i] = String.format("%d", i + 1);
    422             }
    423         }
    424     }
    425 
    426     /**
    427      * Tests whether the current locale is one where there are no real month names,
    428      * such as Chinese, Japanese, or Korean locales.
    429      */
    430     private boolean usingNumericMonths() {
    431         return Character.isDigit(mShortMonths[Calendar.JANUARY].charAt(0));
    432     }
    433 
    434     /**
    435      * Gets a calendar for locale bootstrapped with the value of a given calendar.
    436      *
    437      * @param oldCalendar The old calendar.
    438      * @param locale The locale.
    439      */
    440     private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
    441         if (oldCalendar == null) {
    442             return Calendar.getInstance(locale);
    443         } else {
    444             final long currentTimeMillis = oldCalendar.getTimeInMillis();
    445             Calendar newCalendar = Calendar.getInstance(locale);
    446             newCalendar.setTimeInMillis(currentTimeMillis);
    447             return newCalendar;
    448         }
    449     }
    450 
    451     /**
    452      * Reorders the spinners according to the date format that is
    453      * explicitly set by the user and if no such is set fall back
    454      * to the current locale's default format.
    455      */
    456     private void reorderSpinners() {
    457         mSpinners.removeAllViews();
    458         // We use numeric spinners for year and day, but textual months. Ask icu4c what
    459         // order the user's locale uses for that combination. http://b/7207103.
    460         String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), "yyyyMMMdd");
    461         char[] order = ICU.getDateFormatOrder(pattern);
    462         final int spinnerCount = order.length;
    463         for (int i = 0; i < spinnerCount; i++) {
    464             switch (order[i]) {
    465                 case 'd':
    466                     mSpinners.addView(mDaySpinner);
    467                     setImeOptions(mDaySpinner, spinnerCount, i);
    468                     break;
    469                 case 'M':
    470                     mSpinners.addView(mMonthSpinner);
    471                     setImeOptions(mMonthSpinner, spinnerCount, i);
    472                     break;
    473                 case 'y':
    474                     mSpinners.addView(mYearSpinner);
    475                     setImeOptions(mYearSpinner, spinnerCount, i);
    476                     break;
    477                 default:
    478                     throw new IllegalArgumentException(Arrays.toString(order));
    479             }
    480         }
    481     }
    482 
    483     /**
    484      * Parses the given <code>date</code> and in case of success sets the result
    485      * to the <code>outDate</code>.
    486      *
    487      * @return True if the date was parsed.
    488      */
    489     private boolean parseDate(String date, Calendar outDate) {
    490         try {
    491             outDate.setTime(mDateFormat.parse(date));
    492             return true;
    493         } catch (ParseException e) {
    494             e.printStackTrace();
    495             return false;
    496         }
    497     }
    498 
    499     private boolean isNewDate(int year, int month, int dayOfMonth) {
    500         return (mCurrentDate.get(Calendar.YEAR) != year
    501                 || mCurrentDate.get(Calendar.MONTH) != month
    502                 || mCurrentDate.get(Calendar.DAY_OF_MONTH) != dayOfMonth);
    503     }
    504 
    505     private void setDate(int year, int month, int dayOfMonth) {
    506         mCurrentDate.set(year, month, dayOfMonth);
    507         resetAutofilledValue();
    508         if (mCurrentDate.before(mMinDate)) {
    509             mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
    510         } else if (mCurrentDate.after(mMaxDate)) {
    511             mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
    512         }
    513     }
    514 
    515     private void updateSpinners() {
    516         // set the spinner ranges respecting the min and max dates
    517         if (mCurrentDate.equals(mMinDate)) {
    518             mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
    519             mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
    520             mDaySpinner.setWrapSelectorWheel(false);
    521             mMonthSpinner.setDisplayedValues(null);
    522             mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH));
    523             mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH));
    524             mMonthSpinner.setWrapSelectorWheel(false);
    525         } else if (mCurrentDate.equals(mMaxDate)) {
    526             mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH));
    527             mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
    528             mDaySpinner.setWrapSelectorWheel(false);
    529             mMonthSpinner.setDisplayedValues(null);
    530             mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH));
    531             mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH));
    532             mMonthSpinner.setWrapSelectorWheel(false);
    533         } else {
    534             mDaySpinner.setMinValue(1);
    535             mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
    536             mDaySpinner.setWrapSelectorWheel(true);
    537             mMonthSpinner.setDisplayedValues(null);
    538             mMonthSpinner.setMinValue(0);
    539             mMonthSpinner.setMaxValue(11);
    540             mMonthSpinner.setWrapSelectorWheel(true);
    541         }
    542 
    543         // make sure the month names are a zero based array
    544         // with the months in the month spinner
    545         String[] displayedValues = Arrays.copyOfRange(mShortMonths,
    546                 mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1);
    547         mMonthSpinner.setDisplayedValues(displayedValues);
    548 
    549         // year spinner range does not change based on the current date
    550         mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR));
    551         mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR));
    552         mYearSpinner.setWrapSelectorWheel(false);
    553 
    554         // set the spinner values
    555         mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR));
    556         mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH));
    557         mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
    558 
    559         if (usingNumericMonths()) {
    560             mMonthSpinnerInput.setRawInputType(InputType.TYPE_CLASS_NUMBER);
    561         }
    562     }
    563 
    564     /**
    565      * Updates the calendar view with the current date.
    566      */
    567     private void updateCalendarView() {
    568         mCalendarView.setDate(mCurrentDate.getTimeInMillis(), false, false);
    569     }
    570 
    571 
    572     /**
    573      * Notifies the listener, if such, for a change in the selected date.
    574      */
    575     private void notifyDateChanged() {
    576         mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
    577         if (mOnDateChangedListener != null) {
    578             mOnDateChangedListener.onDateChanged(mDelegator, getYear(), getMonth(),
    579                     getDayOfMonth());
    580         }
    581         if (mAutoFillChangeListener != null) {
    582             mAutoFillChangeListener.onDateChanged(mDelegator, getYear(), getMonth(),
    583                     getDayOfMonth());
    584         }
    585     }
    586 
    587     /**
    588      * Sets the IME options for a spinner based on its ordering.
    589      *
    590      * @param spinner The spinner.
    591      * @param spinnerCount The total spinner count.
    592      * @param spinnerIndex The index of the given spinner.
    593      */
    594     private void setImeOptions(NumberPicker spinner, int spinnerCount, int spinnerIndex) {
    595         final int imeOptions;
    596         if (spinnerIndex < spinnerCount - 1) {
    597             imeOptions = EditorInfo.IME_ACTION_NEXT;
    598         } else {
    599             imeOptions = EditorInfo.IME_ACTION_DONE;
    600         }
    601         TextView input = (TextView) spinner.findViewById(com.android.internal.R.id.numberpicker_input);
    602         input.setImeOptions(imeOptions);
    603     }
    604 
    605     private void setContentDescriptions() {
    606         // Day
    607         trySetContentDescription(mDaySpinner, com.android.internal.R.id.increment,
    608                 com.android.internal.R.string.date_picker_increment_day_button);
    609         trySetContentDescription(mDaySpinner, com.android.internal.R.id.decrement,
    610                 com.android.internal.R.string.date_picker_decrement_day_button);
    611         // Month
    612         trySetContentDescription(mMonthSpinner, com.android.internal.R.id.increment,
    613                 com.android.internal.R.string.date_picker_increment_month_button);
    614         trySetContentDescription(mMonthSpinner, com.android.internal.R.id.decrement,
    615                 com.android.internal.R.string.date_picker_decrement_month_button);
    616         // Year
    617         trySetContentDescription(mYearSpinner, com.android.internal.R.id.increment,
    618                 com.android.internal.R.string.date_picker_increment_year_button);
    619         trySetContentDescription(mYearSpinner, com.android.internal.R.id.decrement,
    620                 com.android.internal.R.string.date_picker_decrement_year_button);
    621     }
    622 
    623     private void trySetContentDescription(View root, int viewId, int contDescResId) {
    624         View target = root.findViewById(viewId);
    625         if (target != null) {
    626             target.setContentDescription(mContext.getString(contDescResId));
    627         }
    628     }
    629 
    630     private void updateInputState() {
    631         // Make sure that if the user changes the value and the IME is active
    632         // for one of the inputs if this widget, the IME is closed. If the user
    633         // changed the value via the IME and there is a next input the IME will
    634         // be shown, otherwise the user chose another means of changing the
    635         // value and having the IME up makes no sense.
    636         InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
    637         if (inputMethodManager != null) {
    638             if (inputMethodManager.isActive(mYearSpinnerInput)) {
    639                 mYearSpinnerInput.clearFocus();
    640                 inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
    641             } else if (inputMethodManager.isActive(mMonthSpinnerInput)) {
    642                 mMonthSpinnerInput.clearFocus();
    643                 inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
    644             } else if (inputMethodManager.isActive(mDaySpinnerInput)) {
    645                 mDaySpinnerInput.clearFocus();
    646                 inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
    647             }
    648         }
    649     }
    650 }
    651