// Copyright 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.content.browser.input; import android.app.AlertDialog; import android.app.DatePickerDialog; import android.app.DatePickerDialog.OnDateSetListener; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnDismissListener; import android.text.format.DateFormat; import android.view.View; import android.widget.AdapterView; import android.widget.DatePicker; import android.widget.ListView; import android.widget.TimePicker; import org.chromium.content.R; import org.chromium.content.browser.input.DateTimePickerDialog.OnDateTimeSetListener; import org.chromium.content.browser.input.MultiFieldTimePickerDialog.OnMultiFieldTimeSetListener; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.TimeZone; import java.util.concurrent.TimeUnit; /** * Opens the approprate date/time picker dialog for the given dialog type. */ public class InputDialogContainer { interface InputActionDelegate { void cancelDateTimeDialog(); void replaceDateTime(double value); } private static int sTextInputTypeDate; private static int sTextInputTypeDateTime; private static int sTextInputTypeDateTimeLocal; private static int sTextInputTypeMonth; private static int sTextInputTypeTime; private static int sTextInputTypeWeek; private final Context mContext; // Prevents sending two notifications (from onClick and from onDismiss) private boolean mDialogAlreadyDismissed; private AlertDialog mDialog; private final InputActionDelegate mInputActionDelegate; static void initializeInputTypes(int textInputTypeDate, int textInputTypeDateTime, int textInputTypeDateTimeLocal, int textInputTypeMonth, int textInputTypeTime, int textInputTypeWeek) { sTextInputTypeDate = textInputTypeDate; sTextInputTypeDateTime = textInputTypeDateTime; sTextInputTypeDateTimeLocal = textInputTypeDateTimeLocal; sTextInputTypeMonth = textInputTypeMonth; sTextInputTypeTime = textInputTypeTime; sTextInputTypeWeek = textInputTypeWeek; } static boolean isDialogInputType(int type) { return type == sTextInputTypeDate || type == sTextInputTypeTime || type == sTextInputTypeDateTime || type == sTextInputTypeDateTimeLocal || type == sTextInputTypeMonth || type == sTextInputTypeWeek; } InputDialogContainer(Context context, InputActionDelegate inputActionDelegate) { mContext = context; mInputActionDelegate = inputActionDelegate; } void showPickerDialog(final int dialogType, double dialogValue, double min, double max, double step) { Calendar cal; // |dialogValue|, |min|, |max| mean different things depending on the |dialogType|. // For input type=month is the number of months since 1970. // For input type=time it is milliseconds since midnight. // For other types they are just milliseconds since 1970. // If |dialogValue| is NaN it means an empty value. We will show the current time. if (Double.isNaN(dialogValue)) { cal = Calendar.getInstance(); cal.set(Calendar.MILLISECOND, 0); } else { if (dialogType == sTextInputTypeMonth) { cal = MonthPicker.createDateFromValue(dialogValue); } else if (dialogType == sTextInputTypeWeek) { cal = WeekPicker.createDateFromValue(dialogValue); } else { GregorianCalendar gregorianCalendar = new GregorianCalendar(TimeZone.getTimeZone("UTC")); // According to the HTML spec we only use the Gregorian calendar // so we ignore the Julian/Gregorian transition. gregorianCalendar.setGregorianChange(new Date(Long.MIN_VALUE)); gregorianCalendar.setTimeInMillis((long) dialogValue); cal = gregorianCalendar; } } if (dialogType == sTextInputTypeDate) { showPickerDialog(dialogType, cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), 0, 0, 0, 0, 0, min, max, step); } else if (dialogType == sTextInputTypeTime) { showPickerDialog(dialogType, 0, 0, 0, cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), 0, 0, 0, min, max, step); } else if (dialogType == sTextInputTypeDateTime || dialogType == sTextInputTypeDateTimeLocal) { showPickerDialog(dialogType, cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND), cal.get(Calendar.MILLISECOND), 0, min, max, step); } else if (dialogType == sTextInputTypeMonth) { showPickerDialog(dialogType, cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), 0, 0, 0, 0, 0, 0, min, max, step); } else if (dialogType == sTextInputTypeWeek) { int year = WeekPicker.getISOWeekYearForDate(cal); int week = WeekPicker.getWeekForDate(cal); showPickerDialog(dialogType, year, 0, 0, 0, 0, 0, 0, week, min, max, step); } } void showSuggestionDialog(final int dialogType, final double dialogValue, final double min, final double max, final double step, DateTimeSuggestion[] suggestions) { ListView suggestionListView = new ListView(mContext); final DateTimeSuggestionListAdapter adapter = new DateTimeSuggestionListAdapter(mContext, Arrays.asList(suggestions)); suggestionListView.setAdapter(adapter); suggestionListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (position == adapter.getCount() - 1) { dismissDialog(); showPickerDialog(dialogType, dialogValue, min, max, step); } else { double suggestionValue = adapter.getItem(position).value(); mInputActionDelegate.replaceDateTime(suggestionValue); dismissDialog(); mDialogAlreadyDismissed = true; } } }); int dialogTitleId = R.string.date_picker_dialog_title; if (dialogType == sTextInputTypeTime) { dialogTitleId = R.string.time_picker_dialog_title; } else if (dialogType == sTextInputTypeDateTime || dialogType == sTextInputTypeDateTimeLocal) { dialogTitleId = R.string.date_time_picker_dialog_title; } else if (dialogType == sTextInputTypeMonth) { dialogTitleId = R.string.month_picker_dialog_title; } else if (dialogType == sTextInputTypeWeek) { dialogTitleId = R.string.week_picker_dialog_title; } mDialog = new AlertDialog.Builder(mContext) .setTitle(dialogTitleId) .setView(suggestionListView) .setNegativeButton(mContext.getText(android.R.string.cancel), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dismissDialog(); } }) .create(); mDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { if (mDialog == dialog && !mDialogAlreadyDismissed) { mDialogAlreadyDismissed = true; mInputActionDelegate.cancelDateTimeDialog(); } } }); mDialogAlreadyDismissed = false; mDialog.show(); } void showDialog(final int type, final double value, double min, double max, double step, DateTimeSuggestion[] suggestions) { // When the web page asks to show a dialog while there is one already open, // dismiss the old one. dismissDialog(); if (suggestions == null) { showPickerDialog(type, value, min, max, step); } else { showSuggestionDialog(type, value, min, max, step, suggestions); } } void 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) { if (isDialogShowing()) mDialog.dismiss(); int stepTime = (int) step; if (dialogType == sTextInputTypeDate) { DatePickerDialog dialog = new DatePickerDialog(mContext, new DateListener(dialogType), year, month, monthDay); DateDialogNormalizer.normalize(dialog.getDatePicker(), dialog, year, month, monthDay, 0, 0, (long) min, (long) max); dialog.setTitle(mContext.getText(R.string.date_picker_dialog_title)); mDialog = dialog; } else if (dialogType == sTextInputTypeTime) { mDialog = new MultiFieldTimePickerDialog( mContext, 0 /* theme */ , hourOfDay, minute, second, millis, (int) min, (int) max, stepTime, DateFormat.is24HourFormat(mContext), new FullTimeListener(dialogType)); } else if (dialogType == sTextInputTypeDateTime || dialogType == sTextInputTypeDateTimeLocal) { mDialog = new DateTimePickerDialog(mContext, new DateTimeListener(dialogType), year, month, monthDay, hourOfDay, minute, DateFormat.is24HourFormat(mContext), min, max); } else if (dialogType == sTextInputTypeMonth) { mDialog = new MonthPickerDialog(mContext, new MonthOrWeekListener(dialogType), year, month, min, max); } else if (dialogType == sTextInputTypeWeek) { mDialog = new WeekPickerDialog(mContext, new MonthOrWeekListener(dialogType), year, week, min, max); } mDialog.setButton(DialogInterface.BUTTON_POSITIVE, mContext.getText(R.string.date_picker_dialog_set), (DialogInterface.OnClickListener) mDialog); mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, mContext.getText(android.R.string.cancel), (DialogInterface.OnClickListener) null); mDialog.setButton(DialogInterface.BUTTON_NEUTRAL, mContext.getText(R.string.date_picker_dialog_clear), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { mDialogAlreadyDismissed = true; mInputActionDelegate.replaceDateTime(Double.NaN); } }); mDialog.setOnDismissListener( new OnDismissListener() { @Override public void onDismiss(final DialogInterface dialog) { if (!mDialogAlreadyDismissed) { mDialogAlreadyDismissed = true; mInputActionDelegate.cancelDateTimeDialog(); } } }); mDialogAlreadyDismissed = false; mDialog.show(); } boolean isDialogShowing() { return mDialog != null && mDialog.isShowing(); } void dismissDialog() { if (isDialogShowing()) mDialog.dismiss(); } private class DateListener implements OnDateSetListener { private final int mDialogType; DateListener(int dialogType) { mDialogType = dialogType; } @Override public void onDateSet(DatePicker view, int year, int month, int monthDay) { setFieldDateTimeValue(mDialogType, year, month, monthDay, 0, 0, 0, 0, 0); } } private class FullTimeListener implements OnMultiFieldTimeSetListener { private final int mDialogType; FullTimeListener(int dialogType) { mDialogType = dialogType; } @Override public void onTimeSet(int hourOfDay, int minute, int second, int milli) { setFieldDateTimeValue(mDialogType, 0, 0, 0, hourOfDay, minute, second, milli, 0); } } private class DateTimeListener implements OnDateTimeSetListener { private final boolean mLocal; private final int mDialogType; public DateTimeListener(int dialogType) { mLocal = dialogType == sTextInputTypeDateTimeLocal; mDialogType = dialogType; } @Override public void onDateTimeSet(DatePicker dateView, TimePicker timeView, int year, int month, int monthDay, int hourOfDay, int minute) { setFieldDateTimeValue(mDialogType, year, month, monthDay, hourOfDay, minute, 0, 0, 0); } } private class MonthOrWeekListener implements TwoFieldDatePickerDialog.OnValueSetListener { private final int mDialogType; MonthOrWeekListener(int dialogType) { mDialogType = dialogType; } @Override public void onValueSet(int year, int positionInYear) { if (mDialogType == sTextInputTypeMonth) { setFieldDateTimeValue(mDialogType, year, positionInYear, 0, 0, 0, 0, 0, 0); } else { setFieldDateTimeValue(mDialogType, year, 0, 0, 0, 0, 0, 0, positionInYear); } } } protected void setFieldDateTimeValue(int dialogType, int year, int month, int monthDay, int hourOfDay, int minute, int second, int millis, int week) { // Prevents more than one callback being sent to the native // side when the dialog triggers multiple events. if (mDialogAlreadyDismissed) return; mDialogAlreadyDismissed = true; if (dialogType == sTextInputTypeMonth) { mInputActionDelegate.replaceDateTime((year - 1970) * 12 + month); } else if (dialogType == sTextInputTypeWeek) { mInputActionDelegate.replaceDateTime( WeekPicker.createDateFromWeek(year, week).getTimeInMillis()); } else if (dialogType == sTextInputTypeTime) { mInputActionDelegate.replaceDateTime(TimeUnit.HOURS.toMillis(hourOfDay) + TimeUnit.MINUTES.toMillis(minute) + TimeUnit.SECONDS.toMillis(second) + millis); } else { Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); cal.clear(); cal.set(Calendar.YEAR, year); cal.set(Calendar.MONTH, month); cal.set(Calendar.DAY_OF_MONTH, monthDay); cal.set(Calendar.HOUR_OF_DAY, hourOfDay); cal.set(Calendar.MINUTE, minute); cal.set(Calendar.SECOND, second); cal.set(Calendar.MILLISECOND, millis); mInputActionDelegate.replaceDateTime(cal.getTimeInMillis()); } } }