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