1 // Copyright 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.OnDateSetListener; 9 import android.content.Context; 10 import android.content.DialogInterface; 11 import android.content.DialogInterface.OnDismissListener; 12 import android.text.format.DateFormat; 13 import android.view.View; 14 import android.widget.AdapterView; 15 import android.widget.DatePicker; 16 import android.widget.ListView; 17 import android.widget.TimePicker; 18 19 import org.chromium.base.ApiCompatibilityUtils; 20 import org.chromium.content.R; 21 import org.chromium.content.browser.input.DateTimePickerDialog.OnDateTimeSetListener; 22 import org.chromium.content.browser.input.MultiFieldTimePickerDialog.OnMultiFieldTimeSetListener; 23 24 import java.util.Arrays; 25 import java.util.Calendar; 26 import java.util.Date; 27 import java.util.GregorianCalendar; 28 import java.util.TimeZone; 29 import java.util.concurrent.TimeUnit; 30 31 /** 32 * Opens the approprate date/time picker dialog for the given dialog type. 33 */ 34 public class InputDialogContainer { 35 36 interface InputActionDelegate { cancelDateTimeDialog()37 void cancelDateTimeDialog(); replaceDateTime(double value)38 void replaceDateTime(double value); 39 } 40 41 private static int sTextInputTypeDate; 42 private static int sTextInputTypeDateTime; 43 private static int sTextInputTypeDateTimeLocal; 44 private static int sTextInputTypeMonth; 45 private static int sTextInputTypeTime; 46 private static int sTextInputTypeWeek; 47 48 private final Context mContext; 49 50 // Prevents sending two notifications (from onClick and from onDismiss) 51 private boolean mDialogAlreadyDismissed; 52 53 private AlertDialog mDialog; 54 private final InputActionDelegate mInputActionDelegate; 55 initializeInputTypes(int textInputTypeDate, int textInputTypeDateTime, int textInputTypeDateTimeLocal, int textInputTypeMonth, int textInputTypeTime, int textInputTypeWeek)56 static void initializeInputTypes(int textInputTypeDate, 57 int textInputTypeDateTime, int textInputTypeDateTimeLocal, 58 int textInputTypeMonth, int textInputTypeTime, 59 int textInputTypeWeek) { 60 sTextInputTypeDate = textInputTypeDate; 61 sTextInputTypeDateTime = textInputTypeDateTime; 62 sTextInputTypeDateTimeLocal = textInputTypeDateTimeLocal; 63 sTextInputTypeMonth = textInputTypeMonth; 64 sTextInputTypeTime = textInputTypeTime; 65 sTextInputTypeWeek = textInputTypeWeek; 66 } 67 isDialogInputType(int type)68 static boolean isDialogInputType(int type) { 69 return type == sTextInputTypeDate || type == sTextInputTypeTime 70 || type == sTextInputTypeDateTime || type == sTextInputTypeDateTimeLocal 71 || type == sTextInputTypeMonth || type == sTextInputTypeWeek; 72 } 73 InputDialogContainer(Context context, InputActionDelegate inputActionDelegate)74 InputDialogContainer(Context context, InputActionDelegate inputActionDelegate) { 75 mContext = context; 76 mInputActionDelegate = inputActionDelegate; 77 } 78 showPickerDialog(final int dialogType, double dialogValue, double min, double max, double step)79 void showPickerDialog(final int dialogType, double dialogValue, 80 double min, double max, double step) { 81 Calendar cal; 82 // |dialogValue|, |min|, |max| mean different things depending on the |dialogType|. 83 // For input type=month is the number of months since 1970. 84 // For input type=time it is milliseconds since midnight. 85 // For other types they are just milliseconds since 1970. 86 // If |dialogValue| is NaN it means an empty value. We will show the current time. 87 if (Double.isNaN(dialogValue)) { 88 cal = Calendar.getInstance(); 89 cal.set(Calendar.MILLISECOND, 0); 90 } else { 91 if (dialogType == sTextInputTypeMonth) { 92 cal = MonthPicker.createDateFromValue(dialogValue); 93 } else if (dialogType == sTextInputTypeWeek) { 94 cal = WeekPicker.createDateFromValue(dialogValue); 95 } else { 96 GregorianCalendar gregorianCalendar = 97 new GregorianCalendar(TimeZone.getTimeZone("UTC")); 98 // According to the HTML spec we only use the Gregorian calendar 99 // so we ignore the Julian/Gregorian transition. 100 gregorianCalendar.setGregorianChange(new Date(Long.MIN_VALUE)); 101 gregorianCalendar.setTimeInMillis((long) dialogValue); 102 cal = gregorianCalendar; 103 } 104 } 105 if (dialogType == sTextInputTypeDate) { 106 showPickerDialog(dialogType, 107 cal.get(Calendar.YEAR), 108 cal.get(Calendar.MONTH), 109 cal.get(Calendar.DAY_OF_MONTH), 110 0, 0, 0, 0, 0, min, max, step); 111 } else if (dialogType == sTextInputTypeTime) { 112 showPickerDialog(dialogType, 0, 0, 0, 113 cal.get(Calendar.HOUR_OF_DAY), 114 cal.get(Calendar.MINUTE), 115 0, 0, 0, min, max, step); 116 } else if (dialogType == sTextInputTypeDateTime || 117 dialogType == sTextInputTypeDateTimeLocal) { 118 showPickerDialog(dialogType, 119 cal.get(Calendar.YEAR), 120 cal.get(Calendar.MONTH), 121 cal.get(Calendar.DAY_OF_MONTH), 122 cal.get(Calendar.HOUR_OF_DAY), 123 cal.get(Calendar.MINUTE), 124 cal.get(Calendar.SECOND), 125 cal.get(Calendar.MILLISECOND), 126 0, min, max, step); 127 } else if (dialogType == sTextInputTypeMonth) { 128 showPickerDialog(dialogType, cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), 0, 129 0, 0, 0, 0, 0, min, max, step); 130 } else if (dialogType == sTextInputTypeWeek) { 131 int year = WeekPicker.getISOWeekYearForDate(cal); 132 int week = WeekPicker.getWeekForDate(cal); 133 showPickerDialog(dialogType, year, 0, 0, 0, 0, 0, 0, week, min, max, step); 134 } 135 } 136 showSuggestionDialog(final int dialogType, final double dialogValue, final double min, final double max, final double step, DateTimeSuggestion[] suggestions)137 void showSuggestionDialog(final int dialogType, 138 final double dialogValue, 139 final double min, final double max, final double step, 140 DateTimeSuggestion[] suggestions) { 141 ListView suggestionListView = new ListView(mContext); 142 final DateTimeSuggestionListAdapter adapter = 143 new DateTimeSuggestionListAdapter(mContext, Arrays.asList(suggestions)); 144 suggestionListView.setAdapter(adapter); 145 suggestionListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 146 @Override 147 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 148 if (position == adapter.getCount() - 1) { 149 dismissDialog(); 150 showPickerDialog(dialogType, dialogValue, min, max, step); 151 } else { 152 double suggestionValue = adapter.getItem(position).value(); 153 mInputActionDelegate.replaceDateTime(suggestionValue); 154 dismissDialog(); 155 mDialogAlreadyDismissed = true; 156 } 157 } 158 }); 159 160 int dialogTitleId = R.string.date_picker_dialog_title; 161 if (dialogType == sTextInputTypeTime) { 162 dialogTitleId = R.string.time_picker_dialog_title; 163 } else if (dialogType == sTextInputTypeDateTime || 164 dialogType == sTextInputTypeDateTimeLocal) { 165 dialogTitleId = R.string.date_time_picker_dialog_title; 166 } else if (dialogType == sTextInputTypeMonth) { 167 dialogTitleId = R.string.month_picker_dialog_title; 168 } else if (dialogType == sTextInputTypeWeek) { 169 dialogTitleId = R.string.week_picker_dialog_title; 170 } 171 172 mDialog = new AlertDialog.Builder(mContext) 173 .setTitle(dialogTitleId) 174 .setView(suggestionListView) 175 .setNegativeButton(mContext.getText(android.R.string.cancel), 176 new DialogInterface.OnClickListener() { 177 @Override 178 public void onClick(DialogInterface dialog, int which) { 179 dismissDialog(); 180 } 181 }) 182 .create(); 183 184 mDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { 185 @Override 186 public void onDismiss(DialogInterface dialog) { 187 if (mDialog == dialog && !mDialogAlreadyDismissed) { 188 mDialogAlreadyDismissed = true; 189 mInputActionDelegate.cancelDateTimeDialog(); 190 } 191 } 192 }); 193 mDialogAlreadyDismissed = false; 194 mDialog.show(); 195 } 196 showDialog(final int type, final double value, double min, double max, double step, DateTimeSuggestion[] suggestions)197 void showDialog(final int type, final double value, 198 double min, double max, double step, 199 DateTimeSuggestion[] suggestions) { 200 // When the web page asks to show a dialog while there is one already open, 201 // dismiss the old one. 202 dismissDialog(); 203 if (suggestions == null) { 204 showPickerDialog(type, value, min, max, step); 205 } else { 206 showSuggestionDialog(type, value, min, max, step, suggestions); 207 } 208 } 209 showPickerDialog(final int dialogType, int year, int month, int monthDay, int hourOfDay, int minute, int second, int millis, int week, double min, double max, double step)210 void showPickerDialog(final int dialogType, 211 int year, int month, int monthDay, 212 int hourOfDay, int minute, int second, int millis, int week, 213 double min, double max, double step) { 214 if (isDialogShowing()) mDialog.dismiss(); 215 216 int stepTime = (int) step; 217 218 if (dialogType == sTextInputTypeDate) { 219 ChromeDatePickerDialog dialog = new ChromeDatePickerDialog(mContext, 220 new DateListener(dialogType), 221 year, month, monthDay); 222 DateDialogNormalizer.normalize(dialog.getDatePicker(), dialog, 223 year, month, monthDay, 224 0, 0, 225 (long) min, (long) max); 226 227 dialog.setTitle(mContext.getText(R.string.date_picker_dialog_title)); 228 mDialog = dialog; 229 } else if (dialogType == sTextInputTypeTime) { 230 mDialog = new MultiFieldTimePickerDialog( 231 mContext, 0 /* theme */ , 232 hourOfDay, minute, second, millis, 233 (int) min, (int) max, stepTime, 234 DateFormat.is24HourFormat(mContext), 235 new FullTimeListener(dialogType)); 236 } else if (dialogType == sTextInputTypeDateTime || 237 dialogType == sTextInputTypeDateTimeLocal) { 238 mDialog = new DateTimePickerDialog(mContext, 239 new DateTimeListener(dialogType), 240 year, month, monthDay, 241 hourOfDay, minute, 242 DateFormat.is24HourFormat(mContext), min, max); 243 } else if (dialogType == sTextInputTypeMonth) { 244 mDialog = new MonthPickerDialog(mContext, new MonthOrWeekListener(dialogType), 245 year, month, min, max); 246 } else if (dialogType == sTextInputTypeWeek) { 247 mDialog = new WeekPickerDialog(mContext, new MonthOrWeekListener(dialogType), 248 year, week, min, max); 249 } 250 if (ApiCompatibilityUtils.datePickerRequiresAccept()) { 251 mDialog.setButton(DialogInterface.BUTTON_POSITIVE, 252 mContext.getText(R.string.date_picker_dialog_set), 253 (DialogInterface.OnClickListener) mDialog); 254 } 255 256 mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, 257 mContext.getText(android.R.string.cancel), 258 (DialogInterface.OnClickListener) null); 259 260 mDialog.setButton(DialogInterface.BUTTON_NEUTRAL, 261 mContext.getText(R.string.date_picker_dialog_clear), 262 new DialogInterface.OnClickListener() { 263 @Override 264 public void onClick(DialogInterface dialog, int which) { 265 mDialogAlreadyDismissed = true; 266 mInputActionDelegate.replaceDateTime(Double.NaN); 267 } 268 }); 269 270 mDialog.setOnDismissListener( 271 new OnDismissListener() { 272 @Override 273 public void onDismiss(final DialogInterface dialog) { 274 if (!mDialogAlreadyDismissed) { 275 mDialogAlreadyDismissed = true; 276 mInputActionDelegate.cancelDateTimeDialog(); 277 } 278 } 279 }); 280 281 mDialogAlreadyDismissed = false; 282 mDialog.show(); 283 } 284 isDialogShowing()285 boolean isDialogShowing() { 286 return mDialog != null && mDialog.isShowing(); 287 } 288 dismissDialog()289 void dismissDialog() { 290 if (isDialogShowing()) mDialog.dismiss(); 291 } 292 293 private class DateListener implements OnDateSetListener { 294 private final int mDialogType; 295 DateListener(int dialogType)296 DateListener(int dialogType) { 297 mDialogType = dialogType; 298 } 299 300 @Override onDateSet(DatePicker view, int year, int month, int monthDay)301 public void onDateSet(DatePicker view, int year, int month, int monthDay) { 302 setFieldDateTimeValue(mDialogType, year, month, monthDay, 0, 0, 0, 0, 0); 303 } 304 } 305 306 private class FullTimeListener implements OnMultiFieldTimeSetListener { 307 private final int mDialogType; FullTimeListener(int dialogType)308 FullTimeListener(int dialogType) { 309 mDialogType = dialogType; 310 } 311 312 @Override onTimeSet(int hourOfDay, int minute, int second, int milli)313 public void onTimeSet(int hourOfDay, int minute, int second, int milli) { 314 setFieldDateTimeValue(mDialogType, 0, 0, 0, hourOfDay, minute, second, milli, 0); 315 } 316 } 317 318 private class DateTimeListener implements OnDateTimeSetListener { 319 private final boolean mLocal; 320 private final int mDialogType; 321 DateTimeListener(int dialogType)322 public DateTimeListener(int dialogType) { 323 mLocal = dialogType == sTextInputTypeDateTimeLocal; 324 mDialogType = dialogType; 325 } 326 327 @Override onDateTimeSet(DatePicker dateView, TimePicker timeView, int year, int month, int monthDay, int hourOfDay, int minute)328 public void onDateTimeSet(DatePicker dateView, TimePicker timeView, 329 int year, int month, int monthDay, 330 int hourOfDay, int minute) { 331 setFieldDateTimeValue(mDialogType, year, month, monthDay, hourOfDay, minute, 0, 0, 0); 332 } 333 } 334 335 private class MonthOrWeekListener implements TwoFieldDatePickerDialog.OnValueSetListener { 336 private final int mDialogType; 337 MonthOrWeekListener(int dialogType)338 MonthOrWeekListener(int dialogType) { 339 mDialogType = dialogType; 340 } 341 342 @Override onValueSet(int year, int positionInYear)343 public void onValueSet(int year, int positionInYear) { 344 if (mDialogType == sTextInputTypeMonth) { 345 setFieldDateTimeValue(mDialogType, year, positionInYear, 0, 0, 0, 0, 0, 0); 346 } else { 347 setFieldDateTimeValue(mDialogType, year, 0, 0, 0, 0, 0, 0, positionInYear); 348 } 349 } 350 } 351 setFieldDateTimeValue(int dialogType, int year, int month, int monthDay, int hourOfDay, int minute, int second, int millis, int week)352 protected void setFieldDateTimeValue(int dialogType, 353 int year, int month, int monthDay, 354 int hourOfDay, int minute, int second, int millis, 355 int week) { 356 // Prevents more than one callback being sent to the native 357 // side when the dialog triggers multiple events. 358 if (mDialogAlreadyDismissed) 359 return; 360 mDialogAlreadyDismissed = true; 361 362 if (dialogType == sTextInputTypeMonth) { 363 mInputActionDelegate.replaceDateTime((year - 1970) * 12 + month); 364 } else if (dialogType == sTextInputTypeWeek) { 365 mInputActionDelegate.replaceDateTime( 366 WeekPicker.createDateFromWeek(year, week).getTimeInMillis()); 367 } else if (dialogType == sTextInputTypeTime) { 368 mInputActionDelegate.replaceDateTime(TimeUnit.HOURS.toMillis(hourOfDay) + 369 TimeUnit.MINUTES.toMillis(minute) + 370 TimeUnit.SECONDS.toMillis(second) + 371 millis); 372 } else { 373 Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); 374 cal.clear(); 375 cal.set(Calendar.YEAR, year); 376 cal.set(Calendar.MONTH, month); 377 cal.set(Calendar.DAY_OF_MONTH, monthDay); 378 cal.set(Calendar.HOUR_OF_DAY, hourOfDay); 379 cal.set(Calendar.MINUTE, minute); 380 cal.set(Calendar.SECOND, second); 381 cal.set(Calendar.MILLISECOND, millis); 382 mInputActionDelegate.replaceDateTime(cal.getTimeInMillis()); 383 } 384 } 385 } 386