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