1 | // Copyright (c) 2012 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.app.DatePickerDialog; |
9 | import android.app.TimePickerDialog; |
10 | import android.app.DatePickerDialog.OnDateSetListener; |
11 | import android.app.TimePickerDialog.OnTimeSetListener; |
12 | import android.content.Context; |
13 | import android.content.DialogInterface; |
14 | import android.content.DialogInterface.OnDismissListener; |
15 | import android.text.TextUtils; |
16 | import android.text.format.DateFormat; |
17 | import android.text.format.Time; |
18 | import android.widget.DatePicker; |
19 | import android.widget.TimePicker; |
20 | |
21 | import org.chromium.content.browser.input.DateTimePickerDialog.OnDateTimeSetListener; |
22 | import org.chromium.content.browser.input.MultiFieldTimePickerDialog.OnMultiFieldTimeSetListener; |
23 | import org.chromium.content.browser.input.TwoFieldDatePickerDialog; |
24 | import org.chromium.content.R; |
25 | |
26 | import java.text.ParseException; |
27 | import java.text.SimpleDateFormat; |
28 | import java.util.Calendar; |
29 | import java.util.Date; |
30 | |
31 | public class InputDialogContainer { |
32 | |
33 | interface InputActionDelegate { |
34 | void cancelDateTimeDialog(); |
35 | void replaceDateTime(int dialogType, |
36 | int year, int month, int day, int hour, int minute, int second, int milli, int week); |
37 | } |
38 | |
39 | // Default values used in Time representations of selected date/time before formatting. |
40 | // They are never displayed to the user. |
41 | private static final int YEAR_DEFAULT = 1970; |
42 | private static final int MONTH_DEFAULT = 0; |
43 | private static final int MONTHDAY_DEFAULT = 1; |
44 | private static final int HOUR_DEFAULT = 0; |
45 | private static final int MINUTE_DEFAULT = 0; |
46 | private static final int WEEK_DEFAULT = 0; |
47 | |
48 | // Date formats as accepted by Time.format. |
49 | private static final String HTML_DATE_FORMAT = "%Y-%m-%d"; |
50 | private static final String HTML_TIME_FORMAT = "%H:%M"; |
51 | // For datetime we always send selected time as UTC, as we have no timezone selector. |
52 | // This is consistent with other browsers. |
53 | private static final String HTML_DATE_TIME_FORMAT = "%Y-%m-%dT%H:%MZ"; |
54 | private static final String HTML_DATE_TIME_LOCAL_FORMAT = "%Y-%m-%dT%H:%M"; |
55 | private static final String HTML_MONTH_FORMAT = "%Y-%m"; |
56 | private static final String HTML_WEEK_FORMAT = "%Y-%w"; |
57 | |
58 | private static int sTextInputTypeDate; |
59 | private static int sTextInputTypeDateTime; |
60 | private static int sTextInputTypeDateTimeLocal; |
61 | private static int sTextInputTypeMonth; |
62 | private static int sTextInputTypeTime; |
63 | private static int sTextInputTypeWeek; |
64 | |
65 | private Context mContext; |
66 | |
67 | // Prevents sending two notifications (from onClick and from onDismiss) |
68 | private boolean mDialogAlreadyDismissed; |
69 | |
70 | private AlertDialog mDialog; |
71 | private InputActionDelegate mInputActionDelegate; |
72 | |
73 | static void initializeInputTypes(int textInputTypeDate, |
74 | int textInputTypeDateTime, int textInputTypeDateTimeLocal, |
75 | int textInputTypeMonth, int textInputTypeTime, |
76 | int textInputTypeWeek) { |
77 | sTextInputTypeDate = textInputTypeDate; |
78 | sTextInputTypeDateTime = textInputTypeDateTime; |
79 | sTextInputTypeDateTimeLocal = textInputTypeDateTimeLocal; |
80 | sTextInputTypeMonth = textInputTypeMonth; |
81 | sTextInputTypeTime = textInputTypeTime; |
82 | sTextInputTypeWeek = textInputTypeWeek; |
83 | } |
84 | |
85 | static boolean isDialogInputType(int type) { |
86 | return type == sTextInputTypeDate || type == sTextInputTypeTime |
87 | || type == sTextInputTypeDateTime || type == sTextInputTypeDateTimeLocal |
88 | || type == sTextInputTypeMonth || type == sTextInputTypeWeek; |
89 | } |
90 | |
91 | InputDialogContainer(Context context, InputActionDelegate inputActionDelegate) { |
92 | mContext = context; |
93 | mInputActionDelegate = inputActionDelegate; |
94 | } |
95 | |
96 | private Time normalizeTime(int year, int month, int monthDay, |
97 | int hour, int minute, int second) { |
98 | Time result = new Time(); |
99 | if (year == 0 && month == 0 && monthDay == 0 && hour == 0 && |
100 | minute == 0 && second == 0) { |
101 | Calendar cal = Calendar.getInstance(); |
102 | result.set(cal.get(Calendar.SECOND), cal.get(Calendar.MINUTE), |
103 | cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.DATE), |
104 | cal.get(Calendar.MONTH), cal.get(Calendar.YEAR)); |
105 | } else { |
106 | result.set(second, minute, hour, monthDay, month, year); |
107 | } |
108 | return result; |
109 | } |
110 | |
111 | void showDialog(final int dialogType, int year, int month, int monthDay, |
112 | int hour, int minute, int second, int milli, int week, |
113 | double min, double max, double step) { |
114 | if (isDialogShowing()) mDialog.dismiss(); |
115 | |
116 | // Java Date dialogs like longs but Blink prefers doubles.. |
117 | // Both parameters mean different things depending on the type |
118 | // For input type=month min and max come as number on months since 1970 |
119 | // For other types (including type=time) they are just milliseconds since 1970 |
120 | // In any case the cast here is safe given the above restrictions. |
121 | long minTime = (long) min; |
122 | long maxTime = (long) max; |
123 | int stepTime = (int) step; |
124 | |
125 | if (milli > 1000) { |
126 | second += milli / 1000; |
127 | milli %= 1000; |
128 | } |
129 | Time time = normalizeTime(year, month, monthDay, hour, minute, second); |
130 | if (dialogType == sTextInputTypeDate) { |
131 | DatePickerDialog dialog = new DatePickerDialog(mContext, |
132 | new DateListener(dialogType), time.year, time.month, time.monthDay); |
133 | DateDialogNormalizer.normalize(dialog.getDatePicker(), dialog, |
134 | time.year, time.month, time.monthDay, 0, 0, minTime, maxTime); |
135 | |
136 | dialog.setTitle(mContext.getText(R.string.date_picker_dialog_title)); |
137 | mDialog = dialog; |
138 | } else if (dialogType == sTextInputTypeTime) { |
139 | mDialog = new MultiFieldTimePickerDialog( |
140 | mContext, 0 /* theme */ , |
141 | time.hour, time.minute, time.second, milli, |
142 | (int) minTime, (int) maxTime, stepTime, |
143 | DateFormat.is24HourFormat(mContext), |
144 | new FullTimeListener(dialogType)); |
145 | } else if (dialogType == sTextInputTypeDateTime || |
146 | dialogType == sTextInputTypeDateTimeLocal) { |
147 | mDialog = new DateTimePickerDialog(mContext, |
148 | new DateTimeListener(dialogType), |
149 | time.year, time.month, time.monthDay, |
150 | time.hour, time.minute, DateFormat.is24HourFormat(mContext), |
151 | minTime, maxTime); |
152 | } else if (dialogType == sTextInputTypeMonth) { |
153 | mDialog = new MonthPickerDialog(mContext, new MonthOrWeekListener(dialogType), |
154 | time.year, time.month, minTime, maxTime); |
155 | } else if (dialogType == sTextInputTypeWeek) { |
156 | if (week == 0) { |
157 | Calendar cal = Calendar.getInstance(); |
158 | year = WeekPicker.getISOWeekYearForDate(cal); |
159 | week = WeekPicker.getWeekForDate(cal); |
160 | } |
161 | mDialog = new WeekPickerDialog(mContext, new MonthOrWeekListener(dialogType), |
162 | year, week, minTime, maxTime); |
163 | } |
164 | |
165 | mDialog.setButton(DialogInterface.BUTTON_POSITIVE, |
166 | mContext.getText(R.string.date_picker_dialog_set), |
167 | (DialogInterface.OnClickListener) mDialog); |
168 | |
169 | mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, |
170 | mContext.getText(android.R.string.cancel), |
171 | new DialogInterface.OnClickListener() { |
172 | @Override |
173 | public void onClick(DialogInterface dialog, int which) { |
174 | mDialogAlreadyDismissed = true; |
175 | mInputActionDelegate.cancelDateTimeDialog(); |
176 | } |
177 | }); |
178 | |
179 | mDialog.setButton(DialogInterface.BUTTON_NEUTRAL, |
180 | mContext.getText(R.string.date_picker_dialog_clear), |
181 | new DialogInterface.OnClickListener() { |
182 | @Override |
183 | public void onClick(DialogInterface dialog, int which) { |
184 | mDialogAlreadyDismissed = true; |
185 | mInputActionDelegate.replaceDateTime(dialogType, 0, 0, 0, 0, 0, 0, 0, 0); |
186 | } |
187 | }); |
188 | |
189 | mDialogAlreadyDismissed = false; |
190 | mDialog.show(); |
191 | } |
192 | |
193 | boolean isDialogShowing() { |
194 | return mDialog != null && mDialog.isShowing(); |
195 | } |
196 | |
197 | void dismissDialog() { |
198 | if (isDialogShowing()) mDialog.dismiss(); |
199 | } |
200 | |
201 | private class DateListener implements OnDateSetListener { |
202 | private final int mDialogType; |
203 | |
204 | DateListener(int dialogType) { |
205 | mDialogType = dialogType; |
206 | } |
207 | |
208 | @Override |
209 | public void onDateSet(DatePicker view, int year, int month, int monthDay) { |
210 | if (!mDialogAlreadyDismissed) { |
211 | setFieldDateTimeValue(mDialogType, |
212 | year, month, monthDay, |
213 | HOUR_DEFAULT, MINUTE_DEFAULT, WEEK_DEFAULT, |
214 | HTML_DATE_FORMAT); |
215 | } |
216 | } |
217 | } |
218 | |
219 | private class TimeListener implements OnTimeSetListener { |
220 | private final int mDialogType; |
221 | |
222 | TimeListener(int dialogType) { |
223 | mDialogType = dialogType; |
224 | } |
225 | |
226 | @Override |
227 | public void onTimeSet(TimePicker view, int hourOfDay, int minute) { |
228 | if (!mDialogAlreadyDismissed) { |
229 | setFieldDateTimeValue(mDialogType, |
230 | YEAR_DEFAULT, MONTH_DEFAULT, MONTHDAY_DEFAULT, |
231 | hourOfDay, minute, WEEK_DEFAULT, HTML_TIME_FORMAT); |
232 | } |
233 | } |
234 | } |
235 | |
236 | private class FullTimeListener implements OnMultiFieldTimeSetListener { |
237 | private final int mDialogType; |
238 | FullTimeListener(int dialogType) { |
239 | mDialogType = dialogType; |
240 | } |
241 | |
242 | @Override |
243 | public void onTimeSet(int hourOfDay, int minute, int second, int milli) { |
244 | if (!mDialogAlreadyDismissed) { |
245 | setFieldDateTimeValue(mDialogType, |
246 | YEAR_DEFAULT, MONTH_DEFAULT, MONTHDAY_DEFAULT, |
247 | hourOfDay, minute, second, milli, WEEK_DEFAULT, HTML_TIME_FORMAT); |
248 | } |
249 | } |
250 | } |
251 | |
252 | private class DateTimeListener implements OnDateTimeSetListener { |
253 | private final boolean mLocal; |
254 | private final int mDialogType; |
255 | |
256 | public DateTimeListener(int dialogType) { |
257 | mLocal = dialogType == sTextInputTypeDateTimeLocal; |
258 | mDialogType = dialogType; |
259 | } |
260 | |
261 | @Override |
262 | public void onDateTimeSet(DatePicker dateView, TimePicker timeView, |
263 | int year, int month, int monthDay, |
264 | int hourOfDay, int minute) { |
265 | if (!mDialogAlreadyDismissed) { |
266 | setFieldDateTimeValue(mDialogType, year, month, monthDay, |
267 | hourOfDay, minute, WEEK_DEFAULT, |
268 | mLocal ? HTML_DATE_TIME_LOCAL_FORMAT : HTML_DATE_TIME_FORMAT); |
269 | } |
270 | } |
271 | } |
272 | |
273 | private class MonthOrWeekListener implements TwoFieldDatePickerDialog.OnValueSetListener { |
274 | private final int mDialogType; |
275 | |
276 | MonthOrWeekListener(int dialogType) { |
277 | mDialogType = dialogType; |
278 | } |
279 | |
280 | @Override |
281 | public void onValueSet(int year, int positionInYear) { |
282 | if (!mDialogAlreadyDismissed) { |
283 | if (mDialogType == sTextInputTypeMonth) { |
284 | setFieldDateTimeValue(mDialogType, year, positionInYear, MONTHDAY_DEFAULT, |
285 | HOUR_DEFAULT, MINUTE_DEFAULT, WEEK_DEFAULT, |
286 | HTML_MONTH_FORMAT); |
287 | } else { |
288 | setFieldDateTimeValue(mDialogType, year, MONTH_DEFAULT, MONTHDAY_DEFAULT, |
289 | HOUR_DEFAULT, MINUTE_DEFAULT, positionInYear, HTML_WEEK_FORMAT); |
290 | } |
291 | } |
292 | } |
293 | } |
294 | |
295 | private void setFieldDateTimeValue(int dialogType, |
296 | int year, int month, int monthDay, int hourOfDay, |
297 | int minute, int week, String dateFormat) { |
298 | // Prevents more than one callback being sent to the native |
299 | // side when the dialog triggers multiple events. |
300 | mDialogAlreadyDismissed = true; |
301 | |
302 | mInputActionDelegate.replaceDateTime(dialogType, |
303 | year, month, monthDay, hourOfDay, minute, 0 /* second */, 0 /* milli */, week); |
304 | } |
305 | |
306 | private void setFieldDateTimeValue(int dialogType, |
307 | int year, int month, int monthDay, int hourOfDay, |
308 | int minute, int second, int milli, int week, String dateFormat) { |
309 | // Prevents more than one callback being sent to the native |
310 | // side when the dialog triggers multiple events. |
311 | mDialogAlreadyDismissed = true; |
312 | |
313 | mInputActionDelegate.replaceDateTime( |
314 | dialogType, year, month, monthDay, hourOfDay, minute, second, milli, week); |
315 | } |
316 | } |