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