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