mobile/android/base/NotificationHelper.java

branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
equal deleted inserted replaced
-1:000000000000 0:272cd8c545b0
1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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 org.mozilla.gecko.gfx.BitmapUtils;
9 import org.mozilla.gecko.util.GeckoEventListener;
10
11 import org.json.JSONArray;
12 import org.json.JSONException;
13 import org.json.JSONObject;
14
15 import android.app.NotificationManager;
16 import android.app.PendingIntent;
17 import android.content.BroadcastReceiver;
18 import android.content.IntentFilter;
19 import android.content.Context;
20 import android.content.Intent;
21 import android.graphics.Bitmap;
22 import android.net.Uri;
23 import android.support.v4.app.NotificationCompat;
24 import android.util.Log;
25
26 import java.util.Iterator;
27 import java.util.Set;
28 import java.util.HashSet;
29
30 public final class NotificationHelper implements GeckoEventListener {
31 public static final String NOTIFICATION_ID = "NotificationHelper_ID";
32 private static final String LOGTAG = "GeckoNotificationManager";
33 private static final String HELPER_NOTIFICATION = "helperNotif";
34 private static final String HELPER_BROADCAST_ACTION = AppConstants.ANDROID_PACKAGE_NAME + ".helperBroadcastAction";
35
36 // Attributes mandatory to be used while sending a notification from js.
37 private static final String TITLE_ATTR = "title";
38 private static final String TEXT_ATTR = "text";
39 private static final String ID_ATTR = "id";
40 private static final String SMALLICON_ATTR = "smallIcon";
41
42 // Attributes that can be used while sending a notification from js.
43 private static final String PROGRESS_VALUE_ATTR = "progress_value";
44 private static final String PROGRESS_MAX_ATTR = "progress_max";
45 private static final String PROGRESS_INDETERMINATE_ATTR = "progress_indeterminate";
46 private static final String LIGHT_ATTR = "light";
47 private static final String ONGOING_ATTR = "ongoing";
48 private static final String WHEN_ATTR = "when";
49 private static final String PRIORITY_ATTR = "priority";
50 private static final String LARGE_ICON_ATTR = "largeIcon";
51 private static final String EVENT_TYPE_ATTR = "eventType";
52 private static final String ACTIONS_ATTR = "actions";
53 private static final String ACTION_ID_ATTR = "buttonId";
54 private static final String ACTION_TITLE_ATTR = "title";
55 private static final String ACTION_ICON_ATTR = "icon";
56 private static final String PERSISTENT_ATTR = "persistent";
57
58 private static final String NOTIFICATION_SCHEME = "moz-notification";
59
60 private static final String BUTTON_EVENT = "notification-button-clicked";
61 private static final String CLICK_EVENT = "notification-clicked";
62 private static final String CLEARED_EVENT = "notification-cleared";
63 private static final String CLOSED_EVENT = "notification-closed";
64
65 private static Context mContext;
66 private static Set<String> mClearableNotifications;
67 private static BroadcastReceiver mReceiver;
68 private static NotificationHelper mInstance;
69
70 private NotificationHelper() {
71 }
72
73 public static void init(Context context) {
74 if (mInstance != null) {
75 Log.w(LOGTAG, "NotificationHelper.init() called twice!");
76 return;
77 }
78 mInstance = new NotificationHelper();
79 mContext = context;
80 mClearableNotifications = new HashSet<String>();
81 registerEventListener("Notification:Show");
82 registerEventListener("Notification:Hide");
83 registerReceiver(context);
84 }
85
86 private static void registerEventListener(String event) {
87 GeckoAppShell.getEventDispatcher().registerEventListener(event, mInstance);
88 }
89
90 @Override
91 public void handleMessage(String event, JSONObject message) {
92 if (event.equals("Notification:Show")) {
93 showNotification(message);
94 } else if (event.equals("Notification:Hide")) {
95 hideNotification(message);
96 }
97 }
98
99 public boolean isHelperIntent(Intent i) {
100 return i.getBooleanExtra(HELPER_NOTIFICATION, false);
101 }
102
103 private static void registerReceiver(Context context) {
104 IntentFilter filter = new IntentFilter(HELPER_BROADCAST_ACTION);
105 // Scheme is needed, otherwise only broadcast with no data will be catched.
106 filter.addDataScheme(NOTIFICATION_SCHEME);
107 mReceiver = new BroadcastReceiver() {
108 @Override
109 public void onReceive(Context context, Intent intent) {
110 mInstance.handleNotificationIntent(intent);
111 }
112 };
113 context.registerReceiver(mReceiver, filter);
114 }
115
116
117 private void handleNotificationIntent(Intent i) {
118 final Uri data = i.getData();
119 if (data == null) {
120 Log.w(LOGTAG, "handleNotificationEvent: empty data");
121 return;
122 }
123 final String id = data.getQueryParameter(ID_ATTR);
124 final String notificationType = data.getQueryParameter(EVENT_TYPE_ATTR);
125 if (id == null || notificationType == null) {
126 Log.w(LOGTAG, "handleNotificationEvent: invalid intent parameters");
127 return;
128 }
129
130 // In case the user swiped out the notification, we empty the id
131 // set.
132 if (CLEARED_EVENT.equals(notificationType)) {
133 mClearableNotifications.remove(id);
134 }
135
136 if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
137 JSONObject args = new JSONObject();
138 try {
139 args.put(ID_ATTR, id);
140 args.put(EVENT_TYPE_ATTR, notificationType);
141
142 if (BUTTON_EVENT.equals(notificationType)) {
143 final String actionName = data.getQueryParameter(ACTION_ID_ATTR);
144 args.put(ACTION_ID_ATTR, actionName);
145 }
146
147 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Notification:Event", args.toString()));
148 } catch (JSONException e) {
149 Log.w(LOGTAG, "Error building JSON notification arguments.", e);
150 }
151 }
152 // If the notification was clicked, we are closing it. This must be executed after
153 // sending the event to js side because when the notification is canceled no event can be
154 // handled.
155 if (CLICK_EVENT.equals(notificationType) && !i.getBooleanExtra(ONGOING_ATTR, false)) {
156 hideNotification(id);
157 }
158
159 }
160
161 private Uri.Builder getNotificationBuilder(JSONObject message, String type) {
162 Uri.Builder b = new Uri.Builder();
163 b.scheme(NOTIFICATION_SCHEME).appendQueryParameter(EVENT_TYPE_ATTR, type);
164
165 try {
166 final String id = message.getString(ID_ATTR);
167 b.appendQueryParameter(ID_ATTR, id);
168 } catch (JSONException ex) {
169 Log.i(LOGTAG, "buildNotificationPendingIntent, error parsing", ex);
170 }
171 return b;
172 }
173
174 private Intent buildNotificationIntent(JSONObject message, Uri.Builder builder) {
175 Intent notificationIntent = new Intent(HELPER_BROADCAST_ACTION);
176 final boolean ongoing = message.optBoolean(ONGOING_ATTR);
177 notificationIntent.putExtra(ONGOING_ATTR, ongoing);
178
179 final Uri dataUri = builder.build();
180 notificationIntent.setData(dataUri);
181 notificationIntent.putExtra(HELPER_NOTIFICATION, true);
182 return notificationIntent;
183 }
184
185 private PendingIntent buildNotificationPendingIntent(JSONObject message, String type) {
186 Uri.Builder builder = getNotificationBuilder(message, type);
187 final Intent notificationIntent = buildNotificationIntent(message, builder);
188 PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
189 return pi;
190 }
191
192 private PendingIntent buildButtonClickPendingIntent(JSONObject message, JSONObject action) {
193 Uri.Builder builder = getNotificationBuilder(message, BUTTON_EVENT);
194 try {
195 // Action name must be in query uri, otherwise buttons pending intents
196 // would be collapsed.
197 if(action.has(ACTION_ID_ATTR)) {
198 builder.appendQueryParameter(ACTION_ID_ATTR, action.getString(ACTION_ID_ATTR));
199 } else {
200 Log.i(LOGTAG, "button event with no name");
201 }
202 } catch (JSONException ex) {
203 Log.i(LOGTAG, "buildNotificationPendingIntent, error parsing", ex);
204 }
205 final Intent notificationIntent = buildNotificationIntent(message, builder);
206 PendingIntent res = PendingIntent.getBroadcast(mContext, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
207 return res;
208 }
209
210 private void showNotification(JSONObject message) {
211 NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext);
212
213 // These attributes are required
214 final String id;
215 try {
216 builder.setContentTitle(message.getString(TITLE_ATTR));
217 builder.setContentText(message.getString(TEXT_ATTR));
218 id = message.getString(ID_ATTR);
219 } catch (JSONException ex) {
220 Log.i(LOGTAG, "Error parsing", ex);
221 return;
222 }
223
224 Uri imageUri = Uri.parse(message.optString(SMALLICON_ATTR));
225 builder.setSmallIcon(BitmapUtils.getResource(imageUri, R.drawable.ic_status_logo));
226
227 JSONArray light = message.optJSONArray(LIGHT_ATTR);
228 if (light != null && light.length() == 3) {
229 try {
230 builder.setLights(light.getInt(0),
231 light.getInt(1),
232 light.getInt(2));
233 } catch (JSONException ex) {
234 Log.i(LOGTAG, "Error parsing", ex);
235 }
236 }
237
238 boolean ongoing = message.optBoolean(ONGOING_ATTR);
239 builder.setOngoing(ongoing);
240
241 if (message.has(WHEN_ATTR)) {
242 long when = message.optLong(WHEN_ATTR);
243 builder.setWhen(when);
244 }
245
246 if (message.has(PRIORITY_ATTR)) {
247 int priority = message.optInt(PRIORITY_ATTR);
248 builder.setPriority(priority);
249 }
250
251 if (message.has(LARGE_ICON_ATTR)) {
252 Bitmap b = BitmapUtils.getBitmapFromDataURI(message.optString(LARGE_ICON_ATTR));
253 builder.setLargeIcon(b);
254 }
255
256 if (message.has(PROGRESS_VALUE_ATTR) &&
257 message.has(PROGRESS_MAX_ATTR) &&
258 message.has(PROGRESS_INDETERMINATE_ATTR)) {
259 try {
260 final int progress = message.getInt(PROGRESS_VALUE_ATTR);
261 final int progressMax = message.getInt(PROGRESS_MAX_ATTR);
262 final boolean progressIndeterminate = message.getBoolean(PROGRESS_INDETERMINATE_ATTR);
263 builder.setProgress(progressMax, progress, progressIndeterminate);
264 } catch (JSONException ex) {
265 Log.i(LOGTAG, "Error parsing", ex);
266 }
267 }
268
269 JSONArray actions = message.optJSONArray(ACTIONS_ATTR);
270 if (actions != null) {
271 try {
272 for (int i = 0; i < actions.length(); i++) {
273 JSONObject action = actions.getJSONObject(i);
274 final PendingIntent pending = buildButtonClickPendingIntent(message, action);
275 final String actionTitle = action.getString(ACTION_TITLE_ATTR);
276 final Uri actionImage = Uri.parse(action.optString(ACTION_ICON_ATTR));
277 builder.addAction(BitmapUtils.getResource(actionImage, R.drawable.ic_status_logo),
278 actionTitle,
279 pending);
280 }
281 } catch (JSONException ex) {
282 Log.i(LOGTAG, "Error parsing", ex);
283 }
284 }
285
286 PendingIntent pi = buildNotificationPendingIntent(message, CLICK_EVENT);
287 builder.setContentIntent(pi);
288 PendingIntent deletePendingIntent = buildNotificationPendingIntent(message, CLEARED_EVENT);
289 builder.setDeleteIntent(deletePendingIntent);
290
291 GeckoAppShell.notificationClient.add(id.hashCode(), builder.build());
292
293 boolean persistent = message.optBoolean(PERSISTENT_ATTR);
294 // We add only not persistent notifications to the list since we want to purge only
295 // them when geckoapp is destroyed.
296 if (!persistent && !mClearableNotifications.contains(id)) {
297 mClearableNotifications.add(id);
298 }
299 }
300
301 private void hideNotification(JSONObject message) {
302 String id;
303 try {
304 id = message.getString("id");
305 } catch (JSONException ex) {
306 Log.i(LOGTAG, "Error parsing", ex);
307 return;
308 }
309
310 hideNotification(id);
311 }
312
313 private void sendNotificationWasClosed(String id) {
314 if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
315 return;
316 }
317 JSONObject args = new JSONObject();
318 try {
319 args.put(ID_ATTR, id);
320 args.put(EVENT_TYPE_ATTR, CLOSED_EVENT);
321 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Notification:Event", args.toString()));
322 } catch (JSONException ex) {
323 Log.w(LOGTAG, "sendNotificationWasClosed: error building JSON notification arguments.", ex);
324 }
325 }
326
327 private void closeNotification(String id) {
328 GeckoAppShell.notificationClient.remove(id.hashCode());
329 sendNotificationWasClosed(id);
330 }
331
332 public void hideNotification(String id) {
333 mClearableNotifications.remove(id);
334 closeNotification(id);
335 }
336
337 private void clearAll() {
338 for (Iterator<String> i = mClearableNotifications.iterator(); i.hasNext();) {
339 final String id = i.next();
340 i.remove();
341 closeNotification(id);
342 }
343 }
344
345 public static void destroy() {
346 if (mInstance != null) {
347 mInstance.clearAll();
348 }
349 }
350 }

mercurial