Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2059)

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/snackbar/SnackbarManager.java

Issue 1229733002: Add new snackbar features to prepare for omnibox geolocation snackbar. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Ian's comments; fixed snackbar location in multiwindow mode when keyboard is visible Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/android/java/src/org/chromium/chrome/browser/snackbar/SnackbarManager.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/snackbar/SnackbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/snackbar/SnackbarManager.java
index d500a015dca60e6b8b119dbfef596bdc9c3e72e4..dec7e6499a0f5620b215a03713974822d29acd4c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/snackbar/SnackbarManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/snackbar/SnackbarManager.java
@@ -4,16 +4,19 @@
package org.chromium.chrome.browser.snackbar;
+import android.graphics.Rect;
import android.os.Handler;
import android.view.Gravity;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.view.Window;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.device.DeviceClassManager;
+import org.chromium.ui.UiUtils;
import org.chromium.ui.base.DeviceFormFactor;
import java.util.HashSet;
@@ -29,7 +32,7 @@ import java.util.Stack;
* When action button is clicked, this manager will call
* {@link SnackbarController#onAction(Object)} in corresponding listener, and show the next
* entry in stack. Otherwise if no action is taken by user during
- * {@link #DEFAULT_SNACKBAR_SHOW_DURATION_MS} milliseconds, it will clear the stack and call
+ * {@link #DEFAULT_SNACKBAR_DURATION_MS} milliseconds, it will clear the stack and call
* {@link SnackbarController#onDismissNoAction(Object)} to all listeners.
*/
public class SnackbarManager implements OnClickListener, OnGlobalLayoutListener {
@@ -76,20 +79,18 @@ public class SnackbarManager implements OnClickListener, OnGlobalLayoutListener
void onDismissForEachType(boolean isTimeout);
}
- private static final int DEFAULT_SNACKBAR_SHOW_DURATION_MS = 3000;
+ private static final int DEFAULT_SNACKBAR_DURATION_MS = 3000;
private static final int ACCESSIBILITY_MODE_SNACKBAR_DURATION_MS = 6000;
// Used instead of the constant so tests can override the value.
- private static int sUndoBarShowDurationMs = DEFAULT_SNACKBAR_SHOW_DURATION_MS;
- private static int sAccessibilityUndoBarDurationMs = ACCESSIBILITY_MODE_SNACKBAR_DURATION_MS;
+ private static int sSnackbarDurationMs = DEFAULT_SNACKBAR_DURATION_MS;
+ private static int sAccessibilitySnackbarDurationMs = ACCESSIBILITY_MODE_SNACKBAR_DURATION_MS;
private final boolean mIsTablet;
- private View mParent;
- // Variable storing current xy position of parent view.
- private int[] mTempTopLeft = new int[2];
+ private View mDecor;
private final Handler mUIThreadHandler;
- private Stack<SnackbarEntry> mStack = new Stack<SnackbarEntry>();
+ private Stack<Snackbar> mStack = new Stack<Snackbar>();
private SnackbarPopupWindow mPopup;
private final Runnable mHideRunnable = new Runnable() {
@Override
@@ -98,19 +99,47 @@ public class SnackbarManager implements OnClickListener, OnGlobalLayoutListener
}
};
+ // Variables used and reused in local calculations.
+ private int[] mTempDecorPosition = new int[2];
+ private Rect mTempVisibleDisplayFrame = new Rect();
+
/**
- * Create an instance of SnackbarManager with the root view of entire activity.
- * @param parent The view that snackbar anchors to. Since SnackbarManager should be initialized
- * during activity initialization, parent should always be set to root view of
- * entire activity.
+ * Constructs a SnackbarManager to show snackbars in the given window.
*/
- public SnackbarManager(View parent) {
- mParent = parent;
+ public SnackbarManager(Window window) {
+ mDecor = window.getDecorView();
mUIThreadHandler = new Handler();
- mIsTablet = DeviceFormFactor.isTablet(parent.getContext());
+ mIsTablet = DeviceFormFactor.isTablet(mDecor.getContext());
}
/**
+ * Shows a snackbar at the bottom of the screen, or above the keyboard if the keyboard is
+ * visible.
+ */
+ public void showSnackbar(Snackbar snackbar) {
+ int durationMs = snackbar.getDuration();
+ if (durationMs == 0) {
+ durationMs = DeviceClassManager.isAccessibilityModeEnabled(mDecor.getContext())
+ ? sAccessibilitySnackbarDurationMs : sSnackbarDurationMs;
+ }
+
+ mUIThreadHandler.removeCallbacks(mHideRunnable);
+ mUIThreadHandler.postDelayed(mHideRunnable, durationMs);
+
+ mStack.push(snackbar);
+ if (mPopup == null) {
+ mPopup = new SnackbarPopupWindow(mDecor, this, snackbar);
+ showPopupAtBottom();
+ mDecor.getViewTreeObserver().addOnGlobalLayoutListener(this);
+ } else {
+ mPopup.update(snackbar, true);
+ }
+
+ mPopup.announceforAccessibility();
+ }
+
+ /**
+ * TODO(newt): delete this method. Update callers to use {@link #showSnackbar(Snackbar)}.
* Shows a snackbar with description text and an action button.
* @param template Teamplate used to compose full description.
* @param description Text for description showing at start of snackbar.
@@ -121,17 +150,14 @@ public class SnackbarManager implements OnClickListener, OnGlobalLayoutListener
*/
public void showSnackbar(String template, String description, String actionText,
Object actionData, SnackbarController controller) {
- int duration = sUndoBarShowDurationMs;
- // Duration for snackbars to show is different in normal mode and in accessibility mode.
- if (DeviceClassManager.isAccessibilityModeEnabled(mParent.getContext())) {
- duration = sAccessibilityUndoBarDurationMs;
- }
- showSnackbar(template, description, actionText, actionData, controller, duration);
+ showSnackbar(Snackbar.make(description, controller).setTemplateText(template)
+ .setAction(actionText, actionData));
}
/**
+ * TODO(newt): delete this method. Update callers to use {@link #showSnackbar(Snackbar)}.
* Shows a snackbar for the given timeout duration with description text and an action button.
- * Allows overriding the default timeout of {@link #DEFAULT_SNACKBAR_SHOW_DURATION_MS} with
+ * Allows overriding the default timeout of {@link #DEFAULT_SNACKBAR_DURATION_MS} with
* a custom value.
* @param template Teamplate used to compose full description.
* @param description Text for description showing at start of snackbar.
@@ -143,40 +169,8 @@ public class SnackbarManager implements OnClickListener, OnGlobalLayoutListener
*/
public void showSnackbar(String template, String description, String actionText,
Object actionData, SnackbarController controller, int timeoutMs) {
- mUIThreadHandler.removeCallbacks(mHideRunnable);
- mUIThreadHandler.postDelayed(mHideRunnable, timeoutMs);
-
- mStack.push(new SnackbarEntry(template, description, actionText, actionData, controller));
- if (mPopup == null) {
- mPopup = new SnackbarPopupWindow(mParent, this, template, description, actionText);
- showPopupAtBottom();
- mParent.getViewTreeObserver().addOnGlobalLayoutListener(this);
- } else {
- mPopup.setTextViews(template, description, actionText, true);
- }
-
- mPopup.announceforAccessibility();
- }
-
- /**
- * Convinient function for showSnackbar. Note this method adds passed entry to stack.
- */
- private void showSnackbar(SnackbarEntry entry) {
- showSnackbar(entry.mTemplate, entry.mDescription, entry.mActionText, entry.mData,
- entry.mController);
- }
-
- /**
- * Change parent view of snackbar. This method is likely to be called when a new window is
- * hiding the snackbar and will dismiss all snackbars.
- * @param newParent The new parent view snackbar anchors to.
- */
- public void setParentView(View newParent) {
- if (newParent == mParent) return;
- mParent.getViewTreeObserver().removeOnGlobalLayoutListener(this);
- mUIThreadHandler.removeCallbacks(mHideRunnable);
- dismissSnackbar(false);
- mParent = newParent;
+ showSnackbar(Snackbar.make(description, controller).setTemplateText(template)
+ .setAction(actionText, actionData).setDuration(timeoutMs));
}
/**
@@ -195,14 +189,14 @@ public class SnackbarManager implements OnClickListener, OnGlobalLayoutListener
HashSet<SnackbarController> controllers = new HashSet<SnackbarController>();
while (!mStack.isEmpty()) {
- SnackbarEntry entry = mStack.pop();
- if (!controllers.contains(entry.mController)) {
- entry.mController.onDismissForEachType(isTimeout);
- controllers.add(entry.mController);
+ Snackbar snackbar = mStack.pop();
+ if (!controllers.contains(snackbar.getController())) {
+ snackbar.getController().onDismissForEachType(isTimeout);
+ controllers.add(snackbar.getController());
}
- entry.mController.onDismissNoAction(entry.mData);
+ snackbar.getController().onDismissNoAction(snackbar.getActionData());
}
- mParent.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ mDecor.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
/**
@@ -212,11 +206,11 @@ public class SnackbarManager implements OnClickListener, OnGlobalLayoutListener
*/
public void removeSnackbarEntry(SnackbarController controller) {
boolean isFound = false;
- SnackbarEntry[] snackbarEntries = new SnackbarEntry[mStack.size()];
- mStack.toArray(snackbarEntries);
- for (SnackbarEntry entry : snackbarEntries) {
- if (entry.mController == controller) {
- mStack.remove(entry);
+ Snackbar[] snackbars = new Snackbar[mStack.size()];
+ mStack.toArray(snackbars);
+ for (Snackbar snackbar : snackbars) {
+ if (snackbar.getController() == controller) {
+ mStack.remove(snackbar);
isFound = true;
}
}
@@ -235,10 +229,10 @@ public class SnackbarManager implements OnClickListener, OnGlobalLayoutListener
*/
public void removeSnackbarEntry(SnackbarController controller, Object data) {
boolean isFound = false;
- for (SnackbarEntry entry : mStack) {
- if (entry.mData != null && entry.mData.equals(data)
- && entry.mController == controller) {
- mStack.remove(entry);
+ for (Snackbar snackbar : mStack) {
+ if (snackbar.getActionData() != null && snackbar.getActionData().equals(data)
+ && snackbar.getController() == controller) {
+ mStack.remove(snackbar);
isFound = true;
break;
}
@@ -266,8 +260,8 @@ public class SnackbarManager implements OnClickListener, OnGlobalLayoutListener
public void onClick(View v) {
assert !mStack.isEmpty();
- SnackbarEntry entry = mStack.pop();
- entry.mController.onAction(entry.mData);
+ Snackbar snackbar = mStack.pop();
+ snackbar.getController().onAction(snackbar.getActionData());
if (!mStack.isEmpty()) {
showSnackbar(mStack.pop());
@@ -276,40 +270,48 @@ public class SnackbarManager implements OnClickListener, OnGlobalLayoutListener
}
}
- /**
- * Calculates the show-up position from TOP START corner of parent view as a workaround of an
- * android bug http://b/17789629 on Lollipop.
- */
private void showPopupAtBottom() {
- int margin = mIsTablet ? mParent.getResources().getDimensionPixelSize(
- R.dimen.undo_bar_tablet_margin) : 0;
- mParent.getLocationInWindow(mTempTopLeft);
- mPopup.showAtLocation(mParent, Gravity.START | Gravity.TOP, margin,
- mTempTopLeft[1] + mParent.getHeight() - mPopup.getHeight() - margin);
+ // When the keyboard is showing, translating the snackbar upwards looks bad because it
+ // overlaps the keyboard. In this case, use an alternative animation without translation.
+ boolean isKeyboardShowing = UiUtils.isKeyboardShowing(mDecor.getContext(), mDecor);
+ mPopup.setAnimationStyle(isKeyboardShowing ? R.style.SnackbarAnimationWithKeyboard
+ : R.style.SnackbarAnimation);
+
+ mDecor.getLocationInWindow(mTempDecorPosition);
+ mDecor.getWindowVisibleDisplayFrame(mTempVisibleDisplayFrame);
+ int decorBottom = mTempDecorPosition[1] + mDecor.getHeight();
+ int visibleBottom = Math.min(mTempVisibleDisplayFrame.bottom, decorBottom);
+ int margin = mIsTablet ? mDecor.getResources().getDimensionPixelSize(
+ R.dimen.snackbar_tablet_margin) : 0;
+
+ mPopup.showAtLocation(mDecor, Gravity.START | Gravity.BOTTOM, margin,
+ decorBottom - visibleBottom + margin);
}
/**
- * Resize and re-align popup window when device orientation changes, or soft keyboard shows up.
+ * Resize and re-position popup window when the device orientation changes or the software
+ * keyboard appears. Be careful not to let the snackbar overlap the Android navigation bar:
+ * http://b/17789629.
*/
@Override
public void onGlobalLayout() {
if (mPopup == null) return;
- mParent.getLocationInWindow(mTempTopLeft);
+
+ mDecor.getLocationInWindow(mTempDecorPosition);
+ mDecor.getWindowVisibleDisplayFrame(mTempVisibleDisplayFrame);
+ int decorBottom = mTempDecorPosition[1] + mDecor.getHeight();
+ int visibleBottom = Math.min(mTempVisibleDisplayFrame.bottom, decorBottom);
+
if (mIsTablet) {
- int margin = mParent.getResources().getDimensionPixelSize(
- R.dimen.undo_bar_tablet_margin);
- int width = mParent.getResources().getDimensionPixelSize(
- R.dimen.undo_bar_tablet_width);
- boolean isRtl = ApiCompatibilityUtils.isLayoutRtl(mParent);
- int startPosition = isRtl ? mParent.getRight() - width - margin
- : mParent.getLeft() + margin;
- mPopup.update(startPosition,
- mTempTopLeft[1] + mParent.getHeight() - mPopup.getHeight() - margin, width, -1);
+ int margin = mDecor.getResources().getDimensionPixelOffset(
+ R.dimen.snackbar_tablet_margin);
+ int width = mDecor.getResources().getDimensionPixelSize(R.dimen.snackbar_tablet_width);
+ boolean isRtl = ApiCompatibilityUtils.isLayoutRtl(mDecor);
+ int startPosition = isRtl ? mDecor.getRight() - width - margin
+ : mDecor.getLeft() + margin;
+ mPopup.update(startPosition, decorBottom - visibleBottom + margin, width, -1);
} else {
- // Phone relayout
- mPopup.update(mParent.getLeft(),
- mTempTopLeft[1] + mParent.getHeight() - mPopup.getHeight(), mParent.getWidth(),
- -1);
+ mPopup.update(mDecor.getLeft(), decorBottom - visibleBottom, mDecor.getWidth(), -1);
}
}
@@ -322,33 +324,12 @@ public class SnackbarManager implements OnClickListener, OnGlobalLayoutListener
}
/**
- * Allows overriding the default timeout of {@link #DEFAULT_SNACKBAR_SHOW_DURATION_MS} with
- * a custom value. This is meant to be used by tests.
- * @param timeoutMs The new timeout to use in ms.
+ * Overrides the default snackbar duration with a custom value for testing.
+ * @param durationMs The duration to use in ms.
*/
@VisibleForTesting
- public static void setTimeoutForTesting(int timeoutMs) {
- sUndoBarShowDurationMs = timeoutMs;
- sAccessibilityUndoBarDurationMs = timeoutMs;
- }
-
- /**
- * Simple data structure representing a single snackbar in stack.
- */
- private static class SnackbarEntry {
- public String mTemplate;
- public String mDescription;
- public String mActionText;
- public Object mData;
- public SnackbarController mController;
-
- public SnackbarEntry(String template, String description, String actionText,
- Object actionData, SnackbarController controller) {
- mTemplate = template;
- mDescription = description;
- mActionText = actionText;
- mData = actionData;
- mController = controller;
- }
+ public static void setDurationForTesting(int durationMs) {
+ sSnackbarDurationMs = durationMs;
+ sAccessibilitySnackbarDurationMs = durationMs;
}
}

Powered by Google App Engine
This is Rietveld 408576698