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