1b7f9224b1495db47eb8fd813b5912250e900770aChris Banes/* 2b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * Copyright (C) 2015 The Android Open Source Project 3b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * 4b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * Licensed under the Apache License, Version 2.0 (the "License"); 5b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * you may not use this file except in compliance with the License. 6b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * You may obtain a copy of the License at 7b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * 8b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * http://www.apache.org/licenses/LICENSE-2.0 9b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * 10b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * Unless required by applicable law or agreed to in writing, software 11b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * distributed under the License is distributed on an "AS IS" BASIS, 12b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * See the License for the specific language governing permissions and 14b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * limitations under the License. 15b7f9224b1495db47eb8fd813b5912250e900770aChris Banes */ 16b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 17b7f9224b1495db47eb8fd813b5912250e900770aChris Banespackage android.support.design.widget; 18b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 19b7f9224b1495db47eb8fd813b5912250e900770aChris Banesimport android.os.Handler; 20b7f9224b1495db47eb8fd813b5912250e900770aChris Banesimport android.os.Looper; 21b7f9224b1495db47eb8fd813b5912250e900770aChris Banesimport android.os.Message; 22b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 23c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banesimport java.lang.ref.WeakReference; 24c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banes 25b7f9224b1495db47eb8fd813b5912250e900770aChris Banes/** 26b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * Manages {@link Snackbar}s. 27b7f9224b1495db47eb8fd813b5912250e900770aChris Banes */ 28b7f9224b1495db47eb8fd813b5912250e900770aChris Banesclass SnackbarManager { 29b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 30b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private static final int MSG_TIMEOUT = 0; 31b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 32b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private static final int SHORT_DURATION_MS = 1500; 33b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private static final int LONG_DURATION_MS = 2750; 34b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 35b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private static SnackbarManager sSnackbarManager; 36b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 37b7f9224b1495db47eb8fd813b5912250e900770aChris Banes static SnackbarManager getInstance() { 38b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (sSnackbarManager == null) { 39b7f9224b1495db47eb8fd813b5912250e900770aChris Banes sSnackbarManager = new SnackbarManager(); 40b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 41b7f9224b1495db47eb8fd813b5912250e900770aChris Banes return sSnackbarManager; 42b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 43b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 44b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private final Object mLock; 45b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private final Handler mHandler; 46b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 47b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private SnackbarRecord mCurrentSnackbar; 48b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private SnackbarRecord mNextSnackbar; 49b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 50b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private SnackbarManager() { 51b7f9224b1495db47eb8fd813b5912250e900770aChris Banes mLock = new Object(); 52b7f9224b1495db47eb8fd813b5912250e900770aChris Banes mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() { 53b7f9224b1495db47eb8fd813b5912250e900770aChris Banes @Override 54b7f9224b1495db47eb8fd813b5912250e900770aChris Banes public boolean handleMessage(Message message) { 55b7f9224b1495db47eb8fd813b5912250e900770aChris Banes switch (message.what) { 56b7f9224b1495db47eb8fd813b5912250e900770aChris Banes case MSG_TIMEOUT: 57b7f9224b1495db47eb8fd813b5912250e900770aChris Banes handleTimeout((SnackbarRecord) message.obj); 58b7f9224b1495db47eb8fd813b5912250e900770aChris Banes return true; 59b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 60b7f9224b1495db47eb8fd813b5912250e900770aChris Banes return false; 61b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 62b7f9224b1495db47eb8fd813b5912250e900770aChris Banes }); 63b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 64b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 65b7f9224b1495db47eb8fd813b5912250e900770aChris Banes interface Callback { 66b7f9224b1495db47eb8fd813b5912250e900770aChris Banes void show(); 67e51e533995e445f031fc8efcce6ff9a61e7066ccChris Banes void dismiss(int event); 68b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 69b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 70b7f9224b1495db47eb8fd813b5912250e900770aChris Banes public void show(int duration, Callback callback) { 71b7f9224b1495db47eb8fd813b5912250e900770aChris Banes synchronized (mLock) { 725462d3e588481416a38e893bdb0f1073f82f8dccChris Banes if (isCurrentSnackbarLocked(callback)) { 73b7f9224b1495db47eb8fd813b5912250e900770aChris Banes // Means that the callback is already in the queue. We'll just update the duration 74b7f9224b1495db47eb8fd813b5912250e900770aChris Banes mCurrentSnackbar.duration = duration; 75b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 76b7f9224b1495db47eb8fd813b5912250e900770aChris Banes // If this is the Snackbar currently being shown, call re-schedule it's 77b7f9224b1495db47eb8fd813b5912250e900770aChris Banes // timeout 78b7f9224b1495db47eb8fd813b5912250e900770aChris Banes mHandler.removeCallbacksAndMessages(mCurrentSnackbar); 79b7f9224b1495db47eb8fd813b5912250e900770aChris Banes scheduleTimeoutLocked(mCurrentSnackbar); 80b7f9224b1495db47eb8fd813b5912250e900770aChris Banes return; 815462d3e588481416a38e893bdb0f1073f82f8dccChris Banes } else if (isNextSnackbarLocked(callback)) { 82b7f9224b1495db47eb8fd813b5912250e900770aChris Banes // We'll just update the duration 83b7f9224b1495db47eb8fd813b5912250e900770aChris Banes mNextSnackbar.duration = duration; 84b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } else { 85b7f9224b1495db47eb8fd813b5912250e900770aChris Banes // Else, we need to create a new record and queue it 86b7f9224b1495db47eb8fd813b5912250e900770aChris Banes mNextSnackbar = new SnackbarRecord(duration, callback); 87b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 88b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 89e51e533995e445f031fc8efcce6ff9a61e7066ccChris Banes if (mCurrentSnackbar != null && cancelSnackbarLocked(mCurrentSnackbar, 90e51e533995e445f031fc8efcce6ff9a61e7066ccChris Banes Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE)) { 91c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banes // If we currently have a Snackbar, try and cancel it and wait in line 92c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banes return; 93b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } else { 94c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banes // Clear out the current snackbar 95c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banes mCurrentSnackbar = null; 96b7f9224b1495db47eb8fd813b5912250e900770aChris Banes // Otherwise, just show it now 97b7f9224b1495db47eb8fd813b5912250e900770aChris Banes showNextSnackbarLocked(); 98b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 99b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 100b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 101b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 102e51e533995e445f031fc8efcce6ff9a61e7066ccChris Banes public void dismiss(Callback callback, int event) { 103b7f9224b1495db47eb8fd813b5912250e900770aChris Banes synchronized (mLock) { 1045462d3e588481416a38e893bdb0f1073f82f8dccChris Banes if (isCurrentSnackbarLocked(callback)) { 105e51e533995e445f031fc8efcce6ff9a61e7066ccChris Banes cancelSnackbarLocked(mCurrentSnackbar, event); 1065462d3e588481416a38e893bdb0f1073f82f8dccChris Banes } else if (isNextSnackbarLocked(callback)) { 107e51e533995e445f031fc8efcce6ff9a61e7066ccChris Banes cancelSnackbarLocked(mNextSnackbar, event); 108b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 109b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 110b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 111b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 112b7f9224b1495db47eb8fd813b5912250e900770aChris Banes /** 113b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * Should be called when a Snackbar is no longer displayed. This is after any exit 114b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * animation has finished. 115b7f9224b1495db47eb8fd813b5912250e900770aChris Banes */ 116b7f9224b1495db47eb8fd813b5912250e900770aChris Banes public void onDismissed(Callback callback) { 117b7f9224b1495db47eb8fd813b5912250e900770aChris Banes synchronized (mLock) { 1185462d3e588481416a38e893bdb0f1073f82f8dccChris Banes if (isCurrentSnackbarLocked(callback)) { 119b7f9224b1495db47eb8fd813b5912250e900770aChris Banes // If the callback is from a Snackbar currently show, remove it and show a new one 120b7f9224b1495db47eb8fd813b5912250e900770aChris Banes mCurrentSnackbar = null; 121b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (mNextSnackbar != null) { 122b7f9224b1495db47eb8fd813b5912250e900770aChris Banes showNextSnackbarLocked(); 123b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 124b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 125b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 126b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 127b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 128b7f9224b1495db47eb8fd813b5912250e900770aChris Banes /** 129b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * Should be called when a Snackbar is being shown. This is after any entrance animation has 130b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * finished. 131b7f9224b1495db47eb8fd813b5912250e900770aChris Banes */ 132b7f9224b1495db47eb8fd813b5912250e900770aChris Banes public void onShown(Callback callback) { 133b7f9224b1495db47eb8fd813b5912250e900770aChris Banes synchronized (mLock) { 1345462d3e588481416a38e893bdb0f1073f82f8dccChris Banes if (isCurrentSnackbarLocked(callback)) { 135b7f9224b1495db47eb8fd813b5912250e900770aChris Banes scheduleTimeoutLocked(mCurrentSnackbar); 136b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 137b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 138b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 139b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 140b7f9224b1495db47eb8fd813b5912250e900770aChris Banes public void cancelTimeout(Callback callback) { 141b7f9224b1495db47eb8fd813b5912250e900770aChris Banes synchronized (mLock) { 1425462d3e588481416a38e893bdb0f1073f82f8dccChris Banes if (isCurrentSnackbarLocked(callback)) { 143b7f9224b1495db47eb8fd813b5912250e900770aChris Banes mHandler.removeCallbacksAndMessages(mCurrentSnackbar); 144b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 145b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 146b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 147b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 148b7f9224b1495db47eb8fd813b5912250e900770aChris Banes public void restoreTimeout(Callback callback) { 149b7f9224b1495db47eb8fd813b5912250e900770aChris Banes synchronized (mLock) { 1505462d3e588481416a38e893bdb0f1073f82f8dccChris Banes if (isCurrentSnackbarLocked(callback)) { 151b7f9224b1495db47eb8fd813b5912250e900770aChris Banes scheduleTimeoutLocked(mCurrentSnackbar); 152b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 153b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 154b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 155b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 1565462d3e588481416a38e893bdb0f1073f82f8dccChris Banes public boolean isCurrent(Callback callback) { 1575462d3e588481416a38e893bdb0f1073f82f8dccChris Banes synchronized (mLock) { 1585462d3e588481416a38e893bdb0f1073f82f8dccChris Banes return isCurrentSnackbarLocked(callback); 1595462d3e588481416a38e893bdb0f1073f82f8dccChris Banes } 1605462d3e588481416a38e893bdb0f1073f82f8dccChris Banes } 1615462d3e588481416a38e893bdb0f1073f82f8dccChris Banes 1625462d3e588481416a38e893bdb0f1073f82f8dccChris Banes public boolean isCurrentOrNext(Callback callback) { 1635462d3e588481416a38e893bdb0f1073f82f8dccChris Banes synchronized (mLock) { 1645462d3e588481416a38e893bdb0f1073f82f8dccChris Banes return isCurrentSnackbarLocked(callback) || isNextSnackbarLocked(callback); 1655462d3e588481416a38e893bdb0f1073f82f8dccChris Banes } 1665462d3e588481416a38e893bdb0f1073f82f8dccChris Banes } 1675462d3e588481416a38e893bdb0f1073f82f8dccChris Banes 168b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private static class SnackbarRecord { 169c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banes private final WeakReference<Callback> callback; 170b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private int duration; 171b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 172b7f9224b1495db47eb8fd813b5912250e900770aChris Banes SnackbarRecord(int duration, Callback callback) { 173c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banes this.callback = new WeakReference<>(callback); 174b7f9224b1495db47eb8fd813b5912250e900770aChris Banes this.duration = duration; 175b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 176c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banes 177c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banes boolean isSnackbar(Callback callback) { 178c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banes return callback != null && this.callback.get() == callback; 179c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banes } 180b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 181b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 182b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private void showNextSnackbarLocked() { 183b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (mNextSnackbar != null) { 184b7f9224b1495db47eb8fd813b5912250e900770aChris Banes mCurrentSnackbar = mNextSnackbar; 185b7f9224b1495db47eb8fd813b5912250e900770aChris Banes mNextSnackbar = null; 186c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banes 187c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banes final Callback callback = mCurrentSnackbar.callback.get(); 188c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banes if (callback != null) { 189c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banes callback.show(); 190c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banes } else { 191c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banes // The callback doesn't exist any more, clear out the Snackbar 192c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banes mCurrentSnackbar = null; 193c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banes } 194b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 195b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 196b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 197e51e533995e445f031fc8efcce6ff9a61e7066ccChris Banes private boolean cancelSnackbarLocked(SnackbarRecord record, int event) { 198c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banes final Callback callback = record.callback.get(); 199c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banes if (callback != null) { 200bfd48d0521963754e04e407499ee9e278fe06c0fChris Banes // Make sure we remove any timeouts for the SnackbarRecord 201bfd48d0521963754e04e407499ee9e278fe06c0fChris Banes mHandler.removeCallbacksAndMessages(record); 202e51e533995e445f031fc8efcce6ff9a61e7066ccChris Banes callback.dismiss(event); 203c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banes return true; 204c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banes } 205c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banes return false; 206b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 207b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 2085462d3e588481416a38e893bdb0f1073f82f8dccChris Banes private boolean isCurrentSnackbarLocked(Callback callback) { 209c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banes return mCurrentSnackbar != null && mCurrentSnackbar.isSnackbar(callback); 210b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 211b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 2125462d3e588481416a38e893bdb0f1073f82f8dccChris Banes private boolean isNextSnackbarLocked(Callback callback) { 213c482f89070ee5032081e394f77a9a1e63c3cd7a8Chris Banes return mNextSnackbar != null && mNextSnackbar.isSnackbar(callback); 214b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 215b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 216b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private void scheduleTimeoutLocked(SnackbarRecord r) { 2170bfb0e034ed6b4f7bbf58a111d2fc893e0553350Chris Banes if (r.duration == Snackbar.LENGTH_INDEFINITE) { 2180bfb0e034ed6b4f7bbf58a111d2fc893e0553350Chris Banes // If we're set to indefinite, we don't want to set a timeout 2190bfb0e034ed6b4f7bbf58a111d2fc893e0553350Chris Banes return; 2200bfb0e034ed6b4f7bbf58a111d2fc893e0553350Chris Banes } 2210bfb0e034ed6b4f7bbf58a111d2fc893e0553350Chris Banes 2222361e349a59da4d3791e5d2a0d842a84dba91b41Chris Banes int durationMs = LONG_DURATION_MS; 2232361e349a59da4d3791e5d2a0d842a84dba91b41Chris Banes if (r.duration > 0) { 2242361e349a59da4d3791e5d2a0d842a84dba91b41Chris Banes durationMs = r.duration; 2252361e349a59da4d3791e5d2a0d842a84dba91b41Chris Banes } else if (r.duration == Snackbar.LENGTH_SHORT) { 2262361e349a59da4d3791e5d2a0d842a84dba91b41Chris Banes durationMs = SHORT_DURATION_MS; 2272361e349a59da4d3791e5d2a0d842a84dba91b41Chris Banes } 228b7f9224b1495db47eb8fd813b5912250e900770aChris Banes mHandler.removeCallbacksAndMessages(r); 2292361e349a59da4d3791e5d2a0d842a84dba91b41Chris Banes mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_TIMEOUT, r), durationMs); 230b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 231b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 232b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private void handleTimeout(SnackbarRecord record) { 233b7f9224b1495db47eb8fd813b5912250e900770aChris Banes synchronized (mLock) { 234b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (mCurrentSnackbar == record || mNextSnackbar == record) { 235e51e533995e445f031fc8efcce6ff9a61e7066ccChris Banes cancelSnackbarLocked(record, Snackbar.Callback.DISMISS_EVENT_TIMEOUT); 236b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 237b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 238b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 239b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 240b7f9224b1495db47eb8fd813b5912250e900770aChris Banes} 241