|
1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- |
|
2 * This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 package org.mozilla.gecko; |
|
7 |
|
8 import android.app.Notification; |
|
9 import android.app.PendingIntent; |
|
10 import android.text.TextUtils; |
|
11 import android.util.Log; |
|
12 |
|
13 import java.util.LinkedList; |
|
14 import java.util.concurrent.ConcurrentHashMap; |
|
15 |
|
16 /** |
|
17 * Client for posting notifications through a NotificationHandler. |
|
18 */ |
|
19 public abstract class NotificationClient { |
|
20 private static final String LOGTAG = "GeckoNotificationClient"; |
|
21 |
|
22 private volatile NotificationHandler mHandler; |
|
23 private boolean mReady; |
|
24 private final LinkedList<Runnable> mTaskQueue = new LinkedList<Runnable>(); |
|
25 private final ConcurrentHashMap<Integer, UpdateRunnable> mUpdatesMap = |
|
26 new ConcurrentHashMap<Integer, UpdateRunnable>(); |
|
27 |
|
28 /** |
|
29 * Runnable that is reused between update notifications. |
|
30 * |
|
31 * Updates happen frequently, so reusing Runnables prevents frequent dynamic allocation. |
|
32 */ |
|
33 private class UpdateRunnable implements Runnable { |
|
34 private long mProgress; |
|
35 private long mProgressMax; |
|
36 private String mAlertText; |
|
37 final private int mNotificationID; |
|
38 |
|
39 public UpdateRunnable(int notificationID) { |
|
40 mNotificationID = notificationID; |
|
41 } |
|
42 |
|
43 public synchronized boolean updateProgress(long progress, long progressMax, String alertText) { |
|
44 if (progress == mProgress |
|
45 && mProgressMax == progressMax |
|
46 && TextUtils.equals(mAlertText, alertText)) { |
|
47 return false; |
|
48 } |
|
49 |
|
50 mProgress = progress; |
|
51 mProgressMax = progressMax; |
|
52 mAlertText = alertText; |
|
53 return true; |
|
54 } |
|
55 |
|
56 @Override |
|
57 public void run() { |
|
58 long progress; |
|
59 long progressMax; |
|
60 String alertText; |
|
61 |
|
62 synchronized (this) { |
|
63 progress = mProgress; |
|
64 progressMax = mProgressMax; |
|
65 alertText = mAlertText; |
|
66 } |
|
67 |
|
68 mHandler.update(mNotificationID, progress, progressMax, alertText); |
|
69 } |
|
70 }; |
|
71 |
|
72 /** |
|
73 * Adds a notification. |
|
74 * |
|
75 * @see NotificationHandler#add(int, String, String, String, PendingIntent, PendingIntent) |
|
76 */ |
|
77 public synchronized void add(final int notificationID, final String aImageUrl, |
|
78 final String aAlertTitle, final String aAlertText, final PendingIntent contentIntent) { |
|
79 mTaskQueue.add(new Runnable() { |
|
80 @Override |
|
81 public void run() { |
|
82 mHandler.add(notificationID, aImageUrl, aAlertTitle, aAlertText, contentIntent); |
|
83 } |
|
84 }); |
|
85 notify(); |
|
86 |
|
87 if (!mReady) { |
|
88 bind(); |
|
89 } |
|
90 } |
|
91 |
|
92 /** |
|
93 * Adds a notification. |
|
94 * |
|
95 * @see NotificationHandler#add(int, Notification) |
|
96 */ |
|
97 public synchronized void add(final int notificationID, final Notification notification) { |
|
98 mTaskQueue.add(new Runnable() { |
|
99 @Override |
|
100 public void run() { |
|
101 mHandler.add(notificationID, notification); |
|
102 } |
|
103 }); |
|
104 notify(); |
|
105 |
|
106 if (!mReady) { |
|
107 bind(); |
|
108 } |
|
109 } |
|
110 |
|
111 /** |
|
112 * Updates a notification. |
|
113 * |
|
114 * @see NotificationHandler#update(int, long, long, String) |
|
115 */ |
|
116 public void update(final int notificationID, final long aProgress, final long aProgressMax, |
|
117 final String aAlertText) { |
|
118 UpdateRunnable runnable = mUpdatesMap.get(notificationID); |
|
119 |
|
120 if (runnable == null) { |
|
121 runnable = new UpdateRunnable(notificationID); |
|
122 mUpdatesMap.put(notificationID, runnable); |
|
123 } |
|
124 |
|
125 // If we've already posted an update with these values, there's no |
|
126 // need to do it again. |
|
127 if (!runnable.updateProgress(aProgress, aProgressMax, aAlertText)) { |
|
128 return; |
|
129 } |
|
130 |
|
131 synchronized (this) { |
|
132 if (mReady) { |
|
133 mTaskQueue.add(runnable); |
|
134 notify(); |
|
135 } |
|
136 } |
|
137 } |
|
138 |
|
139 /** |
|
140 * Removes a notification. |
|
141 * |
|
142 * @see NotificationHandler#remove(int) |
|
143 */ |
|
144 public synchronized void remove(final int notificationID) { |
|
145 if (!mReady) { |
|
146 return; |
|
147 } |
|
148 |
|
149 mTaskQueue.add(new Runnable() { |
|
150 @Override |
|
151 public void run() { |
|
152 mHandler.remove(notificationID); |
|
153 mUpdatesMap.remove(notificationID); |
|
154 } |
|
155 }); |
|
156 notify(); |
|
157 } |
|
158 |
|
159 /** |
|
160 * Determines whether a notification is showing progress. |
|
161 * |
|
162 * @see NotificationHandler#isProgressStyle(int) |
|
163 */ |
|
164 public boolean isOngoing(int notificationID) { |
|
165 final NotificationHandler handler = mHandler; |
|
166 return handler != null && handler.isOngoing(notificationID); |
|
167 } |
|
168 |
|
169 protected void bind() { |
|
170 mReady = true; |
|
171 } |
|
172 |
|
173 protected void unbind() { |
|
174 mReady = false; |
|
175 mUpdatesMap.clear(); |
|
176 } |
|
177 |
|
178 protected void connectHandler(NotificationHandler handler) { |
|
179 mHandler = handler; |
|
180 new Thread(new NotificationRunnable()).start(); |
|
181 } |
|
182 |
|
183 private class NotificationRunnable implements Runnable { |
|
184 @Override |
|
185 public void run() { |
|
186 Runnable r; |
|
187 try { |
|
188 while (true) { |
|
189 // Synchronize polls to prevent tasks from being added to the queue |
|
190 // during the isDone check. |
|
191 synchronized (NotificationClient.this) { |
|
192 r = mTaskQueue.poll(); |
|
193 while (r == null) { |
|
194 if (mHandler.isDone()) { |
|
195 unbind(); |
|
196 return; |
|
197 } |
|
198 NotificationClient.this.wait(); |
|
199 r = mTaskQueue.poll(); |
|
200 } |
|
201 } |
|
202 r.run(); |
|
203 } |
|
204 } catch (InterruptedException e) { |
|
205 Log.e(LOGTAG, "Notification task queue processing interrupted", e); |
|
206 } |
|
207 } |
|
208 } |
|
209 } |