1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.content.browser.input;
6
7import android.app.AlertDialog;
8import android.content.Context;
9import android.content.DialogInterface;
10import android.content.DialogInterface.OnClickListener;
11import android.view.LayoutInflater;
12import android.view.View;
13import android.widget.NumberPicker;
14
15import org.chromium.content.R;
16
17import java.util.ArrayList;
18
19/**
20 * A time picker dialog with upto 5 number pickers left to right:
21 *  hour, minute, second, milli, AM/PM.
22 *
23 * If is24hourFormat is true then AM/PM picker is not displayed and
24 * hour range is 0..23. Otherwise hour range is 1..12.
25 * The milli picker is not displayed if step >= SECOND_IN_MILLIS
26 * The second picker is not displayed if step >= MINUTE_IN_MILLIS.
27 */
28public class MultiFieldTimePickerDialog
29        extends AlertDialog implements OnClickListener {
30
31    private final NumberPicker mHourSpinner;
32    private final NumberPicker mMinuteSpinner;
33    private final NumberPicker mSecSpinner;
34    private final NumberPicker mMilliSpinner;
35    private final NumberPicker mAmPmSpinner;
36    private final OnMultiFieldTimeSetListener mListener;
37    private final int mStep;
38    private final int mBaseMilli;
39    private final boolean mIs24hourFormat;
40
41    public interface OnMultiFieldTimeSetListener {
42        void onTimeSet(int hourOfDay, int minute, int second, int milli);
43    }
44
45    private static final int SECOND_IN_MILLIS = 1000;
46    private static final int MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS;
47    private static final int HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;
48
49    public MultiFieldTimePickerDialog(
50            Context context,
51            int theme,
52            int hour, int minute, int second, int milli,
53            int min, int max, int step, boolean is24hourFormat,
54            OnMultiFieldTimeSetListener listener) {
55        super(context, theme);
56        mListener = listener;
57        mStep = step;
58        mIs24hourFormat = is24hourFormat;
59
60        if (min >= max) {
61            min = 0;
62            max = 24 * HOUR_IN_MILLIS - 1;
63        }
64        if (step < 0 || step >= 24 * HOUR_IN_MILLIS) {
65            step = MINUTE_IN_MILLIS;
66        }
67
68        LayoutInflater inflater =
69                (LayoutInflater) context.getSystemService(
70                        Context.LAYOUT_INFLATER_SERVICE);
71        View view = inflater.inflate(R.layout.multi_field_time_picker_dialog, null);
72        setView(view);
73
74        mHourSpinner = (NumberPicker) view.findViewById(R.id.hour);
75        mMinuteSpinner = (NumberPicker) view.findViewById(R.id.minute);
76        mSecSpinner = (NumberPicker) view.findViewById(R.id.second);
77        mMilliSpinner = (NumberPicker) view.findViewById(R.id.milli);
78        mAmPmSpinner = (NumberPicker) view.findViewById(R.id.ampm);
79
80        int minHour = min / HOUR_IN_MILLIS;
81        int maxHour = max / HOUR_IN_MILLIS;
82        min -= minHour * HOUR_IN_MILLIS;
83        max -= maxHour * HOUR_IN_MILLIS;
84
85        if (minHour == maxHour) {
86            mHourSpinner.setEnabled(false);
87            hour = minHour;
88        }
89
90        if (is24hourFormat) {
91            mAmPmSpinner.setVisibility(View.GONE);
92        } else {
93            int minAmPm = minHour / 12;
94            int maxAmPm = maxHour / 12;
95            int amPm = hour / 12;
96            mAmPmSpinner.setMinValue(minAmPm);
97            mAmPmSpinner.setMaxValue(maxAmPm);
98            mAmPmSpinner.setDisplayedValues(new String[] {
99                    context.getString(R.string.time_picker_dialog_am),
100                    context.getString(R.string.time_picker_dialog_pm)
101            });
102
103            hour %= 12;
104            if (hour == 0) {
105                hour = 12;
106            }
107            if (minAmPm == maxAmPm) {
108                mAmPmSpinner.setEnabled(false);
109                amPm = minAmPm;
110
111                minHour %= 12;
112                maxHour %= 12;
113                if (minHour == 0 && maxHour == 0) {
114                    minHour = 12;
115                    maxHour = 12;
116                } else if (minHour == 0) {
117                    minHour = maxHour;
118                    maxHour = 12;
119                } else if (maxHour == 0) {
120                    maxHour = 12;
121                }
122            } else {
123                minHour = 1;
124                maxHour = 12;
125            }
126            mAmPmSpinner.setValue(amPm);
127        }
128
129        if (minHour == maxHour) {
130            mHourSpinner.setEnabled(false);
131        }
132        mHourSpinner.setMinValue(minHour);
133        mHourSpinner.setMaxValue(maxHour);
134        mHourSpinner.setValue(hour);
135
136        NumberFormatter twoDigitPaddingFormatter = new NumberFormatter("%02d");
137
138        int minMinute = min / MINUTE_IN_MILLIS;
139        int maxMinute = max / MINUTE_IN_MILLIS;
140        min -= minMinute * MINUTE_IN_MILLIS;
141        max -= maxMinute * MINUTE_IN_MILLIS;
142
143        if (minHour == maxHour) {
144            mMinuteSpinner.setMinValue(minMinute);
145            mMinuteSpinner.setMaxValue(maxMinute);
146            if (minMinute == maxMinute) {
147                // Set this otherwise the box is empty until you stroke it.
148                mMinuteSpinner.setDisplayedValues(
149                    new String[] { twoDigitPaddingFormatter.format(minMinute) });
150                mMinuteSpinner.setEnabled(false);
151                minute = minMinute;
152            }
153        } else {
154            mMinuteSpinner.setMinValue(0);
155            mMinuteSpinner.setMaxValue(59);
156        }
157
158        if (step >= HOUR_IN_MILLIS) {
159            mMinuteSpinner.setEnabled(false);
160        }
161
162        mMinuteSpinner.setValue(minute);
163        mMinuteSpinner.setFormatter(twoDigitPaddingFormatter);
164
165        if (step >= MINUTE_IN_MILLIS) {
166            // Remove the ':' in front of the second spinner as well.
167            view.findViewById(R.id.second_colon).setVisibility(View.GONE);
168            mSecSpinner.setVisibility(View.GONE);
169        }
170
171        int minSecond = min / SECOND_IN_MILLIS;
172        int maxSecond = max / SECOND_IN_MILLIS;
173        min -= minSecond * SECOND_IN_MILLIS;
174        max -= maxSecond * SECOND_IN_MILLIS;
175
176        if (minHour == maxHour && minMinute == maxMinute) {
177            mSecSpinner.setMinValue(minSecond);
178            mSecSpinner.setMaxValue(maxSecond);
179            if (minSecond == maxSecond) {
180                // Set this otherwise the box is empty until you stroke it.
181                mSecSpinner.setDisplayedValues(
182                    new String[] { twoDigitPaddingFormatter.format(minSecond) });
183                mSecSpinner.setEnabled(false);
184                second = minSecond;
185            }
186        } else {
187            mSecSpinner.setMinValue(0);
188            mSecSpinner.setMaxValue(59);
189        }
190
191        mSecSpinner.setValue(second);
192        mSecSpinner.setFormatter(twoDigitPaddingFormatter);
193
194        if (step >= SECOND_IN_MILLIS) {
195            // Remove the '.' in front of the milli spinner as well.
196            view.findViewById(R.id.second_dot).setVisibility(View.GONE);
197            mMilliSpinner.setVisibility(View.GONE);
198        }
199
200        // Round to the nearest step.
201        milli = ((milli + step / 2) / step) * step;
202        if (step == 1 || step == 10 || step == 100) {
203            if (minHour == maxHour && minMinute == maxMinute &&
204                minSecond == maxSecond) {
205                mMilliSpinner.setMinValue(min / step);
206                mMilliSpinner.setMaxValue(max / step);
207
208                if (min == max) {
209                    mMilliSpinner.setEnabled(false);
210                    milli = min;
211                }
212            } else {
213                mMilliSpinner.setMinValue(0);
214                mMilliSpinner.setMaxValue(999 / step);
215            }
216
217            if (step == 1) {
218                mMilliSpinner.setFormatter(new NumberFormatter("%03d"));
219            } else if (step == 10) {
220                mMilliSpinner.setFormatter(new NumberFormatter("%02d"));
221            } else if (step == 100) {
222                mMilliSpinner.setFormatter(new NumberFormatter("%d"));
223            }
224            mMilliSpinner.setValue(milli / step);
225            mBaseMilli = 0;
226        } else if (step < SECOND_IN_MILLIS) {
227            // Non-decimal step value.
228            ArrayList<String> strValue = new ArrayList<String>();
229            for (int i = min; i < max; i += step) {
230                strValue.add(String.format("%03d", i));
231            }
232            mMilliSpinner.setMinValue(0);
233            mMilliSpinner.setMaxValue(strValue.size() - 1);
234            mMilliSpinner.setValue((milli - min) / step);
235            mMilliSpinner.setDisplayedValues(
236                strValue.toArray(new String[strValue.size()]));
237            mBaseMilli = min;
238        } else {
239            mBaseMilli = 0;
240        }
241    }
242
243    @Override
244    public void onClick(DialogInterface dialog, int which) {
245        notifyDateSet();
246    }
247
248    private void notifyDateSet() {
249        int hour = mHourSpinner.getValue();
250        int minute = mMinuteSpinner.getValue();
251        int sec = mSecSpinner.getValue();
252        int milli = mMilliSpinner.getValue() * mStep + mBaseMilli;
253        if (!mIs24hourFormat) {
254            int ampm = mAmPmSpinner.getValue();
255            if (hour == 12) {
256                hour = 0;
257            }
258            hour += ampm * 12;
259        }
260        mListener.onTimeSet(hour, minute, sec, milli);
261    }
262
263    private static class NumberFormatter implements NumberPicker.Formatter {
264        private final String mFormat;
265
266        NumberFormatter(String format) {
267            mFormat = format;
268        }
269
270        @Override
271        public String format(int value) {
272            return String.format(mFormat, value);
273        }
274    }
275}
276