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