mobile/android/base/NotificationHelper.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

     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/. */
     6 package org.mozilla.gecko;
     8 import org.mozilla.gecko.gfx.BitmapUtils;
     9 import org.mozilla.gecko.util.GeckoEventListener;
    11 import org.json.JSONArray;
    12 import org.json.JSONException;
    13 import org.json.JSONObject;
    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;
    26 import java.util.Iterator;
    27 import java.util.Set;
    28 import java.util.HashSet;
    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";
    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";
    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";
    58     private static final String NOTIFICATION_SCHEME = "moz-notification";
    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";
    65     private static Context mContext;
    66     private static Set<String> mClearableNotifications;
    67     private static BroadcastReceiver mReceiver;
    68     private static NotificationHelper mInstance;
    70     private NotificationHelper() {
    71     }
    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     }
    86     private static void registerEventListener(String event) {
    87         GeckoAppShell.getEventDispatcher().registerEventListener(event, mInstance);
    88     }
    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     }
    99     public boolean isHelperIntent(Intent i) {
   100         return i.getBooleanExtra(HELPER_NOTIFICATION, false);
   101     }
   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     }
   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         }
   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         }
   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);
   142                 if (BUTTON_EVENT.equals(notificationType)) {
   143                     final String actionName = data.getQueryParameter(ACTION_ID_ATTR);
   144                     args.put(ACTION_ID_ATTR, actionName);
   145                 }
   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         }
   159     }
   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);
   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     }
   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);
   179         final Uri dataUri = builder.build();
   180         notificationIntent.setData(dataUri);
   181         notificationIntent.putExtra(HELPER_NOTIFICATION, true);
   182         return notificationIntent;
   183     }
   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     }
   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     }
   210     private void showNotification(JSONObject message) {
   211         NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext);
   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         }
   224         Uri imageUri = Uri.parse(message.optString(SMALLICON_ATTR));
   225         builder.setSmallIcon(BitmapUtils.getResource(imageUri, R.drawable.ic_status_logo));
   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         }
   238         boolean ongoing = message.optBoolean(ONGOING_ATTR);
   239         builder.setOngoing(ongoing);
   241         if (message.has(WHEN_ATTR)) {
   242             long when = message.optLong(WHEN_ATTR);
   243             builder.setWhen(when);
   244         }
   246         if (message.has(PRIORITY_ATTR)) {
   247             int priority = message.optInt(PRIORITY_ATTR);
   248             builder.setPriority(priority);
   249         }
   251         if (message.has(LARGE_ICON_ATTR)) {
   252             Bitmap b = BitmapUtils.getBitmapFromDataURI(message.optString(LARGE_ICON_ATTR));
   253             builder.setLargeIcon(b);
   254         }
   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         }
   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         }
   286         PendingIntent pi = buildNotificationPendingIntent(message, CLICK_EVENT);
   287         builder.setContentIntent(pi);
   288         PendingIntent deletePendingIntent = buildNotificationPendingIntent(message, CLEARED_EVENT);
   289         builder.setDeleteIntent(deletePendingIntent);
   291         GeckoAppShell.notificationClient.add(id.hashCode(), builder.build());
   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     }
   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         }
   310         hideNotification(id);
   311     }
   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     }
   327     private void closeNotification(String id) {
   328         GeckoAppShell.notificationClient.remove(id.hashCode());
   329         sendNotificationWasClosed(id);
   330     }
   332     public void hideNotification(String id) {
   333         mClearableNotifications.remove(id);
   334         closeNotification(id);
   335     }
   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     }
   345     public static void destroy() {
   346         if (mInstance != null) {
   347             mInstance.clearAll();
   348         }
   349     }
   350 }

mercurial