Bug 885783 - Attach webrtc to a foreground notification. r=bnicholson, a=bajaj
authorWes Johnston <wjohnston@mozilla.com>
Thu, 18 Jul 2013 16:35:34 -0700
changeset 148020 7d47ae7af1fcff58ed9e0bd12e50a134a554d5dc
parent 148019 1fa6d0bcd26f8802a9821b494fd87d40a8733d8c
child 148021 48011fcf0225c849ef2dd8c4146b30b8704c4bad
push id2697
push userbbajaj@mozilla.com
push dateMon, 05 Aug 2013 18:49:53 +0000
treeherdermozilla-beta@dfec938c7b63 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbnicholson, bajaj
bugs885783
milestone24.0a2
Bug 885783 - Attach webrtc to a foreground notification. r=bnicholson, a=bajaj
mobile/android/base/GeckoAppShell.java
mobile/android/base/NotificationClient.java
mobile/android/base/NotificationHandler.java
mobile/android/base/NotificationHelper.java
mobile/android/base/NotificationService.java
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -1232,23 +1232,16 @@ public class GeckoAppShell
             sNotificationClient = client;
         } else {
             Log.d(LOGTAG, "Notification client already set");
         }
     }
 
     public static void showAlertNotification(String aImageUrl, String aAlertTitle, String aAlertText,
                                              String aAlertCookie, String aAlertName) {
-        Log.d(LOGTAG, "GeckoAppShell.showAlertNotification\n" +
-            "- image = '" + aImageUrl + "'\n" +
-            "- title = '" + aAlertTitle + "'\n" +
-            "- text = '" + aAlertText +"'\n" +
-            "- cookie = '" + aAlertCookie +"'\n" +
-            "- name = '" + aAlertName + "'");
-
         // The intent to launch when the user clicks the expanded notification
         String app = getContext().getClass().getName();
         Intent notificationIntent = new Intent(GeckoApp.ACTION_ALERT_CALLBACK);
         notificationIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, app);
         notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
         int notificationID = aAlertName.hashCode();
 
@@ -1292,17 +1285,17 @@ public class GeckoAppShell
     }
 
     public static void handleNotification(String aAction, String aAlertName, String aAlertCookie) {
         int notificationID = aAlertName.hashCode();
 
         if (GeckoApp.ACTION_ALERT_CALLBACK.equals(aAction)) {
             callObserver(aAlertName, "alertclickcallback", aAlertCookie);
 
-            if (sNotificationClient.isProgressStyle(notificationID)) {
+            if (sNotificationClient.isOngoing(notificationID)) {
                 // When clicked, keep the notification if it displays progress
                 return;
             }
         }
 
         // Also send a notification to the observer service
         // New listeners should register for these notifications since they will be called even if
         // Gecko has been killed and restared between when your notification was shown and when the
--- a/mobile/android/base/NotificationClient.java
+++ b/mobile/android/base/NotificationClient.java
@@ -1,15 +1,16 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
+import android.app.Notification;
 import android.app.PendingIntent;
 import android.text.TextUtils;
 import android.util.Log;
 
 import java.util.LinkedList;
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
@@ -84,16 +85,35 @@ public abstract class NotificationClient
         notify();
 
         if (!mReady) {
             bind();
         }
     }
 
     /**
+     * Adds a notification.
+     *
+     * @see NotificationHandler#add(int, Notification)
+     */
+    public synchronized void add(final int notificationID, final Notification notification) {
+        mTaskQueue.add(new Runnable() {
+            @Override
+            public void run() {
+                mHandler.add(notificationID, notification);
+            }
+        });
+        notify();
+
+        if (!mReady) {
+            bind();
+        }
+    }
+
+    /**
      * Updates a notification.
      *
      * @see NotificationHandler#update(int, long, long, String)
      */
     public void update(final int notificationID, final long aProgress, final long aProgressMax,
             final String aAlertText) {
         UpdateRunnable runnable = mUpdatesMap.get(notificationID);
 
@@ -136,19 +156,19 @@ public abstract class NotificationClient
         notify();
     }
 
     /**
      * Determines whether a notification is showing progress.
      *
      * @see NotificationHandler#isProgressStyle(int)
      */
-    public boolean isProgressStyle(int notificationID) {
+    public boolean isOngoing(int notificationID) {
         final NotificationHandler handler = mHandler;
-        return handler != null && handler.isProgressStyle(notificationID);
+        return handler != null && handler.isOngoing(notificationID);
     }
 
     protected void bind() {
         mReady = true;
     }
 
     protected void unbind() {
         mReady = false;
--- a/mobile/android/base/NotificationHandler.java
+++ b/mobile/android/base/NotificationHandler.java
@@ -2,41 +2,46 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.gfx.BitmapUtils;
 
+import android.app.Notification;
+import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.net.Uri;
+import android.util.Log;
 
 import java.util.concurrent.ConcurrentHashMap;
 
 public class NotificationHandler {
-    private final ConcurrentHashMap<Integer, AlertNotification>
-            mAlertNotifications = new ConcurrentHashMap<Integer, AlertNotification>();
+    private final ConcurrentHashMap<Integer, Notification>
+            mNotifications = new ConcurrentHashMap<Integer, Notification>();
     private final Context mContext;
+    private final NotificationManager mNotificationManager;
 
     /**
      * Notification associated with this service's foreground state.
      *
      * {@link android.app.Service#startForeground(int, android.app.Notification)}
      * associates the foreground with exactly one notification from the service.
      * To keep Fennec alive during downloads (and to make sure it can be killed
      * once downloads are complete), we make sure that the foreground is always
      * associated with an active progress notification if and only if at least
      * one download is in progress.
      */
-    private AlertNotification mForegroundNotification;
+    private Notification mForegroundNotification;
 
     public NotificationHandler(Context context) {
         mContext = context;
+        mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
     }
 
     /**
      * Adds a notification.
      *
      * @param notificationID the unique ID of the notification
      * @param aImageUrl      URL of the image to use
      * @param aAlertTitle    title of the notification
@@ -51,95 +56,140 @@ public class NotificationHandler {
 
         Uri imageUri = Uri.parse(aImageUrl);
         int icon = BitmapUtils.getResource(imageUri, R.drawable.ic_status_logo);
         final AlertNotification notification = new AlertNotification(mContext, notificationID,
                 icon, aAlertTitle, aAlertText, System.currentTimeMillis(), imageUri);
 
         notification.setLatestEventInfo(mContext, aAlertTitle, aAlertText, contentIntent);
 
-        notification.show();
-        mAlertNotifications.put(notification.getId(), notification);
+        mNotificationManager.notify(notificationID, notification);
+        mNotifications.put(notificationID, notification);
+    }
+
+    /**
+     * Adds a notification.
+     *
+     * @param id             the unique ID of the notification
+     * @param aNotification  the Notification to add
+     */
+    public void add(int id, Notification notification) {
+        mNotificationManager.notify(id, notification);
+        mNotifications.put(id, notification);
+
+        if (mForegroundNotification == null && isOngoing(notification)) {
+            setForegroundNotification(id, notification);
+        }
     }
 
     /**
      * Updates a notification.
      *
      * @param notificationID ID of existing notification
      * @param aProgress      progress of item being updated
      * @param aProgressMax   max progress of item being updated
      * @param aAlertText     text of the notification
      */
     public void update(int notificationID, long aProgress, long aProgressMax, String aAlertText) {
-        final AlertNotification notification = mAlertNotifications.get(notificationID);
+        final Notification notification = mNotifications.get(notificationID);
         if (notification == null) {
             return;
         }
 
-        notification.updateProgress(aAlertText, aProgress, aProgressMax);
+        if (notification instanceof AlertNotification) {
+            AlertNotification alert = (AlertNotification)notification;
+            alert.updateProgress(aAlertText, aProgress, aProgressMax);
+        }
 
-        if (mForegroundNotification == null && notification.isProgressStyle()) {
-            setForegroundNotification(notification);
+        if (mForegroundNotification == null && isOngoing(notification)) {
+            setForegroundNotification(notificationID, notification);
         }
 
         // Hide the notification at 100%
         if (aProgress == aProgressMax) {
             remove(notificationID);
         }
     }
 
     /**
      * Removes a notification.
      *
      * @param notificationID ID of existing notification
      */
     public void remove(int notificationID) {
-        final AlertNotification notification = mAlertNotifications.remove(notificationID);
+        final Notification notification = mNotifications.remove(notificationID);
         if (notification != null) {
-            updateForegroundNotification(notification);
-            notification.cancel();
+            updateForegroundNotification(notificationID, notification);
         }
+        mNotificationManager.cancel(notificationID);
     }
 
     /**
      * Determines whether the service is done.
      *
      * The service is considered finished when all notifications have been
      * removed.
      *
      * @return whether all notifications have been removed
      */
     public boolean isDone() {
-        return mAlertNotifications.isEmpty();
+        return mNotifications.isEmpty();
+    }
+
+    /**
+     * Determines whether a notification should hold a foreground service to keep Gecko alive
+     *
+     * @param notificationID the id of the notification to check
+     * @return               whether the notification is ongoing
+     */
+    public boolean isOngoing(int notificationID) {
+        final Notification notification = mNotifications.get(notificationID);
+        return isOngoing(notification);
     }
 
     /**
-     * Determines whether a notification is showing progress.
+     * Determines whether a notification should hold a foreground service to keep Gecko alive
      *
-     * @param notificationID the notification to check
-     * @return               whether the notification is progress style
+     * @param notification   the notification to check
+     * @return               whether the notification is ongoing
      */
-    public boolean isProgressStyle(int notificationID) {
-        final AlertNotification notification = mAlertNotifications.get(notificationID);
-        return notification != null && notification.isProgressStyle();
+    public boolean isOngoing(Notification notification) {
+        if (notification != null && (isProgressStyle(notification) || ((notification.flags & Notification.FLAG_ONGOING_EVENT) > 0))) {
+            return true;
+        }
+        return false;
     }
 
-    protected void setForegroundNotification(AlertNotification notification) {
+    /**
+     * Helper method to determines whether a notification is an AlertNotification that is showing progress
+     * This method will be deprecated when AlertNotifications are removed (bug 893289). 
+     *
+     * @param notification   the notification to check
+     * @return               whether the notification is an AlertNotification showing progress.
+     */
+    private boolean isProgressStyle(Notification notification) {
+        if (notification != null && notification instanceof AlertNotification) {
+            return ((AlertNotification)notification).isProgressStyle();
+        }
+        return false;
+    }
+
+    protected void setForegroundNotification(int id, Notification notification) {
         mForegroundNotification = notification;
     }
 
-    private void updateForegroundNotification(AlertNotification oldNotification) {
+    private void updateForegroundNotification(int id, Notification oldNotification) {
         if (mForegroundNotification == oldNotification) {
             // If we're removing the notification associated with the
             // foreground, we need to pick another active notification to act
             // as the foreground notification.
-            AlertNotification foregroundNotification = null;
-            for (final AlertNotification notification : mAlertNotifications.values()) {
-                if (notification.isProgressStyle()) {
+            Notification foregroundNotification = null;
+            for (final Notification notification : mNotifications.values()) {
+                if (isOngoing(notification)) {
                     foregroundNotification = notification;
                     break;
                 }
             }
 
-            setForegroundNotification(foregroundNotification);
+            setForegroundNotification(id, foregroundNotification);
         }
     }
 }
--- a/mobile/android/base/NotificationHelper.java
+++ b/mobile/android/base/NotificationHelper.java
@@ -101,18 +101,17 @@ public class NotificationHelper implemen
         // can remove the notification from our list of active notifications if its clicked
         if (!ongoing) {
             notificationIntent.putExtra(NOTIFICATION_ID, id);
         }
 
         PendingIntent pi = PendingIntent.getActivity(mContext, 0, notificationIntent, 0);
         builder.setContentIntent(pi);
 
-        NotificationManager manager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
-        manager.notify(id.hashCode(), builder.build());
+        GeckoAppShell.sNotificationClient.add(id.hashCode(), builder.build());
         if (!mShowing.contains(id)) {
             mShowing.add(id);
         }
     }
 
     private void hideNotification(JSONObject message) {
         String id;
         try {
@@ -121,18 +120,17 @@ public class NotificationHelper implemen
             Log.i(LOGTAG, "Error parsing", ex);
             return;
         }
 
         hideNotification(id);
     }
 
     public void hideNotification(String id) {
-        NotificationManager manager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
-        manager.cancel(id.hashCode());
+        GeckoAppShell.sNotificationClient.remove(id.hashCode());
         mShowing.remove(id);
     }
 
     private void clearAll() {
         for (String id : mShowing) {
             hideNotification(id);
         }
     }
--- a/mobile/android/base/NotificationService.java
+++ b/mobile/android/base/NotificationService.java
@@ -1,34 +1,42 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
+import android.app.Notification;
 import android.app.Service;
 import android.content.Intent;
 import android.os.Binder;
 import android.os.IBinder;
 
 public class NotificationService extends Service {
     private final IBinder mBinder = new NotificationBinder();
-    private final NotificationHandler mHandler = new NotificationHandler(this) {
-        @Override
-        protected void setForegroundNotification(AlertNotification notification) {
-            super.setForegroundNotification(notification);
+    private NotificationHandler mHandler;
 
-            if (notification == null) {
-                stopForeground(true);
-            } else {
-                startForeground(notification.getId(), notification);
+    @Override
+    public void onCreate() {
+        // This has to be initialized in onCreate in order to ensure that the NotificationHandler can
+        // access the NotificationManager service.
+        mHandler = new NotificationHandler(this) {
+            @Override
+            protected void setForegroundNotification(int id, Notification notification) {
+                super.setForegroundNotification(id, notification);
+
+                if (notification == null) {
+                    stopForeground(true);
+                } else {
+                    startForeground(id, notification);
+                }
             }
-        }
-    };
+        };
+    }
 
     public class NotificationBinder extends Binder {
         NotificationService getService() {
             // Return this instance of NotificationService so clients can call public methods
             return NotificationService.this;
         }
     }