1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
19import android.annotation.Widget;
20import android.content.Context;
21import android.content.res.Configuration;
22import android.content.res.TypedArray;
23import android.os.Parcel;
24import android.os.Parcelable;
25import android.text.format.DateFormat;
26import android.text.format.DateUtils;
27import android.util.AttributeSet;
28import android.view.LayoutInflater;
29import android.view.View;
30import android.view.ViewGroup;
31import android.view.accessibility.AccessibilityEvent;
32import android.view.accessibility.AccessibilityNodeInfo;
33import android.view.inputmethod.EditorInfo;
34import android.view.inputmethod.InputMethodManager;
35import android.widget.NumberPicker.OnValueChangeListener;
36
37import com.android.internal.R;
38
39import java.text.DateFormatSymbols;
40import java.util.Calendar;
41import java.util.Locale;
42
43/**
44 * A view for selecting the time of day, in either 24 hour or AM/PM mode. The
45 * hour, each minute digit, and AM/PM (if applicable) can be conrolled by
46 * vertical spinners. The hour can be entered by keyboard input. Entering in two
47 * digit hours can be accomplished by hitting two digits within a timeout of
48 * about a second (e.g. '1' then '2' to select 12). The minutes can be entered
49 * by entering single digits. Under AM/PM mode, the user can hit 'a', 'A", 'p'
50 * or 'P' to pick. For a dialog using this view, see
51 * {@link android.app.TimePickerDialog}.
52 *<p>
53 * See the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a>
54 * guide.
55 * </p>
56 */
57@Widget
58public class TimePicker extends FrameLayout {
59
60    private static final boolean DEFAULT_ENABLED_STATE = true;
61
62    private static final int HOURS_IN_HALF_DAY = 12;
63
64    /**
65     * A no-op callback used in the constructor to avoid null checks later in
66     * the code.
67     */
68    private static final OnTimeChangedListener NO_OP_CHANGE_LISTENER = new OnTimeChangedListener() {
69        public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
70        }
71    };
72
73    // state
74    private boolean mIs24HourView;
75
76    private boolean mIsAm;
77
78    // ui components
79    private final NumberPicker mHourSpinner;
80
81    private final NumberPicker mMinuteSpinner;
82
83    private final NumberPicker mAmPmSpinner;
84
85    private final EditText mHourSpinnerInput;
86
87    private final EditText mMinuteSpinnerInput;
88
89    private final EditText mAmPmSpinnerInput;
90
91    private final TextView mDivider;
92
93    // Note that the legacy implementation of the TimePicker is
94    // using a button for toggling between AM/PM while the new
95    // version uses a NumberPicker spinner. Therefore the code
96    // accommodates these two cases to be backwards compatible.
97    private final Button mAmPmButton;
98
99    private final String[] mAmPmStrings;
100
101    private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
102
103    // callbacks
104    private OnTimeChangedListener mOnTimeChangedListener;
105
106    private Calendar mTempCalendar;
107
108    private Locale mCurrentLocale;
109
110    private boolean mHourWithTwoDigit;
111    private char mHourFormat;
112
113    /**
114     * The callback interface used to indicate the time has been adjusted.
115     */
116    public interface OnTimeChangedListener {
117
118        /**
119         * @param view The view associated with this listener.
120         * @param hourOfDay The current hour.
121         * @param minute The current minute.
122         */
123        void onTimeChanged(TimePicker view, int hourOfDay, int minute);
124    }
125
126    public TimePicker(Context context) {
127        this(context, null);
128    }
129
130    public TimePicker(Context context, AttributeSet attrs) {
131        this(context, attrs, R.attr.timePickerStyle);
132    }
133
134    public TimePicker(Context context, AttributeSet attrs, int defStyle) {
135        super(context, attrs, defStyle);
136
137        // initialization based on locale
138        setCurrentLocale(Locale.getDefault());
139
140        // process style attributes
141        TypedArray attributesArray = context.obtainStyledAttributes(
142                attrs, R.styleable.TimePicker, defStyle, 0);
143        int layoutResourceId = attributesArray.getResourceId(
144                R.styleable.TimePicker_internalLayout, R.layout.time_picker);
145        attributesArray.recycle();
146
147        LayoutInflater inflater = (LayoutInflater) context.getSystemService(
148                Context.LAYOUT_INFLATER_SERVICE);
149        inflater.inflate(layoutResourceId, this, true);
150
151        // hour
152        mHourSpinner = (NumberPicker) findViewById(R.id.hour);
153        mHourSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
154            public void onValueChange(NumberPicker spinner, int oldVal, int newVal) {
155                updateInputState();
156                if (!is24HourView()) {
157                    if ((oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY)
158                            || (oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1)) {
159                        mIsAm = !mIsAm;
160                        updateAmPmControl();
161                    }
162                }
163                onTimeChanged();
164            }
165        });
166        mHourSpinnerInput = (EditText) mHourSpinner.findViewById(R.id.numberpicker_input);
167        mHourSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
168
169        // divider (only for the new widget style)
170        mDivider = (TextView) findViewById(R.id.divider);
171        if (mDivider != null) {
172            setDividerText();
173        }
174
175        // minute
176        mMinuteSpinner = (NumberPicker) findViewById(R.id.minute);
177        mMinuteSpinner.setMinValue(0);
178        mMinuteSpinner.setMaxValue(59);
179        mMinuteSpinner.setOnLongPressUpdateInterval(100);
180        mMinuteSpinner.setFormatter(NumberPicker.getTwoDigitFormatter());
181        mMinuteSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
182            public void onValueChange(NumberPicker spinner, int oldVal, int newVal) {
183                updateInputState();
184                int minValue = mMinuteSpinner.getMinValue();
185                int maxValue = mMinuteSpinner.getMaxValue();
186                if (oldVal == maxValue && newVal == minValue) {
187                    int newHour = mHourSpinner.getValue() + 1;
188                    if (!is24HourView() && newHour == HOURS_IN_HALF_DAY) {
189                        mIsAm = !mIsAm;
190                        updateAmPmControl();
191                    }
192                    mHourSpinner.setValue(newHour);
193                } else if (oldVal == minValue && newVal == maxValue) {
194                    int newHour = mHourSpinner.getValue() - 1;
195                    if (!is24HourView() && newHour == HOURS_IN_HALF_DAY - 1) {
196                        mIsAm = !mIsAm;
197                        updateAmPmControl();
198                    }
199                    mHourSpinner.setValue(newHour);
200                }
201                onTimeChanged();
202            }
203        });
204        mMinuteSpinnerInput = (EditText) mMinuteSpinner.findViewById(R.id.numberpicker_input);
205        mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
206
207        /* Get the localized am/pm strings and use them in the spinner */
208        mAmPmStrings = new DateFormatSymbols().getAmPmStrings();
209
210        // am/pm
211        View amPmView = findViewById(R.id.amPm);
212        if (amPmView instanceof Button) {
213            mAmPmSpinner = null;
214            mAmPmSpinnerInput = null;
215            mAmPmButton = (Button) amPmView;
216            mAmPmButton.setOnClickListener(new OnClickListener() {
217                public void onClick(View button) {
218                    button.requestFocus();
219                    mIsAm = !mIsAm;
220                    updateAmPmControl();
221                    onTimeChanged();
222                }
223            });
224        } else {
225            mAmPmButton = null;
226            mAmPmSpinner = (NumberPicker) amPmView;
227            mAmPmSpinner.setMinValue(0);
228            mAmPmSpinner.setMaxValue(1);
229            mAmPmSpinner.setDisplayedValues(mAmPmStrings);
230            mAmPmSpinner.setOnValueChangedListener(new OnValueChangeListener() {
231                public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
232                    updateInputState();
233                    picker.requestFocus();
234                    mIsAm = !mIsAm;
235                    updateAmPmControl();
236                    onTimeChanged();
237                }
238            });
239            mAmPmSpinnerInput = (EditText) mAmPmSpinner.findViewById(R.id.numberpicker_input);
240            mAmPmSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE);
241        }
242
243        if (isAmPmAtStart()) {
244            // Move the am/pm view to the beginning
245            ViewGroup amPmParent = (ViewGroup) findViewById(R.id.timePickerLayout);
246            amPmParent.removeView(amPmView);
247            amPmParent.addView(amPmView, 0);
248            // Swap layout margins if needed. They may be not symmetrical (Old Standard Theme for
249            // example and not for Holo Theme)
250            ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) amPmView.getLayoutParams();
251            final int startMargin = lp.getMarginStart();
252            final int endMargin = lp.getMarginEnd();
253            if (startMargin != endMargin) {
254                lp.setMarginStart(endMargin);
255                lp.setMarginEnd(startMargin);
256            }
257        }
258
259        getHourFormatData();
260
261        // update controls to initial state
262        updateHourControl();
263        updateMinuteControl();
264        updateAmPmControl();
265
266        setOnTimeChangedListener(NO_OP_CHANGE_LISTENER);
267
268        // set to current time
269        setCurrentHour(mTempCalendar.get(Calendar.HOUR_OF_DAY));
270        setCurrentMinute(mTempCalendar.get(Calendar.MINUTE));
271
272        if (!isEnabled()) {
273            setEnabled(false);
274        }
275
276        // set the content descriptions
277        setContentDescriptions();
278
279        // If not explicitly specified this view is important for accessibility.
280        if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
281            setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
282        }
283    }
284
285    private void getHourFormatData() {
286        final Locale defaultLocale = Locale.getDefault();
287        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(defaultLocale,
288                (mIs24HourView) ? "Hm" : "hm");
289        final int lengthPattern = bestDateTimePattern.length();
290        mHourWithTwoDigit = false;
291        char hourFormat = '\0';
292        // Check if the returned pattern is single or double 'H', 'h', 'K', 'k'. We also save
293        // the hour format that we found.
294        for (int i = 0; i < lengthPattern; i++) {
295            final char c = bestDateTimePattern.charAt(i);
296            if (c == 'H' || c == 'h' || c == 'K' || c == 'k') {
297                mHourFormat = c;
298                if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) {
299                    mHourWithTwoDigit = true;
300                }
301                break;
302            }
303        }
304    }
305
306    private boolean isAmPmAtStart() {
307        final Locale defaultLocale = Locale.getDefault();
308        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(defaultLocale,
309                "hm" /* skeleton */);
310
311        return bestDateTimePattern.startsWith("a");
312    }
313
314    @Override
315    public void setEnabled(boolean enabled) {
316        if (mIsEnabled == enabled) {
317            return;
318        }
319        super.setEnabled(enabled);
320        mMinuteSpinner.setEnabled(enabled);
321        if (mDivider != null) {
322            mDivider.setEnabled(enabled);
323        }
324        mHourSpinner.setEnabled(enabled);
325        if (mAmPmSpinner != null) {
326            mAmPmSpinner.setEnabled(enabled);
327        } else {
328            mAmPmButton.setEnabled(enabled);
329        }
330        mIsEnabled = enabled;
331    }
332
333    @Override
334    public boolean isEnabled() {
335        return mIsEnabled;
336    }
337
338    @Override
339    protected void onConfigurationChanged(Configuration newConfig) {
340        super.onConfigurationChanged(newConfig);
341        setCurrentLocale(newConfig.locale);
342    }
343
344    /**
345     * Sets the current locale.
346     *
347     * @param locale The current locale.
348     */
349    private void setCurrentLocale(Locale locale) {
350        if (locale.equals(mCurrentLocale)) {
351            return;
352        }
353        mCurrentLocale = locale;
354        mTempCalendar = Calendar.getInstance(locale);
355    }
356
357    /**
358     * Used to save / restore state of time picker
359     */
360    private static class SavedState extends BaseSavedState {
361
362        private final int mHour;
363
364        private final int mMinute;
365
366        private SavedState(Parcelable superState, int hour, int minute) {
367            super(superState);
368            mHour = hour;
369            mMinute = minute;
370        }
371
372        private SavedState(Parcel in) {
373            super(in);
374            mHour = in.readInt();
375            mMinute = in.readInt();
376        }
377
378        public int getHour() {
379            return mHour;
380        }
381
382        public int getMinute() {
383            return mMinute;
384        }
385
386        @Override
387        public void writeToParcel(Parcel dest, int flags) {
388            super.writeToParcel(dest, flags);
389            dest.writeInt(mHour);
390            dest.writeInt(mMinute);
391        }
392
393        @SuppressWarnings({"unused", "hiding"})
394        public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
395            public SavedState createFromParcel(Parcel in) {
396                return new SavedState(in);
397            }
398
399            public SavedState[] newArray(int size) {
400                return new SavedState[size];
401            }
402        };
403    }
404
405    @Override
406    protected Parcelable onSaveInstanceState() {
407        Parcelable superState = super.onSaveInstanceState();
408        return new SavedState(superState, getCurrentHour(), getCurrentMinute());
409    }
410
411    @Override
412    protected void onRestoreInstanceState(Parcelable state) {
413        SavedState ss = (SavedState) state;
414        super.onRestoreInstanceState(ss.getSuperState());
415        setCurrentHour(ss.getHour());
416        setCurrentMinute(ss.getMinute());
417    }
418
419    /**
420     * Set the callback that indicates the time has been adjusted by the user.
421     *
422     * @param onTimeChangedListener the callback, should not be null.
423     */
424    public void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener) {
425        mOnTimeChangedListener = onTimeChangedListener;
426    }
427
428    /**
429     * @return The current hour in the range (0-23).
430     */
431    public Integer getCurrentHour() {
432        int currentHour = mHourSpinner.getValue();
433        if (is24HourView()) {
434            return currentHour;
435        } else if (mIsAm) {
436            return currentHour % HOURS_IN_HALF_DAY;
437        } else {
438            return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY;
439        }
440    }
441
442    /**
443     * Set the current hour.
444     */
445    public void setCurrentHour(Integer currentHour) {
446        setCurrentHour(currentHour, true);
447    }
448
449    private void setCurrentHour(Integer currentHour, boolean notifyTimeChanged) {
450        // why was Integer used in the first place?
451        if (currentHour == null || currentHour == getCurrentHour()) {
452            return;
453        }
454        if (!is24HourView()) {
455            // convert [0,23] ordinal to wall clock display
456            if (currentHour >= HOURS_IN_HALF_DAY) {
457                mIsAm = false;
458                if (currentHour > HOURS_IN_HALF_DAY) {
459                    currentHour = currentHour - HOURS_IN_HALF_DAY;
460                }
461            } else {
462                mIsAm = true;
463                if (currentHour == 0) {
464                    currentHour = HOURS_IN_HALF_DAY;
465                }
466            }
467            updateAmPmControl();
468        }
469        mHourSpinner.setValue(currentHour);
470        if (notifyTimeChanged) {
471            onTimeChanged();
472        }
473    }
474
475    /**
476     * Set whether in 24 hour or AM/PM mode.
477     *
478     * @param is24HourView True = 24 hour mode. False = AM/PM.
479     */
480    public void setIs24HourView(Boolean is24HourView) {
481        if (mIs24HourView == is24HourView) {
482            return;
483        }
484        // cache the current hour since spinner range changes and BEFORE changing mIs24HourView!!
485        int currentHour = getCurrentHour();
486        // Order is important here.
487        mIs24HourView = is24HourView;
488        getHourFormatData();
489        updateHourControl();
490        // set value after spinner range is updated - be aware that because mIs24HourView has
491        // changed then getCurrentHour() is not equal to the currentHour we cached before so
492        // explicitly ask for *not* propagating any onTimeChanged()
493        setCurrentHour(currentHour, false /* no onTimeChanged() */);
494        updateMinuteControl();
495        updateAmPmControl();
496    }
497
498    /**
499     * @return true if this is in 24 hour view else false.
500     */
501    public boolean is24HourView() {
502        return mIs24HourView;
503    }
504
505    /**
506     * @return The current minute.
507     */
508    public Integer getCurrentMinute() {
509        return mMinuteSpinner.getValue();
510    }
511
512    /**
513     * Set the current minute (0-59).
514     */
515    public void setCurrentMinute(Integer currentMinute) {
516        if (currentMinute == getCurrentMinute()) {
517            return;
518        }
519        mMinuteSpinner.setValue(currentMinute);
520        onTimeChanged();
521    }
522
523    /**
524     * The time separator is defined in the Unicode CLDR and cannot be supposed to be ":".
525     *
526     * See http://unicode.org/cldr/trac/browser/trunk/common/main
527     *
528     * We pass the correct "skeleton" depending on 12 or 24 hours view and then extract the
529     * separator as the character which is just after the hour marker in the returned pattern.
530     */
531    private void setDividerText() {
532        final Locale defaultLocale = Locale.getDefault();
533        final String skeleton = (mIs24HourView) ? "Hm" : "hm";
534        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(defaultLocale,
535                skeleton);
536        final String separatorText;
537        int hourIndex = bestDateTimePattern.lastIndexOf('H');
538        if (hourIndex == -1) {
539            hourIndex = bestDateTimePattern.lastIndexOf('h');
540        }
541        if (hourIndex == -1) {
542            // Default case
543            separatorText = ":";
544        } else {
545            int minuteIndex = bestDateTimePattern.indexOf('m', hourIndex + 1);
546            if  (minuteIndex == -1) {
547                separatorText = Character.toString(bestDateTimePattern.charAt(hourIndex + 1));
548            } else {
549                separatorText = bestDateTimePattern.substring(hourIndex + 1, minuteIndex);
550            }
551        }
552        mDivider.setText(separatorText);
553    }
554
555    @Override
556    public int getBaseline() {
557        return mHourSpinner.getBaseline();
558    }
559
560    @Override
561    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
562        onPopulateAccessibilityEvent(event);
563        return true;
564    }
565
566    @Override
567    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
568        super.onPopulateAccessibilityEvent(event);
569
570        int flags = DateUtils.FORMAT_SHOW_TIME;
571        if (mIs24HourView) {
572            flags |= DateUtils.FORMAT_24HOUR;
573        } else {
574            flags |= DateUtils.FORMAT_12HOUR;
575        }
576        mTempCalendar.set(Calendar.HOUR_OF_DAY, getCurrentHour());
577        mTempCalendar.set(Calendar.MINUTE, getCurrentMinute());
578        String selectedDateUtterance = DateUtils.formatDateTime(mContext,
579                mTempCalendar.getTimeInMillis(), flags);
580        event.getText().add(selectedDateUtterance);
581    }
582
583    @Override
584    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
585        super.onInitializeAccessibilityEvent(event);
586        event.setClassName(TimePicker.class.getName());
587    }
588
589    @Override
590    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
591        super.onInitializeAccessibilityNodeInfo(info);
592        info.setClassName(TimePicker.class.getName());
593    }
594
595    private void updateHourControl() {
596        if (is24HourView()) {
597            // 'k' means 1-24 hour
598            if (mHourFormat == 'k') {
599                mHourSpinner.setMinValue(1);
600                mHourSpinner.setMaxValue(24);
601            } else {
602                mHourSpinner.setMinValue(0);
603                mHourSpinner.setMaxValue(23);
604            }
605        } else {
606            // 'K' means 0-11 hour
607            if (mHourFormat == 'K') {
608                mHourSpinner.setMinValue(0);
609                mHourSpinner.setMaxValue(11);
610            } else {
611                mHourSpinner.setMinValue(1);
612                mHourSpinner.setMaxValue(12);
613            }
614        }
615        mHourSpinner.setFormatter(mHourWithTwoDigit ? NumberPicker.getTwoDigitFormatter() : null);
616    }
617
618    private void updateMinuteControl() {
619        if (is24HourView()) {
620            mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE);
621        } else {
622            mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
623        }
624    }
625
626    private void updateAmPmControl() {
627        if (is24HourView()) {
628            if (mAmPmSpinner != null) {
629                mAmPmSpinner.setVisibility(View.GONE);
630            } else {
631                mAmPmButton.setVisibility(View.GONE);
632            }
633        } else {
634            int index = mIsAm ? Calendar.AM : Calendar.PM;
635            if (mAmPmSpinner != null) {
636                mAmPmSpinner.setValue(index);
637                mAmPmSpinner.setVisibility(View.VISIBLE);
638            } else {
639                mAmPmButton.setText(mAmPmStrings[index]);
640                mAmPmButton.setVisibility(View.VISIBLE);
641            }
642        }
643        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
644    }
645
646    private void onTimeChanged() {
647        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
648        if (mOnTimeChangedListener != null) {
649            mOnTimeChangedListener.onTimeChanged(this, getCurrentHour(), getCurrentMinute());
650        }
651    }
652
653    private void setContentDescriptions() {
654        // Minute
655        trySetContentDescription(mMinuteSpinner, R.id.increment,
656                R.string.time_picker_increment_minute_button);
657        trySetContentDescription(mMinuteSpinner, R.id.decrement,
658                R.string.time_picker_decrement_minute_button);
659        // Hour
660        trySetContentDescription(mHourSpinner, R.id.increment,
661                R.string.time_picker_increment_hour_button);
662        trySetContentDescription(mHourSpinner, R.id.decrement,
663                R.string.time_picker_decrement_hour_button);
664        // AM/PM
665        if (mAmPmSpinner != null) {
666            trySetContentDescription(mAmPmSpinner, R.id.increment,
667                    R.string.time_picker_increment_set_pm_button);
668            trySetContentDescription(mAmPmSpinner, R.id.decrement,
669                    R.string.time_picker_decrement_set_am_button);
670        }
671    }
672
673    private void trySetContentDescription(View root, int viewId, int contDescResId) {
674        View target = root.findViewById(viewId);
675        if (target != null) {
676            target.setContentDescription(mContext.getString(contDescResId));
677        }
678    }
679
680    private void updateInputState() {
681        // Make sure that if the user changes the value and the IME is active
682        // for one of the inputs if this widget, the IME is closed. If the user
683        // changed the value via the IME and there is a next input the IME will
684        // be shown, otherwise the user chose another means of changing the
685        // value and having the IME up makes no sense.
686        InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
687        if (inputMethodManager != null) {
688            if (inputMethodManager.isActive(mHourSpinnerInput)) {
689                mHourSpinnerInput.clearFocus();
690                inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
691            } else if (inputMethodManager.isActive(mMinuteSpinnerInput)) {
692                mMinuteSpinnerInput.clearFocus();
693                inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
694            } else if (inputMethodManager.isActive(mAmPmSpinnerInput)) {
695                mAmPmSpinnerInput.clearFocus();
696                inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
697            }
698        }
699    }
700}
701