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
17package android.widget;
18
19import android.content.Context;
20import android.content.res.Configuration;
21import android.content.res.TypedArray;
22import android.icu.util.Calendar;
23import android.os.Parcelable;
24import android.text.InputType;
25import android.text.TextUtils;
26import android.text.format.DateFormat;
27import android.util.AttributeSet;
28import android.view.LayoutInflater;
29import android.view.View;
30import android.view.accessibility.AccessibilityEvent;
31import android.view.inputmethod.EditorInfo;
32import android.view.inputmethod.InputMethodManager;
33import android.widget.DatePicker.AbstractDatePickerDelegate;
34import android.widget.NumberPicker.OnValueChangeListener;
35
36import libcore.icu.ICU;
37
38import java.text.DateFormatSymbols;
39import java.text.ParseException;
40import java.text.SimpleDateFormat;
41import java.util.Arrays;
42import java.util.Locale;
43
44/**
45 * A delegate implementing the basic DatePicker
46 */
47class 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