Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package org.chromium.chrome.browser.snackbar; | 5 package org.chromium.chrome.browser.snackbar; |
| 6 | 6 |
| 7 import android.annotation.TargetApi; | |
| 8 import android.app.Activity; | |
| 9 import android.content.Context; | |
| 7 import android.graphics.Rect; | 10 import android.graphics.Rect; |
| 11 import android.os.Build; | |
| 8 import android.os.Handler; | 12 import android.os.Handler; |
| 13 import android.util.AttributeSet; | |
| 9 import android.view.Gravity; | 14 import android.view.Gravity; |
| 10 import android.view.View; | 15 import android.view.View; |
| 11 import android.view.View.OnClickListener; | 16 import android.view.View.OnClickListener; |
| 12 import android.view.ViewTreeObserver.OnGlobalLayoutListener; | 17 import android.view.ViewTreeObserver.OnGlobalLayoutListener; |
| 13 import android.view.Window; | 18 import android.widget.LinearLayout; |
| 14 | 19 |
| 15 import org.chromium.base.ApiCompatibilityUtils; | 20 import org.chromium.base.ApiCompatibilityUtils; |
| 16 import org.chromium.base.VisibleForTesting; | 21 import org.chromium.base.VisibleForTesting; |
| 17 import org.chromium.chrome.R; | 22 import org.chromium.chrome.R; |
| 18 import org.chromium.chrome.browser.ChromeActivity; | 23 import org.chromium.chrome.browser.ChromeActivity; |
| 19 import org.chromium.chrome.browser.device.DeviceClassManager; | 24 import org.chromium.chrome.browser.device.DeviceClassManager; |
| 20 import org.chromium.ui.UiUtils; | 25 import org.chromium.ui.UiUtils; |
| 21 import org.chromium.ui.base.DeviceFormFactor; | 26 import org.chromium.ui.base.DeviceFormFactor; |
| 22 | 27 |
| 23 import java.util.HashSet; | 28 import java.util.HashSet; |
| 24 import java.util.Stack; | 29 import java.util.Stack; |
| 25 | 30 |
| 26 /** | 31 /** |
| 27 * Manager for the snackbar showing at the bottom of activity. | 32 * Manager for the snackbar showing at the bottom of activity. |
| 28 * <p/> | 33 * <p/> |
| 29 * There should be only one SnackbarManager and one snackbar in the activity. Th e manager maintains | 34 * There should be only one SnackbarManager and one snackbar in the activity. Th e manager maintains |
| 30 * a stack to store all entries that should be displayed. When showing a new sna ckbar, old entry | 35 * a stack to store all entries that should be displayed. When showing a new sna ckbar, old entry |
| 31 * will be pushed to stack and text/button will be updated to the newest entry. | 36 * will be pushed to stack and text/button will be updated to the newest entry. |
| 32 * <p/> | 37 * <p/> |
| 33 * When action button is clicked, this manager will call | 38 * When action button is clicked, this manager will call |
| 34 * {@link SnackbarController#onAction(Object)} in corresponding listener, and sh ow the next | 39 * {@link SnackbarController#onAction(Object)} in corresponding listener, and sh ow the next |
| 35 * entry in stack. Otherwise if no action is taken by user during | 40 * entry in stack. Otherwise if no action is taken by user during |
| 36 * {@link #DEFAULT_SNACKBAR_DURATION_MS} milliseconds, it will clear the stack a nd call | 41 * {@link #DEFAULT_SNACKBAR_DURATION_MS} milliseconds, it will clear the stack a nd call |
| 37 * {@link SnackbarController#onDismissNoAction(Object)} to all listeners. | 42 * {@link SnackbarController#onDismissNoAction(Object)} to all listeners. |
| 38 */ | 43 */ |
| 39 public class SnackbarManager implements OnClickListener, OnGlobalLayoutListener { | 44 public class SnackbarManager implements OnClickListener, OnGlobalLayoutListener { |
| 40 | 45 |
| 46 private static RuntimeException sWindowDetachTrace; | |
| 47 | |
| 48 /** | |
| 49 * A {@link LinearLayout} that logs the stack trace when {@link #onDetachedF romWindow()} is | |
| 50 * called. | |
| 51 */ | |
| 52 public static class WindowDismissalAwareLayout extends LinearLayout { | |
| 53 // TODO(ianwen): remove this class after crbug.com/553569 is fixed. | |
| 54 /** | |
| 55 * Constructor for XML inflation. | |
| 56 */ | |
| 57 public WindowDismissalAwareLayout(Context context, AttributeSet attrs) { | |
| 58 super(context, attrs); | |
| 59 } | |
| 60 | |
| 61 @Override | |
| 62 protected void onDetachedFromWindow() { | |
| 63 super.onDetachedFromWindow(); | |
| 64 sWindowDetachTrace = new RuntimeException( | |
| 65 "Stacktrace for Snackbar view to be detached from window"); | |
| 66 } | |
| 67 } | |
| 68 | |
| 41 /** | 69 /** |
| 42 * Interface that shows the ability to provide a unified snackbar manager. | 70 * Interface that shows the ability to provide a unified snackbar manager. |
| 43 */ | 71 */ |
| 44 public interface SnackbarManageable { | 72 public interface SnackbarManageable { |
| 45 /** | 73 /** |
| 46 * @return The snackbar manager that has a proper anchor view. | 74 * @return The snackbar manager that has a proper anchor view. |
| 47 */ | 75 */ |
| 48 SnackbarManager getSnackbarManager(); | 76 SnackbarManager getSnackbarManager(); |
| 49 } | 77 } |
| 50 | 78 |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 75 * Notify each SnackbarControllers instance only once immediately before the snackbar is | 103 * Notify each SnackbarControllers instance only once immediately before the snackbar is |
| 76 * dismissed. This function is likely to be used for controllers to do u ser metrics for | 104 * dismissed. This function is likely to be used for controllers to do u ser metrics for |
| 77 * dismissal. | 105 * dismissal. |
| 78 * @param isTimeout Whether this dismissal is triggered by timeout. | 106 * @param isTimeout Whether this dismissal is triggered by timeout. |
| 79 */ | 107 */ |
| 80 void onDismissForEachType(boolean isTimeout); | 108 void onDismissForEachType(boolean isTimeout); |
| 81 } | 109 } |
| 82 | 110 |
| 83 private static final int DEFAULT_SNACKBAR_DURATION_MS = 3000; | 111 private static final int DEFAULT_SNACKBAR_DURATION_MS = 3000; |
| 84 private static final int ACCESSIBILITY_MODE_SNACKBAR_DURATION_MS = 6000; | 112 private static final int ACCESSIBILITY_MODE_SNACKBAR_DURATION_MS = 6000; |
| 113 private static final String TAG = "cr_snackbar"; | |
| 85 | 114 |
| 86 // Used instead of the constant so tests can override the value. | 115 // Used instead of the constant so tests can override the value. |
| 87 private static int sSnackbarDurationMs = DEFAULT_SNACKBAR_DURATION_MS; | 116 private static int sSnackbarDurationMs = DEFAULT_SNACKBAR_DURATION_MS; |
| 88 private static int sAccessibilitySnackbarDurationMs = ACCESSIBILITY_MODE_SNA CKBAR_DURATION_MS; | 117 private static int sAccessibilitySnackbarDurationMs = ACCESSIBILITY_MODE_SNA CKBAR_DURATION_MS; |
| 89 | 118 |
| 90 private final boolean mIsTablet; | 119 private final boolean mIsTablet; |
| 91 | 120 |
| 121 private Activity mActivity; | |
| 92 private View mDecor; | 122 private View mDecor; |
| 93 private final Handler mUIThreadHandler; | 123 private final Handler mUIThreadHandler; |
| 94 private Stack<Snackbar> mStack = new Stack<Snackbar>(); | 124 private Stack<Snackbar> mStack = new Stack<Snackbar>(); |
| 95 private SnackbarPopupWindow mPopup; | 125 private SnackbarPopupWindow mPopup; |
| 96 private final Runnable mHideRunnable = new Runnable() { | 126 private final Runnable mHideRunnable = new Runnable() { |
| 97 @Override | 127 @Override |
| 98 public void run() { | 128 public void run() { |
| 99 dismissAllSnackbars(true); | 129 dismissAllSnackbars(true); |
| 100 } | 130 } |
| 101 }; | 131 }; |
| 102 | 132 |
| 103 // Variables used and reused in local calculations. | 133 // Variables used and reused in local calculations. |
| 104 private int[] mTempDecorPosition = new int[2]; | 134 private int[] mTempDecorPosition = new int[2]; |
| 105 private Rect mTempVisibleDisplayFrame = new Rect(); | 135 private Rect mTempVisibleDisplayFrame = new Rect(); |
| 106 | 136 |
| 107 /** | 137 /** |
| 108 * Constructs a SnackbarManager to show snackbars in the given window. | 138 * Constructs a SnackbarManager to show snackbars in the given window. |
| 109 */ | 139 */ |
| 110 public SnackbarManager(Window window) { | 140 public SnackbarManager(Activity activity) { |
| 111 mDecor = window.getDecorView(); | 141 mActivity = activity; |
| 142 mDecor = activity.getWindow().getDecorView(); | |
| 112 mUIThreadHandler = new Handler(); | 143 mUIThreadHandler = new Handler(); |
| 113 mIsTablet = DeviceFormFactor.isTablet(mDecor.getContext()); | 144 mIsTablet = DeviceFormFactor.isTablet(mDecor.getContext()); |
| 114 } | 145 } |
| 115 | 146 |
| 116 /** | 147 /** |
| 117 * Shows a snackbar at the bottom of the screen, or above the keyboard if th e keyboard is | 148 * Shows a snackbar at the bottom of the screen, or above the keyboard if th e keyboard is |
| 118 * visible. | 149 * visible. |
| 119 */ | 150 */ |
| 120 public void showSnackbar(Snackbar snackbar) { | 151 public void showSnackbar(Snackbar snackbar) { |
| 121 int durationMs = snackbar.getDuration(); | 152 int durationMs = snackbar.getDuration(); |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 143 * Warning: Calling this method might cause cascading destroy loop, because you might trigger | 174 * Warning: Calling this method might cause cascading destroy loop, because you might trigger |
| 144 * callbacks for other {@link SnackbarController}. This method is only meant to be used during | 175 * callbacks for other {@link SnackbarController}. This method is only meant to be used during |
| 145 * {@link ChromeActivity}'s destruction routine. For other purposes, use | 176 * {@link ChromeActivity}'s destruction routine. For other purposes, use |
| 146 * {@link #dismissSnackbars(SnackbarController)} instead. | 177 * {@link #dismissSnackbars(SnackbarController)} instead. |
| 147 * <p> | 178 * <p> |
| 148 * Dismisses all snackbars in stack. This will call | 179 * Dismisses all snackbars in stack. This will call |
| 149 * {@link SnackbarController#onDismissNoAction(Object)} for every closing sn ackbar. | 180 * {@link SnackbarController#onDismissNoAction(Object)} for every closing sn ackbar. |
| 150 * | 181 * |
| 151 * @param isTimeout Whether dismissal was triggered by timeout. | 182 * @param isTimeout Whether dismissal was triggered by timeout. |
| 152 */ | 183 */ |
| 184 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) | |
| 153 public void dismissAllSnackbars(boolean isTimeout) { | 185 public void dismissAllSnackbars(boolean isTimeout) { |
| 154 mUIThreadHandler.removeCallbacks(mHideRunnable); | 186 mUIThreadHandler.removeCallbacks(mHideRunnable); |
| 155 | 187 |
| 156 if (mPopup != null) { | 188 if (mPopup != null) { |
| 157 mPopup.dismiss(); | 189 // TODO(ianwen): remove the try catch after crbug.com/553569 is fixe d. |
| 190 try { | |
| 191 mPopup.dismiss(); | |
| 192 } catch (IllegalArgumentException ex) { | |
| 193 if (mActivity != null) { | |
| 194 android.util.Log.d(TAG, "Activity.toString()? " + mActivity) ; | |
| 195 android.util.Log.d(TAG, "Activity is finishing? " + mActivit y.isFinishing()); | |
| 196 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_ MR1) { | |
| 197 android.util.Log.d(TAG, "Activity is destroyed?" + mActi vity.isDestroyed()); | |
| 198 } | |
| 199 } | |
| 200 if (sWindowDetachTrace != null) { | |
| 201 throw sWindowDetachTrace; | |
|
newt (away)
2015/11/18 01:46:54
If you throw sWindowDetachTrace then Chrome will c
Ian Wen
2015/11/18 01:56:26
Done.
| |
| 202 } | |
| 203 throw ex; | |
| 204 } | |
| 205 | |
| 158 mPopup = null; | 206 mPopup = null; |
| 159 } | 207 } |
| 160 | 208 |
| 161 HashSet<SnackbarController> controllers = new HashSet<SnackbarController >(); | 209 HashSet<SnackbarController> controllers = new HashSet<SnackbarController >(); |
| 162 | 210 |
| 163 while (!mStack.isEmpty()) { | 211 while (!mStack.isEmpty()) { |
| 164 Snackbar snackbar = mStack.pop(); | 212 Snackbar snackbar = mStack.pop(); |
| 165 if (!controllers.contains(snackbar.getController())) { | 213 if (!controllers.contains(snackbar.getController())) { |
| 166 snackbar.getController().onDismissForEachType(isTimeout); | 214 snackbar.getController().onDismissForEachType(isTimeout); |
| 167 controllers.add(snackbar.getController()); | 215 controllers.add(snackbar.getController()); |
| (...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 296 /** | 344 /** |
| 297 * Overrides the default snackbar duration with a custom value for testing. | 345 * Overrides the default snackbar duration with a custom value for testing. |
| 298 * @param durationMs The duration to use in ms. | 346 * @param durationMs The duration to use in ms. |
| 299 */ | 347 */ |
| 300 @VisibleForTesting | 348 @VisibleForTesting |
| 301 public static void setDurationForTesting(int durationMs) { | 349 public static void setDurationForTesting(int durationMs) { |
| 302 sSnackbarDurationMs = durationMs; | 350 sSnackbarDurationMs = durationMs; |
| 303 sAccessibilitySnackbarDurationMs = durationMs; | 351 sAccessibilitySnackbarDurationMs = durationMs; |
| 304 } | 352 } |
| 305 } | 353 } |
| OLD | NEW |