SnackbarManager.java revision b7f9224b1495db47eb8fd813b5912250e900770a
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
23b7f9224b1495db47eb8fd813b5912250e900770aChris Banes/**
24b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * Manages {@link Snackbar}s.
25b7f9224b1495db47eb8fd813b5912250e900770aChris Banes */
26b7f9224b1495db47eb8fd813b5912250e900770aChris Banesclass SnackbarManager {
27b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
28b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    private static final int MSG_TIMEOUT = 0;
29b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
30b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    private static final int SHORT_DURATION_MS = 1500;
31b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    private static final int LONG_DURATION_MS = 2750;
32b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
33b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    private static SnackbarManager sSnackbarManager;
34b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
35b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    static SnackbarManager getInstance() {
36b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        if (sSnackbarManager == null) {
37b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            sSnackbarManager = new SnackbarManager();
38b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        }
39b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        return sSnackbarManager;
40b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    }
41b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
42b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    private final Object mLock;
43b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    private final Handler mHandler;
44b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
45b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    private SnackbarRecord mCurrentSnackbar;
46b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    private SnackbarRecord mNextSnackbar;
47b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
48b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    private SnackbarManager() {
49b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        mLock = new Object();
50b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
51b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            @Override
52b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            public boolean handleMessage(Message message) {
53b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                switch (message.what) {
54b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                    case MSG_TIMEOUT:
55b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                        handleTimeout((SnackbarRecord) message.obj);
56b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                        return true;
57b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                }
58b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                return false;
59b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            }
60b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        });
61b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    }
62b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
63b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    interface Callback {
64b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        void show();
65b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        void dismiss();
66b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    }
67b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
68b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    public void show(int duration, Callback callback) {
69b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        synchronized (mLock) {
70b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            if (isCurrentSnackbar(callback)) {
71b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                // Means that the callback is already in the queue. We'll just update the duration
72b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                mCurrentSnackbar.duration = duration;
73b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
74b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                // If this is the Snackbar currently being shown, call re-schedule it's
75b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                // timeout
76b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                mHandler.removeCallbacksAndMessages(mCurrentSnackbar);
77b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                scheduleTimeoutLocked(mCurrentSnackbar);
78b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                return;
79b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            } else if (isNextSnackbar(callback)) {
80b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                // We'll just update the duration
81b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                mNextSnackbar.duration = duration;
82b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            } else {
83b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                // Else, we need to create a new record and queue it
84b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                mNextSnackbar = new SnackbarRecord(duration, callback);
85b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            }
86b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
87b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            if (mCurrentSnackbar != null) {
88b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                // If the new Snackbar isn't in position 0, we'll cancel the current one and wait
89b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                // in line
90b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                cancelSnackbarLocked(mCurrentSnackbar);
91b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            } else {
92b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                // Otherwise, just show it now
93b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                showNextSnackbarLocked();
94b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            }
95b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        }
96b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    }
97b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
98b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    public void dismiss(Callback callback) {
99b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        synchronized (mLock) {
100b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            if (isCurrentSnackbar(callback)) {
101b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                cancelSnackbarLocked(mCurrentSnackbar);
102b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            }
103b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            if (isNextSnackbar(callback)) {
104b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                cancelSnackbarLocked(mNextSnackbar);
105b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            }
106b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        }
107b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    }
108b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
109b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    /**
110b7f9224b1495db47eb8fd813b5912250e900770aChris Banes     * Should be called when a Snackbar is no longer displayed. This is after any exit
111b7f9224b1495db47eb8fd813b5912250e900770aChris Banes     * animation has finished.
112b7f9224b1495db47eb8fd813b5912250e900770aChris Banes     */
113b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    public void onDismissed(Callback callback) {
114b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        synchronized (mLock) {
115b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            if (isCurrentSnackbar(callback)) {
116b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                // If the callback is from a Snackbar currently show, remove it and show a new one
117b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                mCurrentSnackbar = null;
118b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                if (mNextSnackbar != null) {
119b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                    showNextSnackbarLocked();
120b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                }
121b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            }
122b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        }
123b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    }
124b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
125b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    /**
126b7f9224b1495db47eb8fd813b5912250e900770aChris Banes     * Should be called when a Snackbar is being shown. This is after any entrance animation has
127b7f9224b1495db47eb8fd813b5912250e900770aChris Banes     * finished.
128b7f9224b1495db47eb8fd813b5912250e900770aChris Banes     */
129b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    public void onShown(Callback callback) {
130b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        synchronized (mLock) {
131b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            if (isCurrentSnackbar(callback)) {
132b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                scheduleTimeoutLocked(mCurrentSnackbar);
133b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            }
134b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        }
135b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    }
136b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
137b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    public void cancelTimeout(Callback callback) {
138b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        synchronized (mLock) {
139b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            if (isCurrentSnackbar(callback)) {
140b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                mHandler.removeCallbacksAndMessages(mCurrentSnackbar);
141b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            }
142b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        }
143b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    }
144b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
145b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    public void restoreTimeout(Callback callback) {
146b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        synchronized (mLock) {
147b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            if (isCurrentSnackbar(callback)) {
148b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                scheduleTimeoutLocked(mCurrentSnackbar);
149b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            }
150b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        }
151b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    }
152b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
153b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    private static class SnackbarRecord {
154b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        private Callback callback;
155b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        private int duration;
156b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
157b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        SnackbarRecord(int duration, Callback callback) {
158b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            this.callback = callback;
159b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            this.duration = duration;
160b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        }
161b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    }
162b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
163b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    private void showNextSnackbarLocked() {
164b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        if (mNextSnackbar != null) {
165b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            mCurrentSnackbar = mNextSnackbar;
166b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            mCurrentSnackbar.callback.show();
167b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            mNextSnackbar = null;
168b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        }
169b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    }
170b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
171b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    private void cancelSnackbarLocked(SnackbarRecord record) {
172b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        record.callback.dismiss();
173b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    }
174b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
175b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    private boolean isCurrentSnackbar(Callback callback) {
176b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        return mCurrentSnackbar != null && mCurrentSnackbar.callback == callback;
177b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    }
178b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
179b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    private boolean isNextSnackbar(Callback callback) {
180b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        return mNextSnackbar != null && mNextSnackbar.callback == callback;
181b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    }
182b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
183b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    private void scheduleTimeoutLocked(SnackbarRecord r) {
184b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        mHandler.removeCallbacksAndMessages(r);
185b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_TIMEOUT, r),
186b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                r.duration == Snackbar.LENGTH_LONG
187b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                        ? LONG_DURATION_MS
188b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                        : SHORT_DURATION_MS);
189b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    }
190b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
191b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    private void handleTimeout(SnackbarRecord record) {
192b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        synchronized (mLock) {
193b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            if (mCurrentSnackbar == record || mNextSnackbar == record) {
194b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                cancelSnackbarLocked(record);
195b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            }
196b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        }
197b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    }
198b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
199b7f9224b1495db47eb8fd813b5912250e900770aChris Banes}
200