Index: chrome/android/java_staging/src/org/chromium/chrome/browser/snackbar/undo/UndoBarPopupController.java |
diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/snackbar/undo/UndoBarPopupController.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/snackbar/undo/UndoBarPopupController.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..fb37bcc3f45e8d5b2dbc9c58d7a00d5a28274811 |
--- /dev/null |
+++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/snackbar/undo/UndoBarPopupController.java |
@@ -0,0 +1,204 @@ |
+// Copyright 2015 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.snackbar.undo; |
+ |
+import android.content.Context; |
+ |
+import com.google.android.apps.chrome.R; |
+ |
+import org.chromium.base.metrics.RecordHistogram; |
+import org.chromium.chrome.browser.Tab; |
+import org.chromium.chrome.browser.device.DeviceClassManager; |
+import org.chromium.chrome.browser.snackbar.SnackbarManager; |
+import org.chromium.chrome.browser.tabmodel.EmptyTabModelObserver; |
+import org.chromium.chrome.browser.tabmodel.TabModel; |
+import org.chromium.chrome.browser.tabmodel.TabModelObserver; |
+import org.chromium.chrome.browser.tabmodel.TabModelSelector; |
+ |
+import java.util.List; |
+import java.util.Locale; |
+ |
+/** |
+ * A controller that listens to and visually represents cancelable tab closures. |
+ * <p/> |
+ * Each time a tab is undoably closed via {@link TabModelObserver#tabPendingClosure(Tab)}, |
+ * this controller saves that tab id and title to the stack of SnackbarManager. It will then let |
+ * SnackbarManager to show a snackbar representing the top entry in of stack. Each added entry |
+ * resets the timeout that tracks when to commit the undoable actions. |
+ * <p/> |
+ * When the undo button is clicked, it will cancel the tab closure if any. all pending closing will |
+ * be committed. |
+ * <p/> |
+ * This class also responds to external changes to the undo state by monitoring |
+ * {@link TabModelObserver#tabClosureUndone(Tab)} and |
+ * {@link TabModelObserver#tabClosureCommitted(Tab)} to properly keep it's internal state |
+ * in sync with the model. |
+ */ |
+public class UndoBarPopupController implements SnackbarManager.SnackbarController { |
+ // AndroidTabCloseUndoToastEvent defined in tools/metrics/histograms/histograms.xml. |
+ private static final int TAB_CLOSE_UNDO_TOAST_SHOWN_COLD = 0; |
+ private static final int TAB_CLOSE_UNDO_TOAST_SHOWN_WARM = 1; |
+ private static final int TAB_CLOSE_UNDO_TOAST_PRESSED = 2; |
+ private static final int TAB_CLOSE_UNDO_TOAST_DISMISSED_TIMEOUT = 3; |
+ private static final int TAB_CLOSE_UNDO_TOAST_DISMISSED_ACTION = 4; |
+ private static final int TAB_CLOSE_UNDO_TOAST_COUNT = 5; |
+ |
+ private final TabModelSelector mTabModelSelector; |
+ private final TabModelObserver mTabModelObserver; |
+ private final SnackbarManager mSnackbarManager; |
+ private final Context mContext; |
+ |
+ /** |
+ * Creates an instance of a {@link UndoBarPopupController}. |
+ * @param context The {@link Context} in which snackbar is shown. |
+ * @param selector The {@link TabModelSelector} that will be used to commit and undo tab |
+ * closures. |
+ * @param snackbarManager The manager that helps to show up snackbar. |
+ */ |
+ public UndoBarPopupController(Context context, TabModelSelector selector, |
+ SnackbarManager snackbarManager) { |
+ mSnackbarManager = snackbarManager; |
+ mTabModelSelector = selector; |
+ mContext = context; |
+ mTabModelObserver = new EmptyTabModelObserver() { |
+ private boolean disableUndo() { |
+ return !DeviceClassManager.enableUndo(mContext) |
+ || DeviceClassManager.isAccessibilityModeEnabled(mContext) |
+ || DeviceClassManager.enableAccessibilityLayout(); |
+ } |
+ |
+ @Override |
+ public void tabPendingClosure(Tab tab) { |
+ if (disableUndo()) return; |
+ showUndoBar(tab.getId(), tab.getTitle()); |
+ } |
+ |
+ @Override |
+ public void tabClosureUndone(Tab tab) { |
+ if (disableUndo()) return; |
+ mSnackbarManager.removeSnackbarEntry(UndoBarPopupController.this, tab.getId()); |
+ } |
+ |
+ @Override |
+ public void tabClosureCommitted(Tab tab) { |
+ if (disableUndo()) return; |
+ mSnackbarManager.removeSnackbarEntry(UndoBarPopupController.this, tab.getId()); |
+ } |
+ |
+ @Override |
+ public void allTabsPendingClosure(List<Integer> tabIds) { |
+ if (disableUndo()) return; |
+ showUndoCloseAllBar(tabIds); |
+ } |
+ |
+ @Override |
+ public void allTabsClosureCommitted() { |
+ if (disableUndo()) return; |
+ mSnackbarManager.removeSnackbarEntry(UndoBarPopupController.this); |
+ } |
+ }; |
+ } |
+ |
+ /** |
+ * Carry out native library dependent operations like registering observers and notifications. |
+ */ |
+ public void initialize() { |
+ mTabModelSelector.getModel(false).addObserver(mTabModelObserver); |
+ } |
+ |
+ /** |
+ * Cleans up this class, unregistering for application notifications from the |
+ * {@link TabModelSelector}. |
+ */ |
+ public void destroy() { |
+ TabModel model = mTabModelSelector.getModel(false); |
+ if (model != null) model.removeObserver(mTabModelObserver); |
+ } |
+ |
+ /** |
+ * Shows an undo bar. Based on user actions, this will cause a call to either |
+ * {@link TabModel#commitTabClosure(int)} or {@link TabModel#cancelTabClosure(int)} to be called |
+ * for {@code tabId}. |
+ * |
+ * @param tabId The id of the tab. |
+ * @param content The title of the tab. |
+ */ |
+ private void showUndoBar(int tabId, String content) { |
+ RecordHistogram.recordEnumeratedHistogram("AndroidTabCloseUndo.Toast", |
+ mSnackbarManager.isShowing() ? TAB_CLOSE_UNDO_TOAST_SHOWN_WARM |
+ : TAB_CLOSE_UNDO_TOAST_SHOWN_COLD, |
+ TAB_CLOSE_UNDO_TOAST_COUNT); |
+ mSnackbarManager.showSnackbar(mContext.getString(R.string.undo_bar_close_message), |
+ content, mContext.getString(R.string.undo_bar_button_text), |
+ tabId, this); |
+ } |
+ |
+ /** |
+ * Shows an undo close all bar. Based on user actions, this will cause a call to either |
+ * {@link TabModel#commitTabClosure(int)} or {@link TabModel#cancelTabClosure(int)} to be called |
+ * for each tab in {@code closedTabIds}. This will happen unless |
+ * {@code SnackbarManager#removeFromStackForData(Object)} is called. |
+ * |
+ * @param closedTabIds A list of ids corresponding to tabs that were closed |
+ */ |
+ private void showUndoCloseAllBar(List<Integer> closedTabIds) { |
+ String content = String.format(Locale.getDefault(), "%d", closedTabIds.size()); |
+ mSnackbarManager.showSnackbar(mContext.getString(R.string.undo_bar_close_all_message), |
+ content, mContext.getString(R.string.undo_bar_button_text), |
+ closedTabIds, this); |
+ } |
+ |
+ /** |
+ * Calls {@link TabModel#cancelTabClosure(int)} for the tab or for each tab in |
+ * the list of closed tabs. |
+ */ |
+ @SuppressWarnings("unchecked") |
+ @Override |
+ public void onAction(Object actionData) { |
+ RecordHistogram.recordEnumeratedHistogram("AndroidTabCloseUndo.Toast", |
+ TAB_CLOSE_UNDO_TOAST_PRESSED, TAB_CLOSE_UNDO_TOAST_COUNT); |
+ if (actionData instanceof Integer) { |
+ cancelTabClosure((Integer) actionData); |
+ } else { |
+ for (Integer id : (List<Integer>) actionData) { |
+ cancelTabClosure(id); |
+ } |
+ } |
+ } |
+ |
+ private void cancelTabClosure(int tabId) { |
+ TabModel model = mTabModelSelector.getModelForTabId(tabId); |
+ if (model != null) model.cancelTabClosure(tabId); |
+ } |
+ |
+ /** |
+ * Calls {@link TabModel#commitTabClosure(int)} for the tab or for each tab in |
+ * the list of closed tabs. |
+ */ |
+ @SuppressWarnings("unchecked") |
+ @Override |
+ public void onDismissNoAction(Object actionData) { |
+ if (actionData instanceof Integer) { |
+ commitTabClosure((Integer) actionData); |
+ } else { |
+ for (Integer tabId : (List<Integer>) actionData) { |
+ commitTabClosure(tabId); |
+ } |
+ } |
+ } |
+ |
+ private void commitTabClosure(int tabId) { |
+ TabModel model = mTabModelSelector.getModelForTabId(tabId); |
+ if (model != null) model.commitTabClosure(tabId); |
+ } |
+ |
+ @Override |
+ public void onDismissForEachType(boolean isTimeout) { |
+ RecordHistogram.recordEnumeratedHistogram("AndroidTabCloseUndo.Toast", |
+ isTimeout ? TAB_CLOSE_UNDO_TOAST_DISMISSED_TIMEOUT |
+ : TAB_CLOSE_UNDO_TOAST_DISMISSED_ACTION, |
+ TAB_CLOSE_UNDO_TOAST_COUNT); |
+ } |
+} |