| // Copyright 2017 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.modaldialog; |
| |
| import android.support.annotation.IntDef; |
| import android.support.annotation.NonNull; |
| import android.support.annotation.Nullable; |
| import android.util.SparseArray; |
| import android.view.View; |
| |
| import org.chromium.base.VisibleForTesting; |
| import org.chromium.ui.UiUtils; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * Manager for managing the display of a queue of {@link ModalDialogView}s. |
| */ |
| public class ModalDialogManager { |
| /** |
| * Present a {@link ModalDialogView} in a container. |
| */ |
| public static abstract class Presenter { |
| private Runnable mCancelCallback; |
| private ModalDialogView mModalDialog; |
| private View mCurrentView; |
| |
| /** |
| * @param dialog The dialog that's currently showing in this presenter. If null, no dialog |
| * is currently showing. |
| */ |
| private void setModalDialog( |
| @Nullable ModalDialogView dialog, @Nullable Runnable cancelCallback) { |
| if (dialog == null) { |
| removeDialogView(mCurrentView); |
| mModalDialog = null; |
| mCancelCallback = null; |
| } else { |
| assert mModalDialog |
| == null : "Should call setModalDialog(null) before setting a modal dialog."; |
| mModalDialog = dialog; |
| mCurrentView = dialog.getView(); |
| mCancelCallback = cancelCallback; |
| // Make sure the view is detached from any parent before adding it to the container. |
| // This is not detached after removeDialogView() because there could be animation |
| // running on removing the dialog view. |
| UiUtils.removeViewFromParent(mCurrentView); |
| addDialogView(mCurrentView); |
| } |
| } |
| |
| /** |
| * Run the cached cancel callback and reset the cached callback. |
| */ |
| protected final void cancelCurrentDialog() { |
| if (mCancelCallback == null) return; |
| |
| // Set #mCancelCallback to null before calling the callback to avoid it being |
| // updated during the callback. |
| Runnable callback = mCancelCallback; |
| mCancelCallback = null; |
| callback.run(); |
| } |
| |
| /** |
| * @return The modal dialog that this presenter is showing. |
| */ |
| protected final ModalDialogView getModalDialog() { |
| return mModalDialog; |
| } |
| |
| /** |
| * Add the specified {@link ModalDialogView} in a container. |
| * @param dialogView The {@link ModalDialogView} that needs to be shown. |
| */ |
| protected abstract void addDialogView(View dialogView); |
| |
| /** |
| * Remove the specified {@link ModalDialogView} from a container. |
| * @param dialogView The {@link ModalDialogView} that needs to be removed. |
| */ |
| protected abstract void removeDialogView(View dialogView); |
| } |
| |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef({APP_MODAL, TAB_MODAL}) |
| public @interface ModalDialogType {} |
| |
| /** |
| * The integer assigned to each type represents its priority. A smaller number represents a |
| * higher priority type of dialog. |
| */ |
| public static final int APP_MODAL = 0; |
| public static final int TAB_MODAL = 1; |
| |
| /** Mapping of the {@link Presenter}s and the type of dialogs they are showing. */ |
| private final SparseArray<Presenter> mPresenters = new SparseArray<>(); |
| |
| /** Mapping of the lists of pending dialogs and the type of the dialogs. */ |
| private final SparseArray<List<ModalDialogView>> mPendingDialogs = new SparseArray<>(); |
| |
| /** |
| * The list of suspended types of dialogs. The dialogs of types in the list will be suspended |
| * from showing and will only be shown after {@link #resumeType(int)} is called. |
| */ |
| private final Set<Integer> mSuspendedTypes = new HashSet<>(); |
| |
| /** The default presenter to be used if a specified type is not supported. */ |
| private final Presenter mDefaultPresenter; |
| |
| /** |
| * The presenter of the type of the dialog that is currently showing. Note that if there is no |
| * matching {@link Presenter} for {@link #mCurrentType}, this will be the default presenter. |
| */ |
| private Presenter mCurrentPresenter; |
| |
| /** |
| * The type of the current dialog. This can be different from the type of the current |
| * {@link Presenter} if there is no registered presenter for this type. |
| */ |
| private @ModalDialogType int mCurrentType; |
| |
| /** |
| * True if the current dialog is in the process of being dismissed. |
| */ |
| private boolean mDismissingCurrentDialog; |
| |
| /** |
| * Constructor for initializing default {@link Presenter}. |
| * @param defaultPresenter The default presenter to be used when no presenter specified. |
| * @param defaultType The dialog type of the default presenter. |
| */ |
| public ModalDialogManager( |
| @NonNull Presenter defaultPresenter, @ModalDialogType int defaultType) { |
| mDefaultPresenter = defaultPresenter; |
| registerPresenter(defaultPresenter, defaultType); |
| } |
| |
| /** Clears any dependencies on the showing or pending dialogs. */ |
| public void destroy() { |
| cancelAllDialogs(); |
| } |
| |
| /** |
| * Register a {@link Presenter} that shows a specific type of dialog. Note that only one |
| * presenter of each type can be registered. |
| * @param presenter The {@link Presenter} to be registered. |
| * @param dialogType The type of the dialog shown by the specified presenter. |
| */ |
| public void registerPresenter(Presenter presenter, @ModalDialogType int dialogType) { |
| assert mPresenters.get(dialogType) |
| == null : "Only one presenter can be registered for each type."; |
| mPresenters.put(dialogType, presenter); |
| } |
| |
| /** |
| * @return Whether a dialog is currently showing. |
| */ |
| public boolean isShowing() { |
| return mCurrentPresenter != null; |
| } |
| |
| /** |
| * Show the specified dialog. If another dialog is currently showing, the specified dialog will |
| * be added to the pending dialog list. |
| * @param dialog The dialog to be shown or added to pending list. |
| * @param dialogType The type of the dialog to be shown. |
| */ |
| public void showDialog(ModalDialogView dialog, @ModalDialogType int dialogType) { |
| List<ModalDialogView> dialogs = mPendingDialogs.get(dialogType); |
| if (dialogs == null) mPendingDialogs.put(dialogType, dialogs = new ArrayList<>()); |
| |
| if (mSuspendedTypes.contains(dialogType)) { |
| dialogs.add(dialog); |
| return; |
| } |
| |
| if (isShowing()) { |
| // Put the new dialog in pending list if the current dialog is of higher priority. |
| if (mCurrentType <= dialogType) { |
| dialogs.add(dialog); |
| return; |
| } |
| suspendCurrentDialog(); |
| } |
| |
| assert !isShowing(); |
| dialog.prepareBeforeShow(); |
| mCurrentType = dialogType; |
| mCurrentPresenter = mPresenters.get(dialogType, mDefaultPresenter); |
| mCurrentPresenter.setModalDialog(dialog, () -> cancelDialog(dialog)); |
| } |
| |
| /** |
| * Dismiss the specified dialog. If the dialog is not currently showing, it will be removed from |
| * the pending dialog list. If the dialog is currently being dismissed this function does |
| * nothing. |
| * @param dialog The dialog to be dismissed or removed from pending list. |
| */ |
| public void dismissDialog(ModalDialogView dialog) { |
| if (mCurrentPresenter == null || dialog != mCurrentPresenter.getModalDialog()) { |
| for (int i = 0; i < mPendingDialogs.size(); ++i) { |
| List<ModalDialogView> dialogs = mPendingDialogs.valueAt(i); |
| for (int j = 0; j < dialogs.size(); ++j) { |
| if (dialogs.get(j) == dialog) { |
| dialogs.remove(j).getController().onDismiss(); |
| return; |
| } |
| } |
| } |
| // If the specified dialog is not found, return without any callbacks. |
| return; |
| } |
| |
| if (!isShowing()) return; |
| assert dialog == mCurrentPresenter.getModalDialog(); |
| if (mDismissingCurrentDialog) return; |
| mDismissingCurrentDialog = true; |
| dialog.getController().onDismiss(); |
| mCurrentPresenter.setModalDialog(null, null); |
| mCurrentPresenter = null; |
| mDismissingCurrentDialog = false; |
| showNextDialog(); |
| } |
| |
| /** |
| * Cancel showing the specified dialog. This is essentially the same as |
| * {@link #dismissDialog(ModalDialogView)} but will also call the onCancelled callback from the |
| * modal dialog. |
| * @param dialog The dialog to be cancelled. |
| */ |
| public void cancelDialog(ModalDialogView dialog) { |
| dialog.getController().onCancel(); |
| dismissDialog(dialog); |
| } |
| |
| /** |
| * Dismiss the dialog currently shown and remove all pending dialogs and call the onCancelled |
| * callbacks from the modal dialogs. |
| */ |
| public void cancelAllDialogs() { |
| for (int i = 0; i < mPendingDialogs.size(); ++i) { |
| cancelPendingDialogs(mPendingDialogs.keyAt(i)); |
| } |
| if (isShowing()) cancelDialog(mCurrentPresenter.getModalDialog()); |
| } |
| |
| /** |
| * Dismiss the dialog currently shown and remove all pending dialogs of the specified type and |
| * call the onCancelled callbacks from the modal dialogs. |
| * @param dialogType The specified type of dialog. |
| */ |
| protected void cancelAllDialogs(@ModalDialogType int dialogType) { |
| cancelPendingDialogs(dialogType); |
| if (isShowing() && dialogType == mCurrentType) { |
| cancelDialog(mCurrentPresenter.getModalDialog()); |
| } |
| } |
| |
| /** Helper method to cancel pending dialogs of the specified type. */ |
| private void cancelPendingDialogs(@ModalDialogType int dialogType) { |
| List<ModalDialogView> dialogs = mPendingDialogs.get(dialogType); |
| if (dialogs == null) return; |
| |
| while (!dialogs.isEmpty()) { |
| ModalDialogView.Controller controller = dialogs.remove(0).getController(); |
| controller.onDismiss(); |
| controller.onCancel(); |
| } |
| } |
| |
| /** |
| * Suspend all dialogs of the specified type, including the one currently shown. These dialogs |
| * will be prevented from showing unless {@link #resumeType(int)} is called after the |
| * suspension. If the current dialog is suspended, it will be moved back to the first dialog |
| * in the pending list. Any dialogs of the specified type in the pending list will be skipped. |
| * @param dialogType The specified type of dialogs to be suspended. |
| */ |
| protected void suspendType(@ModalDialogType int dialogType) { |
| mSuspendedTypes.add(dialogType); |
| if (isShowing() && dialogType == mCurrentType) { |
| suspendCurrentDialog(); |
| showNextDialog(); |
| } |
| } |
| |
| /** |
| * Resume the specified type of dialogs after suspension. |
| * @param dialogType The specified type of dialogs to be resumed. |
| */ |
| protected void resumeType(@ModalDialogType int dialogType) { |
| mSuspendedTypes.remove(dialogType); |
| if (!isShowing()) showNextDialog(); |
| } |
| |
| /** Hide the current dialog and put it back to the front of the pending list. */ |
| private void suspendCurrentDialog() { |
| assert isShowing(); |
| ModalDialogView dialogView = mCurrentPresenter.getModalDialog(); |
| mCurrentPresenter.setModalDialog(null, null); |
| mCurrentPresenter = null; |
| mPendingDialogs.get(mCurrentType).add(0, dialogView); |
| } |
| |
| /** Helper method for showing the next available dialog in the pending dialog list. */ |
| private void showNextDialog() { |
| assert !isShowing(); |
| // Show the next dialog of highest priority that its type is not suspended. |
| for (int i = 0; i < mPendingDialogs.size(); ++i) { |
| int dialogType = mPendingDialogs.keyAt(i); |
| if (mSuspendedTypes.contains(dialogType)) continue; |
| |
| List<ModalDialogView> dialogs = mPendingDialogs.valueAt(i); |
| if (!dialogs.isEmpty()) { |
| showDialog(dialogs.remove(0), dialogType); |
| return; |
| } |
| } |
| } |
| |
| @VisibleForTesting |
| public ModalDialogView getCurrentDialogForTest() { |
| return mCurrentPresenter == null ? null : mCurrentPresenter.getModalDialog(); |
| } |
| |
| @VisibleForTesting |
| List<ModalDialogView> getPendingDialogsForTest(@ModalDialogType int dialogType) { |
| return mPendingDialogs.get(dialogType); |
| } |
| |
| @VisibleForTesting |
| Presenter getPresenterForTest(@ModalDialogType int dialogType) { |
| return mPresenters.get(dialogType); |
| } |
| |
| @VisibleForTesting |
| Presenter getCurrentPresenterForTest() { |
| return mCurrentPresenter; |
| } |
| } |