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

Side by Side Diff: chrome/android/java_staging/src/org/chromium/chrome/browser/snackbar/SnackbarManager.java

Issue 1141283003: Upstream oodles of Chrome for Android code into Chromium. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: final patch? Created 5 years, 7 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 unified diff | Download patch
OLDNEW
(Empty)
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
3 // found in the LICENSE file.
4
5 package org.chromium.chrome.browser.snackbar;
6
7 import android.os.Handler;
8 import android.view.Gravity;
9 import android.view.View;
10 import android.view.View.OnClickListener;
11 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
12
13 import com.google.android.apps.chrome.R;
14
15 import org.chromium.base.ApiCompatibilityUtils;
16 import org.chromium.base.VisibleForTesting;
17 import org.chromium.chrome.browser.device.DeviceClassManager;
18 import org.chromium.ui.base.DeviceFormFactor;
19
20 import java.util.HashSet;
21 import java.util.Stack;
22
23 /**
24 * Manager for the snackbar showing at the bottom of activity.
25 * <p/>
26 * There should be only one SnackbarManager and one snackbar in the activity. Th e manager maintains
27 * a stack to store all entries that should be displayed. When showing a new sna ckbar, old entry
28 * will be pushed to stack and text/button will be updated to the newest entry.
29 * <p/>
30 * When action button is clicked, this manager will call
31 * {@link SnackbarController#onAction(Object)} in corresponding listener, and sh ow the next
32 * entry in stack. Otherwise if no action is taken by user during
33 * {@link #DEFAULT_SNACKBAR_SHOW_DURATION_MS} milliseconds, it will clear the st ack and call
34 * {@link SnackbarController#onDismissNoAction(Object)} to all listeners.
35 */
36 public class SnackbarManager implements OnClickListener, OnGlobalLayoutListener {
37
38 /**
39 * Interface that shows the ability to provide a unified snackbar manager.
40 */
41 public interface SnackbarManageable {
42 /**
43 * @return The snackbar manager that has a proper anchor view.
44 */
45 SnackbarManager getSnackbarManager();
46 }
47
48 /**
49 * Controller that post entries to snackbar manager and interact with snackb ar manager during
50 * dismissal and action click event.
51 */
52 public static interface SnackbarController {
53 /**
54 * Callback triggered when user clicks on button at end of snackbar. Thi s method is only
55 * called for controller having posted the entry the user clicked on; ot her controllers are
56 * not notified. Also once this {@link #onAction(Object)} is called,
57 * {@link #onDismissNoAction(Object)} and {@link #onDismissForEachType(b oolean)} will not be
58 * called.
59 * @param actionData Data object passed when showing this specific snack bar.
60 */
61 void onAction(Object actionData);
62
63 /**
64 * Callback triggered when the snackbar is dismissed by either timeout o r UI environment
65 * change. This callback will be called for each entry a controller has posted, _except_ for
66 * entries which the user has done action with, by clicking the action b utton.
67 * @param actionData Data object associated with the dismissed snackbar entry.
68 */
69 void onDismissNoAction(Object actionData);
70
71 /**
72 * Notify each SnackbarControllers instance only once immediately before the snackbar is
73 * dismissed. This function is likely to be used for controllers to do u ser metrics for
74 * dismissal.
75 * @param isTimeout Whether this dismissal is triggered by timeout.
76 */
77 void onDismissForEachType(boolean isTimeout);
78 }
79
80 private static final int DEFAULT_SNACKBAR_SHOW_DURATION_MS = 3000;
81 private static final int ACCESSIBILITY_MODE_SNACKBAR_DURATION_MS = 6000;
82
83 // Used instead of the constant so tests can override the value.
84 private static int sUndoBarShowDurationMs = DEFAULT_SNACKBAR_SHOW_DURATION_M S;
85 private static int sAccessibilityUndoBarDurationMs = ACCESSIBILITY_MODE_SNAC KBAR_DURATION_MS;
86
87 private final boolean mIsTablet;
88
89 private View mParent;
90 // Variable storing current xy position of parent view.
91 private int[] mTempTopLeft = new int[2];
92 private final Handler mUIThreadHandler;
93 private Stack<SnackbarEntry> mStack = new Stack<SnackbarEntry>();
94 private SnackbarPopupWindow mPopup;
95 private final Runnable mHideRunnable = new Runnable() {
96 @Override
97 public void run() {
98 dismissSnackbar(true);
99 }
100 };
101
102 /**
103 * Create an instance of SnackbarManager with the root view of entire activi ty.
104 * @param parent The view that snackbar anchors to. Since SnackbarManager sh ould be initialized
105 * during activity initialization, parent should always be set to root view of
106 * entire activity.
107 */
108 public SnackbarManager(View parent) {
109 mParent = parent;
110 mUIThreadHandler = new Handler();
111 mIsTablet = DeviceFormFactor.isTablet(parent.getContext());
112 }
113
114 /**
115 * Shows a snackbar with description text and an action button.
116 * @param template Teamplate used to compose full description.
117 * @param description Text for description showing at start of snackbar.
118 * @param actionText Text for action button to show.
119 * @param actionData Data bound to this snackbar entry. Will be returned to listeners when
120 * action be clicked or snackbar be dismissed.
121 * @param controller Listener for this snackbar entry.
122 */
123 public void showSnackbar(String template, String description, String actionT ext,
124 Object actionData, SnackbarController controller) {
125 mUIThreadHandler.removeCallbacks(mHideRunnable);
126 int duration = sUndoBarShowDurationMs;
127 // Duration for snackbars to show is different in normal mode and in acc essibility mode.
128 if (DeviceClassManager.isAccessibilityModeEnabled(mParent.getContext())) {
129 duration = sAccessibilityUndoBarDurationMs;
130 }
131 mUIThreadHandler.postDelayed(mHideRunnable, duration);
132
133 mStack.push(new SnackbarEntry(template, description, actionText, actionD ata, controller));
134 if (mPopup == null) {
135 mPopup = new SnackbarPopupWindow(mParent, this, template, descriptio n, actionText);
136 showPopupAtBottom();
137 mParent.getViewTreeObserver().addOnGlobalLayoutListener(this);
138 } else {
139 mPopup.setTextViews(template, description, actionText, true);
140 }
141
142 mPopup.announceforAccessibility();
143 }
144
145 /**
146 * Convinient function for showSnackbar. Note this method adds passed entry to stack.
147 */
148 private void showSnackbar(SnackbarEntry entry) {
149 showSnackbar(entry.mTemplate, entry.mDescription, entry.mActionText, ent ry.mData,
150 entry.mController);
151 }
152
153 /**
154 * Change parent view of snackbar. This method is likely to be called when a new window is
155 * hiding the snackbar and will dismiss all snackbars.
156 * @param newParent The new parent view snackbar anchors to.
157 */
158 public void setParentView(View newParent) {
159 if (newParent == mParent) return;
160 mParent.getViewTreeObserver().removeOnGlobalLayoutListener(this);
161 mUIThreadHandler.removeCallbacks(mHideRunnable);
162 dismissSnackbar(false);
163 mParent = newParent;
164 }
165
166 /**
167 * Dismisses snackbar, clears out all entries in stack and prevents future r emove callbacks from
168 * happening. This method also unregisters this class from global layout not ifications.
169 * @param isTimeout Whether dismissal was triggered by timeout.
170 */
171 public void dismissSnackbar(boolean isTimeout) {
172 mUIThreadHandler.removeCallbacks(mHideRunnable);
173
174 if (mPopup != null) {
175 mPopup.dismiss();
176 mPopup = null;
177 }
178
179 HashSet<SnackbarController> controllers = new HashSet<SnackbarController >();
180
181 while (!mStack.isEmpty()) {
182 SnackbarEntry entry = mStack.pop();
183 if (!controllers.contains(entry.mController)) {
184 entry.mController.onDismissForEachType(isTimeout);
185 controllers.add(entry.mController);
186 }
187 entry.mController.onDismissNoAction(entry.mData);
188 }
189 mParent.getViewTreeObserver().removeOnGlobalLayoutListener(this);
190 }
191
192 /**
193 * Removes all entries for certain type of controller. This method is used w hen a controller
194 * wants to remove all entries it posted to snackbar manager before.
195 * @param controller This method only removes entries posted by this control ler.
196 */
197 public void removeSnackbarEntry(SnackbarController controller) {
198 boolean isFound = false;
199 SnackbarEntry[] snackbarEntries = new SnackbarEntry[mStack.size()];
200 mStack.toArray(snackbarEntries);
201 for (SnackbarEntry entry : snackbarEntries) {
202 if (entry.mController == controller) {
203 mStack.remove(entry);
204 isFound = true;
205 }
206 }
207 if (!isFound) return;
208
209 finishSnackbarEntryRemoval(controller);
210 }
211
212 /**
213 * Removes all entries for certain type of controller and with specified dat a. This method is
214 * used when a controller wants to remove some entries it posted to snackbar manager before.
215 * However it does not affect other controllers' entries. Note that this met hod assumes
216 * different types of snackbar controllers are not sharing the same instance .
217 * @param controller This method only removes entries posted by this control ler.
218 * @param data Identifier of an entry to be removed from stack.
219 */
220 public void removeSnackbarEntry(SnackbarController controller, Object data) {
221 boolean isFound = false;
222 for (SnackbarEntry entry : mStack) {
223 if (entry.mData != null && entry.mData.equals(data)
224 && entry.mController == controller) {
225 mStack.remove(entry);
226 isFound = true;
227 break;
228 }
229 }
230 if (!isFound) return;
231
232 finishSnackbarEntryRemoval(controller);
233 }
234
235 private void finishSnackbarEntryRemoval(SnackbarController controller) {
236 controller.onDismissForEachType(false);
237
238 if (mStack.isEmpty()) {
239 dismissSnackbar(false);
240 } else {
241 // Refresh the snackbar to let it show top of stack and have full ti meout.
242 showSnackbar(mStack.pop());
243 }
244 }
245
246 /**
247 * Handles click event for action button at end of snackbar.
248 */
249 @Override
250 public void onClick(View v) {
251 assert !mStack.isEmpty();
252
253 SnackbarEntry entry = mStack.pop();
254 entry.mController.onAction(entry.mData);
255
256 if (!mStack.isEmpty()) {
257 showSnackbar(mStack.pop());
258 } else {
259 dismissSnackbar(false);
260 }
261 }
262
263 /**
264 * Calculates the show-up position from TOP START corner of parent view as a workaround of an
265 * android bug http://b/17789629 on Lollipop.
266 */
267 private void showPopupAtBottom() {
268 int margin = mIsTablet ? mParent.getResources().getDimensionPixelSize(
269 R.dimen.undo_bar_tablet_margin) : 0;
270 mParent.getLocationInWindow(mTempTopLeft);
271 mPopup.showAtLocation(mParent, Gravity.START | Gravity.TOP, margin,
272 mTempTopLeft[1] + mParent.getHeight() - mPopup.getHeight() - mar gin);
273 }
274
275 /**
276 * Resize and re-align popup window when device orientation changes, or soft keyboard shows up.
277 */
278 @Override
279 public void onGlobalLayout() {
280 if (mPopup == null) return;
281 mParent.getLocationInWindow(mTempTopLeft);
282 if (mIsTablet) {
283 int margin = mParent.getResources().getDimensionPixelSize(
284 R.dimen.undo_bar_tablet_margin);
285 int width = mParent.getResources().getDimensionPixelSize(
286 R.dimen.undo_bar_tablet_width);
287 boolean isRtl = ApiCompatibilityUtils.isLayoutRtl(mParent);
288 int startPosition = isRtl ? mParent.getRight() - width - margin
289 : mParent.getLeft() + margin;
290 mPopup.update(startPosition,
291 mTempTopLeft[1] + mParent.getHeight() - mPopup.getHeight() - margin, width, -1);
292 } else {
293 // Phone relayout
294 mPopup.update(mParent.getLeft(),
295 mTempTopLeft[1] + mParent.getHeight() - mPopup.getHeight(), mParent.getWidth(),
296 -1);
297 }
298 }
299
300 /**
301 * @return Whether there is a snackbar on screen.
302 */
303 public boolean isShowing() {
304 if (mPopup == null) return false;
305 return mPopup.isShowing();
306 }
307
308 /**
309 * Allows overriding the default timeout of {@link #DEFAULT_SNACKBAR_SHOW_DU RATION_MS} with
310 * a custom value. This is meant to be used by tests.
311 * @param timeoutMs The new timeout to use in ms.
312 */
313 @VisibleForTesting
314 public static void setTimeoutForTesting(int timeoutMs) {
315 sUndoBarShowDurationMs = timeoutMs;
316 sAccessibilityUndoBarDurationMs = timeoutMs;
317 }
318
319 /**
320 * Simple data structure representing a single snackbar in stack.
321 */
322 private static class SnackbarEntry {
323 public String mTemplate;
324 public String mDescription;
325 public String mActionText;
326 public Object mData;
327 public SnackbarController mController;
328
329 public SnackbarEntry(String template, String description, String actionT ext,
330 Object actionData, SnackbarController controller) {
331 mTemplate = template;
332 mDescription = description;
333 mActionText = actionText;
334 mData = actionData;
335 mController = controller;
336 }
337 }
338 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698