Download later: Implement date time picker with Android dialog APIs.
Currently we implement date time picker using clank modal dialog system
and embedded the DatePicker and TimePicker view inside of it. However
Android uses hard code width for the date picker, and these embedded
view implementations will keep changing across Android versions.
This CL adds an implementation that uses Android's DatePickerDialog
and TimePickerDialog API to implement the download later date time
picker.
Test will be added in a separate CL.
Bug: 1107611
Change-Id: If64cfe710ca509eff0c9591a3451ff34f30c8b00
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2315481
Reviewed-by: David Trainor <dtrainor@chromium.org>
Commit-Queue: Xing Liu <xingliu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#791332}
diff --git a/chrome/browser/download/android/BUILD.gn b/chrome/browser/download/android/BUILD.gn
index d14c6f0..e49d4ff 100644
--- a/chrome/browser/download/android/BUILD.gn
+++ b/chrome/browser/download/android/BUILD.gn
@@ -22,7 +22,9 @@
"java/src/org/chromium/chrome/browser/download/MediaStoreHelper.java",
"java/src/org/chromium/chrome/browser/download/MimeUtils.java",
"java/src/org/chromium/chrome/browser/download/StringUtils.java",
+ "java/src/org/chromium/chrome/browser/download/dialogs/DownloadDateTimePickerDialog.java",
"java/src/org/chromium/chrome/browser/download/dialogs/DownloadDateTimePickerDialogCoordinator.java",
+ "java/src/org/chromium/chrome/browser/download/dialogs/DownloadDateTimePickerDialogImpl.java",
"java/src/org/chromium/chrome/browser/download/dialogs/DownloadDateTimePickerDialogProperties.java",
"java/src/org/chromium/chrome/browser/download/dialogs/DownloadDateTimePickerView.java",
"java/src/org/chromium/chrome/browser/download/dialogs/DownloadLaterDialogChoice.java",
diff --git a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/DownloadDialogBridge.java b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/DownloadDialogBridge.java
index 8fd3531..6f01b7ca 100644
--- a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/DownloadDialogBridge.java
+++ b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/DownloadDialogBridge.java
@@ -10,7 +10,8 @@
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.chrome.browser.download.DownloadLaterMetrics.DownloadLaterUiEvent;
-import org.chromium.chrome.browser.download.dialogs.DownloadDateTimePickerDialogCoordinator;
+import org.chromium.chrome.browser.download.dialogs.DownloadDateTimePickerDialog;
+import org.chromium.chrome.browser.download.dialogs.DownloadDateTimePickerDialogImpl;
import org.chromium.chrome.browser.download.dialogs.DownloadLaterDialogChoice;
import org.chromium.chrome.browser.download.dialogs.DownloadLaterDialogController;
import org.chromium.chrome.browser.download.dialogs.DownloadLaterDialogCoordinator;
@@ -72,8 +73,7 @@
@CalledByNative
private static DownloadDialogBridge create(long nativeDownloadDialogBridge) {
DownloadLocationDialogCoordinator locationDialog = new DownloadLocationDialogCoordinator();
- DownloadDateTimePickerDialogCoordinator dateTimePickerDialog =
- new DownloadDateTimePickerDialogCoordinator();
+ DownloadDateTimePickerDialog dateTimePickerDialog = new DownloadDateTimePickerDialogImpl();
DownloadLaterDialogCoordinator downloadLaterDialog =
new DownloadLaterDialogCoordinator(dateTimePickerDialog);
dateTimePickerDialog.initialize(downloadLaterDialog);
diff --git a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadDateTimePickerDialog.java b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadDateTimePickerDialog.java
new file mode 100644
index 0000000..388bcfb
--- /dev/null
+++ b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadDateTimePickerDialog.java
@@ -0,0 +1,55 @@
+// Copyright 2020 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.chrome.browser.download.dialogs;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import org.chromium.ui.modaldialog.ModalDialogManager;
+import org.chromium.ui.modelutil.PropertyModel;
+
+/**
+ * A date time picker for the user to select download start time. The dialog has two stage:
+ * 1. A calendar to let the user to pick the date.
+ * 2. A clock to let the user to pick a time.
+ */
+public interface DownloadDateTimePickerDialog {
+ /**
+ * The controller that receives events from the date time picker.
+ */
+ interface Controller {
+ /**
+ * Called when the user picked the time from date picker and time picker.
+ * @param time The time the user picked as a unix timestamp.
+ */
+ void onDateTimePicked(long time);
+
+ /**
+ * The user canceled date time picking flow.
+ */
+ void onDateTimePickerCanceled();
+ }
+
+ /**
+ * Initializes the download date time picker dialog.
+ * @param controller The controller that receives events from the date time picker.
+ */
+ void initialize(@NonNull Controller controller);
+
+ /**
+ * Shows the date time picker.
+ * @param context The {@link Context} for the date time picker.
+ * @param modalDialogManager Used to show/dismiss modal dialog.
+ * @param model The model that defines the application data used to update the UI view.
+ */
+ void showDialog(Context context, ModalDialogManager modalDialogManager, PropertyModel model);
+
+ /**
+ * Destroys the download date time picker dialog. Usually called before the associated activity
+ * is destroyed.
+ */
+ void destroy();
+}
diff --git a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadDateTimePickerDialogCoordinator.java b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadDateTimePickerDialogCoordinator.java
index c4dd986..11ce3be 100644
--- a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadDateTimePickerDialogCoordinator.java
+++ b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadDateTimePickerDialogCoordinator.java
@@ -18,29 +18,13 @@
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
/**
- * The coordinator for download date time picker. The user can pick an exact time to start the
- * download later. The dialog has two stage:
- * 1. A calendar to let the user to pick the date.
- * 2. A clock to let the user to pick a time.
+ * A {@link DownloadDateTimePickerDialog} that uses Clank modal dialog and embeds the date picker
+ * and time picker view.
*/
-public class DownloadDateTimePickerDialogCoordinator implements ModalDialogProperties.Controller {
- /**
- * The controller that receives events from the date time picker.
- */
- public interface Controller {
- /**
- * Called when the user picked the time from date picker and time picker.
- * @param time The time the user picked as a unix timestamp.
- */
- void onDateTimePicked(long time);
-
- /**
- * The user canceled date time picking flow.
- */
- void onDateTimePickerCanceled();
- }
-
- private Controller mController;
+// TODO(xingliu): Remove this.
+public class DownloadDateTimePickerDialogCoordinator
+ implements DownloadDateTimePickerDialog, ModalDialogProperties.Controller {
+ private DownloadDateTimePickerDialog.Controller mController;
private ModalDialogManager mModalDialogManager;
@@ -49,20 +33,13 @@
private DownloadDateTimePickerView mView;
private PropertyModelChangeProcessor mProcessor;
- /**
- * Initializes the download date time picker dialog.
- * @param controller The controller that receives events from the date time picker.
- */
- public void initialize(@NonNull Controller controller) {
+ // DownloadDateTimePickerDialog implementation.
+ @Override
+ public void initialize(@NonNull DownloadDateTimePickerDialog.Controller controller) {
mController = controller;
}
- /**
- * Shows the date time picker.
- * @param context The {@link Context} for the date time picker.
- * @param windowAndroid The window android handle that provides contexts.
- * @param model The model that defines the application data used to update the UI view.
- */
+ @Override
public void showDialog(
Context context, ModalDialogManager modalDialogManager, PropertyModel model) {
if (context == null || modalDialogManager == null) {
@@ -80,9 +57,7 @@
getModalDialogModel(context), ModalDialogManager.ModalDialogType.APP);
}
- /**
- * Destroys the download date time picker dialog.
- */
+ @Override
public void destroy() {
if (mProcessor != null) mProcessor.destroy();
}
diff --git a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadDateTimePickerDialogImpl.java b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadDateTimePickerDialogImpl.java
new file mode 100644
index 0000000..0dc5a84
--- /dev/null
+++ b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadDateTimePickerDialogImpl.java
@@ -0,0 +1,149 @@
+// Copyright 2020 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.chrome.browser.download.dialogs;
+
+import android.app.DatePickerDialog;
+import android.app.TimePickerDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.widget.DatePicker;
+import android.widget.TimePicker;
+
+import androidx.annotation.NonNull;
+
+import org.chromium.base.Log;
+import org.chromium.chrome.browser.download.R;
+import org.chromium.ui.modaldialog.ModalDialogManager;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.PropertyModel.ReadableObjectPropertyKey;
+
+import java.util.Calendar;
+
+/**
+ * An implementation of {@link DownloadDateTimePickerDialog} that glues
+ * {@link android.app.DatePickerDialog} and {@link android.app.TimePickerDialog} widgets. The user
+ * will see the date picker and time picker in a sequence when trying to select a time.
+ */
+public class DownloadDateTimePickerDialogImpl
+ implements DownloadDateTimePickerDialog, TimePickerDialog.OnTimeSetListener {
+ private static final String TAG = "DownloadTimePicker";
+ private static final long INVALID_TIMESTAMP = -1;
+ private DatePickerDialog mDatePickerDialog;
+ private TimePickerDialog mTimePickerDialog;
+ private Controller mController;
+ private final Calendar mCalendar = Calendar.getInstance();
+
+ @Override
+ public void initialize(@NonNull Controller controller) {
+ mController = controller;
+ }
+
+ @Override
+ public void showDialog(
+ Context context, ModalDialogManager modalDialogManager, PropertyModel model) {
+ // Reset and compute the initial time.
+ long initialTime = getLong(model, DownloadDateTimePickerDialogProperties.INITIAL_TIME,
+ System.currentTimeMillis());
+ mCalendar.setTimeInMillis(initialTime);
+
+ // Reset dialogs.
+ destroy();
+
+ // Setup the date picker. Use null DatePickerDialog.OnDateSetListener due to Android API
+ // issue.
+ mDatePickerDialog = new DatePickerDialog(context, null, mCalendar.get(Calendar.YEAR),
+ mCalendar.get(Calendar.MONTH), mCalendar.get(Calendar.DAY_OF_MONTH));
+ long minDate =
+ getLong(model, DownloadDateTimePickerDialogProperties.MIN_TIME, INVALID_TIMESTAMP);
+ long maxDate =
+ getLong(model, DownloadDateTimePickerDialogProperties.MAX_TIME, INVALID_TIMESTAMP);
+ if (minDate > 0) mDatePickerDialog.getDatePicker().setMinDate(minDate);
+ if (maxDate > 0) mDatePickerDialog.getDatePicker().setMaxDate(maxDate);
+
+ mDatePickerDialog.setButton(DialogInterface.BUTTON_POSITIVE,
+ context.getResources().getString(R.string.download_date_time_picker_next_text),
+ this::onDatePickerClicked);
+ mDatePickerDialog.setButton(DialogInterface.BUTTON_NEGATIVE,
+ context.getResources().getString(R.string.cancel), this::onDatePickerClicked);
+
+ // Setup the time picker.
+ mTimePickerDialog = new TimePickerDialog(context, this, mCalendar.get(Calendar.HOUR),
+ mCalendar.get(Calendar.MINUTE), false /*is24HourView*/);
+ mTimePickerDialog.setButton(DialogInterface.BUTTON_POSITIVE,
+ context.getResources().getString(R.string.download_date_time_picker_next_text),
+ this::onTimePickerClicked);
+ mTimePickerDialog.setButton(DialogInterface.BUTTON_NEGATIVE,
+ context.getResources().getString(R.string.cancel), this::onTimePickerClicked);
+
+ // Start the flow.
+ mDatePickerDialog.show();
+ }
+
+ @Override
+ public void destroy() {
+ if (mDatePickerDialog != null) mDatePickerDialog.dismiss();
+ if (mTimePickerDialog != null) mTimePickerDialog.dismiss();
+ }
+
+ // TimePickerDialog.OnTimeSetListener.
+ @Override
+ public void onTimeSet(TimePicker timePicker, int hourOfDay, int minute) {
+ mCalendar.set(Calendar.HOUR, hourOfDay);
+ mCalendar.set(Calendar.MINUTE, minute);
+
+ // Click on the clock will complete the selection as well.
+ onComplete();
+ }
+
+ private void onDatePickerClicked(DialogInterface dialogInterface, int which) {
+ switch (which) {
+ case DialogInterface.BUTTON_POSITIVE:
+ DatePicker datePicker = mDatePickerDialog.getDatePicker();
+ mCalendar.set(Calendar.YEAR, datePicker.getYear());
+ mCalendar.set(Calendar.MONTH, datePicker.getMonth());
+ mCalendar.set(Calendar.DAY_OF_MONTH, datePicker.getDayOfMonth());
+
+ // Show the time picker after the date picker is done.
+ mTimePickerDialog.show();
+ break;
+ case DialogInterface.BUTTON_NEGATIVE:
+ onCancel();
+ break;
+ default:
+ Log.e(TAG, "Unsupported button type clicked in date picker, type: %d", which);
+ }
+ }
+
+ private void onTimePickerClicked(DialogInterface dialogInterface, int which) {
+ switch (which) {
+ case DialogInterface.BUTTON_POSITIVE:
+ // Handled in TimePickerDialog.OnTimeSetListener.
+ break;
+ case DialogInterface.BUTTON_NEGATIVE:
+ onCancel();
+ break;
+ default:
+ Log.e(TAG, "Unsupported button type clicked in time picker, type: %d", which);
+ }
+ }
+
+ private void onCancel() {
+ assert mController != null;
+ mCalendar.clear();
+ mController.onDateTimePickerCanceled();
+ }
+
+ private void onComplete() {
+ assert mController != null;
+ mController.onDateTimePicked(mCalendar.getTimeInMillis());
+ mCalendar.clear();
+ }
+
+ private static long getLong(
+ PropertyModel model, ReadableObjectPropertyKey<Long> key, long defaultValue) {
+ Long value = model.get(key);
+ return (value != null) ? value : defaultValue;
+ }
+}
diff --git a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadDateTimePickerView.java b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadDateTimePickerView.java
index d8725f4b..09e3e33 100644
--- a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadDateTimePickerView.java
+++ b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadDateTimePickerView.java
@@ -23,6 +23,7 @@
/**
* The view for download date time picker. Contains a {@link DatePicker} and a {@link TimePicker}.
*/
+// TODO(xingliu): Remove this.
public class DownloadDateTimePickerView extends LinearLayout {
/**
* The view binder to propagate events from model to view.
diff --git a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLaterDialogCoordinator.java b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLaterDialogCoordinator.java
index 6bd7e55..46646f2 100644
--- a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLaterDialogCoordinator.java
+++ b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLaterDialogCoordinator.java
@@ -25,9 +25,9 @@
/**
* Coordinator to construct the download later dialog.
*/
-public class DownloadLaterDialogCoordinator
- implements ModalDialogProperties.Controller, DownloadLaterDialogView.Controller,
- DownloadDateTimePickerDialogCoordinator.Controller {
+public class DownloadLaterDialogCoordinator implements ModalDialogProperties.Controller,
+ DownloadLaterDialogView.Controller,
+ DownloadDateTimePickerDialog.Controller {
private static final long INVALID_START_TIME = -1;
private PropertyModel mDownloadLaterDialogModel;
private DownloadLaterDialogView mCustomView;
@@ -41,7 +41,7 @@
mPropertyModelChangeProcessor;
private DownloadLaterDialogController mController;
- private final DownloadDateTimePickerDialogCoordinator mDateTimePickerDialog;
+ private final DownloadDateTimePickerDialog mDateTimePickerDialog;
@DownloadLaterDialogChoice
private int mDownloadLaterChoice = DownloadLaterDialogChoice.DOWNLOAD_NOW;
@@ -51,7 +51,7 @@
* @param dateTimePickerDialog The date time selection widget.
*/
public DownloadLaterDialogCoordinator(
- @NonNull DownloadDateTimePickerDialogCoordinator dateTimePickerDialog) {
+ @NonNull DownloadDateTimePickerDialog dateTimePickerDialog) {
mDateTimePickerDialog = dateTimePickerDialog;
}
@@ -216,7 +216,7 @@
notifyCancel();
}
- // DownloadDateTimePickerDialogCoordinator.Controller implementation.
+ // DownloadDateTimePickerDialog.Controller implementation.
@Override
public void onDateTimePicked(long time) {
DownloadLaterMetrics.recordDownloadLaterUiEvent(
diff --git a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLaterDialogHelper.java b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLaterDialogHelper.java
index e375e62b..c03e85b 100644
--- a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLaterDialogHelper.java
+++ b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLaterDialogHelper.java
@@ -49,8 +49,7 @@
*/
public static DownloadLaterDialogHelper create(
Context context, ModalDialogManager manager, PrefService prefService) {
- DownloadDateTimePickerDialogCoordinator dateTimePicker =
- new DownloadDateTimePickerDialogCoordinator();
+ DownloadDateTimePickerDialog dateTimePicker = new DownloadDateTimePickerDialogImpl();
DownloadLaterDialogCoordinator dialog = new DownloadLaterDialogCoordinator(dateTimePicker);
dateTimePicker.initialize(dialog);
return new DownloadLaterDialogHelper(context, manager, prefService, dialog);