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 com.android.internal.R;
20
21import android.annotation.IntDef;
22import android.annotation.IntRange;
23import android.annotation.NonNull;
24import android.annotation.TestApi;
25import android.annotation.Widget;
26import android.content.Context;
27import android.content.res.TypedArray;
28import android.os.Parcel;
29import android.os.Parcelable;
30import android.util.AttributeSet;
31import android.util.MathUtils;
32import android.view.View;
33import android.view.accessibility.AccessibilityEvent;
34
35import java.lang.annotation.Retention;
36import java.lang.annotation.RetentionPolicy;
37import java.util.Locale;
38
39import libcore.icu.LocaleData;
40
41/**
42 * A widget for selecting the time of day, in either 24-hour or AM/PM mode.
43 * <p>
44 * For a dialog using this view, see {@link android.app.TimePickerDialog}. See
45 * the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a>
46 * guide for more information.
47 *
48 * @attr ref android.R.styleable#TimePicker_timePickerMode
49 */
50@Widget
51public class TimePicker extends FrameLayout {
52    /**
53     * Presentation mode for the Holo-style time picker that uses a set of
54     * {@link android.widget.NumberPicker}s.
55     *
56     * @see #getMode()
57     * @hide Visible for testing only.
58     */
59    @TestApi
60    public static final int MODE_SPINNER = 1;
61
62    /**
63     * Presentation mode for the Material-style time picker that uses a clock
64     * face.
65     *
66     * @see #getMode()
67     * @hide Visible for testing only.
68     */
69    @TestApi
70    public static final int MODE_CLOCK = 2;
71
72    /** @hide */
73    @IntDef({MODE_SPINNER, MODE_CLOCK})
74    @Retention(RetentionPolicy.SOURCE)
75    public @interface TimePickerMode {}
76
77    private final TimePickerDelegate mDelegate;
78
79    @TimePickerMode
80    private final int mMode;
81
82    /**
83     * The callback interface used to indicate the time has been adjusted.
84     */
85    public interface OnTimeChangedListener {
86
87        /**
88         * @param view The view associated with this listener.
89         * @param hourOfDay The current hour.
90         * @param minute The current minute.
91         */
92        void onTimeChanged(TimePicker view, int hourOfDay, int minute);
93    }
94
95    public TimePicker(Context context) {
96        this(context, null);
97    }
98
99    public TimePicker(Context context, AttributeSet attrs) {
100        this(context, attrs, R.attr.timePickerStyle);
101    }
102
103    public TimePicker(Context context, AttributeSet attrs, int defStyleAttr) {
104        this(context, attrs, defStyleAttr, 0);
105    }
106
107    public TimePicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
108        super(context, attrs, defStyleAttr, defStyleRes);
109
110        final TypedArray a = context.obtainStyledAttributes(
111                attrs, R.styleable.TimePicker, defStyleAttr, defStyleRes);
112        final boolean isDialogMode = a.getBoolean(R.styleable.TimePicker_dialogMode, false);
113        final int requestedMode = a.getInt(R.styleable.TimePicker_timePickerMode, MODE_SPINNER);
114        a.recycle();
115
116        if (requestedMode == MODE_CLOCK && isDialogMode) {
117            // You want MODE_CLOCK? YOU CAN'T HANDLE MODE_CLOCK! Well, maybe
118            // you can depending on your screen size. Let's check...
119            mMode = context.getResources().getInteger(R.integer.time_picker_mode);
120        } else {
121            mMode = requestedMode;
122        }
123
124        switch (mMode) {
125            case MODE_CLOCK:
126                mDelegate = new TimePickerClockDelegate(
127                        this, context, attrs, defStyleAttr, defStyleRes);
128                break;
129            case MODE_SPINNER:
130            default:
131                mDelegate = new TimePickerSpinnerDelegate(
132                        this, context, attrs, defStyleAttr, defStyleRes);
133                break;
134        }
135    }
136
137    /**
138     * @return the picker's presentation mode, one of {@link #MODE_CLOCK} or
139     *         {@link #MODE_SPINNER}
140     * @attr ref android.R.styleable#TimePicker_timePickerMode
141     * @hide Visible for testing only.
142     */
143    @TimePickerMode
144    @TestApi
145    public int getMode() {
146        return mMode;
147    }
148
149    /**
150     * Sets the currently selected hour using 24-hour time.
151     *
152     * @param hour the hour to set, in the range (0-23)
153     * @see #getHour()
154     */
155    public void setHour(@IntRange(from = 0, to = 23) int hour) {
156        mDelegate.setHour(MathUtils.constrain(hour, 0, 23));
157    }
158
159    /**
160     * Returns the currently selected hour using 24-hour time.
161     *
162     * @return the currently selected hour, in the range (0-23)
163     * @see #setHour(int)
164     */
165    public int getHour() {
166        return mDelegate.getHour();
167    }
168
169    /**
170     * Sets the currently selected minute.
171     *
172     * @param minute the minute to set, in the range (0-59)
173     * @see #getMinute()
174     */
175    public void setMinute(@IntRange(from = 0, to = 59) int minute) {
176        mDelegate.setMinute(MathUtils.constrain(minute, 0, 59));
177    }
178
179    /**
180     * Returns the currently selected minute.
181     *
182     * @return the currently selected minute, in the range (0-59)
183     * @see #setMinute(int)
184     */
185    public int getMinute() {
186        return mDelegate.getMinute();
187    }
188
189    /**
190     * Sets the currently selected hour using 24-hour time.
191     *
192     * @param currentHour the hour to set, in the range (0-23)
193     * @deprecated Use {@link #setHour(int)}
194     */
195    @Deprecated
196    public void setCurrentHour(@NonNull Integer currentHour) {
197        setHour(currentHour);
198    }
199
200    /**
201     * @return the currently selected hour, in the range (0-23)
202     * @deprecated Use {@link #getHour()}
203     */
204    @NonNull
205    @Deprecated
206    public Integer getCurrentHour() {
207        return getHour();
208    }
209
210    /**
211     * Sets the currently selected minute.
212     *
213     * @param currentMinute the minute to set, in the range (0-59)
214     * @deprecated Use {@link #setMinute(int)}
215     */
216    @Deprecated
217    public void setCurrentMinute(@NonNull Integer currentMinute) {
218        setMinute(currentMinute);
219    }
220
221    /**
222     * @return the currently selected minute, in the range (0-59)
223     * @deprecated Use {@link #getMinute()}
224     */
225    @NonNull
226    @Deprecated
227    public Integer getCurrentMinute() {
228        return getMinute();
229    }
230
231    /**
232     * Sets whether this widget displays time in 24-hour mode or 12-hour mode
233     * with an AM/PM picker.
234     *
235     * @param is24HourView {@code true} to display in 24-hour mode,
236     *                     {@code false} for 12-hour mode with AM/PM
237     * @see #is24HourView()
238     */
239    public void setIs24HourView(@NonNull Boolean is24HourView) {
240        if (is24HourView == null) {
241            return;
242        }
243
244        mDelegate.setIs24Hour(is24HourView);
245    }
246
247    /**
248     * @return {@code true} if this widget displays time in 24-hour mode,
249     *         {@code false} otherwise}
250     * @see #setIs24HourView(Boolean)
251     */
252    public boolean is24HourView() {
253        return mDelegate.is24Hour();
254    }
255
256    /**
257     * Set the callback that indicates the time has been adjusted by the user.
258     *
259     * @param onTimeChangedListener the callback, should not be null.
260     */
261    public void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener) {
262        mDelegate.setOnTimeChangedListener(onTimeChangedListener);
263    }
264
265    @Override
266    public void setEnabled(boolean enabled) {
267        super.setEnabled(enabled);
268        mDelegate.setEnabled(enabled);
269    }
270
271    @Override
272    public boolean isEnabled() {
273        return mDelegate.isEnabled();
274    }
275
276    @Override
277    public int getBaseline() {
278        return mDelegate.getBaseline();
279    }
280
281    @Override
282    protected Parcelable onSaveInstanceState() {
283        Parcelable superState = super.onSaveInstanceState();
284        return mDelegate.onSaveInstanceState(superState);
285    }
286
287    @Override
288    protected void onRestoreInstanceState(Parcelable state) {
289        BaseSavedState ss = (BaseSavedState) state;
290        super.onRestoreInstanceState(ss.getSuperState());
291        mDelegate.onRestoreInstanceState(ss);
292    }
293
294    @Override
295    public CharSequence getAccessibilityClassName() {
296        return TimePicker.class.getName();
297    }
298
299    /** @hide */
300    @Override
301    public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
302        return mDelegate.dispatchPopulateAccessibilityEvent(event);
303    }
304
305    /**
306     * A delegate interface that defined the public API of the TimePicker. Allows different
307     * TimePicker implementations. This would need to be implemented by the TimePicker delegates
308     * for the real behavior.
309     */
310    interface TimePickerDelegate {
311        void setHour(@IntRange(from = 0, to = 23) int hour);
312        int getHour();
313
314        void setMinute(@IntRange(from = 0, to = 59) int minute);
315        int getMinute();
316
317        void setIs24Hour(boolean is24Hour);
318        boolean is24Hour();
319
320        void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener);
321
322        void setEnabled(boolean enabled);
323        boolean isEnabled();
324
325        int getBaseline();
326
327        Parcelable onSaveInstanceState(Parcelable superState);
328        void onRestoreInstanceState(Parcelable state);
329
330        boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event);
331        void onPopulateAccessibilityEvent(AccessibilityEvent event);
332    }
333
334    static String[] getAmPmStrings(Context context) {
335        final Locale locale = context.getResources().getConfiguration().locale;
336        final LocaleData d = LocaleData.get(locale);
337
338        final String[] result = new String[2];
339        result[0] = d.amPm[0].length() > 4 ? d.narrowAm : d.amPm[0];
340        result[1] = d.amPm[1].length() > 4 ? d.narrowPm : d.amPm[1];
341        return result;
342    }
343
344    /**
345     * An abstract class which can be used as a start for TimePicker implementations
346     */
347    abstract static class AbstractTimePickerDelegate implements TimePickerDelegate {
348        protected final TimePicker mDelegator;
349        protected final Context mContext;
350        protected final Locale mLocale;
351
352        protected OnTimeChangedListener mOnTimeChangedListener;
353
354        public AbstractTimePickerDelegate(@NonNull TimePicker delegator, @NonNull Context context) {
355            mDelegator = delegator;
356            mContext = context;
357            mLocale = context.getResources().getConfiguration().locale;
358        }
359
360        protected static class SavedState extends View.BaseSavedState {
361            private final int mHour;
362            private final int mMinute;
363            private final boolean mIs24HourMode;
364            private final int mCurrentItemShowing;
365
366            public SavedState(Parcelable superState, int hour, int minute, boolean is24HourMode) {
367                this(superState, hour, minute, is24HourMode, 0);
368            }
369
370            public SavedState(Parcelable superState, int hour, int minute, boolean is24HourMode,
371                    int currentItemShowing) {
372                super(superState);
373                mHour = hour;
374                mMinute = minute;
375                mIs24HourMode = is24HourMode;
376                mCurrentItemShowing = currentItemShowing;
377            }
378
379            private SavedState(Parcel in) {
380                super(in);
381                mHour = in.readInt();
382                mMinute = in.readInt();
383                mIs24HourMode = (in.readInt() == 1);
384                mCurrentItemShowing = in.readInt();
385            }
386
387            public int getHour() {
388                return mHour;
389            }
390
391            public int getMinute() {
392                return mMinute;
393            }
394
395            public boolean is24HourMode() {
396                return mIs24HourMode;
397            }
398
399            public int getCurrentItemShowing() {
400                return mCurrentItemShowing;
401            }
402
403            @Override
404            public void writeToParcel(Parcel dest, int flags) {
405                super.writeToParcel(dest, flags);
406                dest.writeInt(mHour);
407                dest.writeInt(mMinute);
408                dest.writeInt(mIs24HourMode ? 1 : 0);
409                dest.writeInt(mCurrentItemShowing);
410            }
411
412            @SuppressWarnings({"unused", "hiding"})
413            public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
414                public SavedState createFromParcel(Parcel in) {
415                    return new SavedState(in);
416                }
417
418                public SavedState[] newArray(int size) {
419                    return new SavedState[size];
420                }
421            };
422        }
423    }
424}
425