Bug 1304145 - 2. Change GeckoAppShell's notification interface; r=nalexander
authorJim Chen <nchen@mozilla.com>
Thu, 22 Sep 2016 16:38:19 -0400
changeset 314941 4cffc34944279275900d4489a50011268586cee9
parent 314940 10ce1c1de6b777af5aeabb31698cc91ba3e44ec2
child 314942 d676a611d3ac68dfb398527fae4026629f1b0775
push id82024
push usernchen@mozilla.com
push dateThu, 22 Sep 2016 20:38:38 +0000
treeherdermozilla-inbound@4ead4233329b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnalexander
bugs1304145
milestone52.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1304145 - 2. Change GeckoAppShell's notification interface; r=nalexander Instead of using NotificationClient directly from GeckoAppShell, add a NotificationListener interface, which NotificationClient would implement. This isolates NotificationClient (and the notification package) from GeckoAppShell and lets us move the notification package to Fennec. It also makes a cleaner interface for GeckoView consumers to implement notification support.
mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
mobile/android/base/moz.build
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoService.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/NotificationListener.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/notifications/NotificationHelper.java
widget/android/AndroidAlerts.cpp
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -1136,21 +1136,20 @@ public abstract class GeckoApp
 
         // GeckoAppShell is tightly coupled to us, rather than
         // the app context, because various parts of Fennec (e.g.,
         // GeckoScreenOrientation) use GAS to access the Activity in
         // the guise of fetching a Context.
         // When that's fixed, `this` can change to
         // `(GeckoApplication) getApplication()` here.
         GeckoAppShell.setContextGetter(this);
-        GeckoAppShell.setApplicationContext(getApplicationContext());
         GeckoAppShell.setGeckoInterface(this);
         // We need to set the notification client before launching Gecko, since Gecko could start
         // sending notifications immediately after startup, which we don't want to lose/crash on.
-        GeckoAppShell.setNotificationClient(makeNotificationClient());
+        GeckoAppShell.setNotificationListener(makeNotificationClient());
 
         // Tell Stumbler to register a local broadcast listener to listen for preference intents.
         // We do this via intents since we can't easily access Stumbler directly,
         // as it might be compiled outside of Fennec.
         getApplicationContext().sendBroadcast(
                 new Intent(INTENT_REGISTER_STUMBLER_LISTENER)
         );
 
@@ -2389,17 +2388,18 @@ public abstract class GeckoApp
         });
     }
 
     public void handleNotification(String action, String alertName, String alertCookie) {
         // If Gecko isn't running yet, we ignore the notification. Note that
         // even if Gecko is running but it was restarted since the notification
         // was created, the notification won't be handled (bug 849653).
         if (GeckoThread.isRunning()) {
-            GeckoAppShell.handleNotification(action, alertName, alertCookie);
+            ((NotificationClient) GeckoAppShell.getNotificationListener()).onNotificationClick(
+                    alertName);
         }
     }
 
     private void checkMigrateProfile() {
         final File profileDir = getProfile().getDir();
 
         if (profileDir != null) {
             ThreadUtils.postToBackgroundThread(new Runnable() {
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
@@ -153,16 +153,17 @@ public class GeckoApplication extends Ap
 
     @Override
     public void onCreate() {
         Log.i(LOG_TAG, "zerdatime " + SystemClock.uptimeMillis() + " - Fennec application start");
 
         mRefWatcher = LeakCanary.install(this);
 
         final Context context = getApplicationContext();
+        GeckoAppShell.setApplicationContext(context);
         HardwareUtils.init(context);
         Clipboard.init(context);
         FilePicker.init(context);
         DownloadsIntegration.init();
         HomePanelsManager.getInstance().init(context);
 
         // This getInstance call will force initialization of the NotificationHelper, but does nothing with the result
         NotificationHelper.getInstance(context).init();
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -264,16 +264,17 @@ gvjar.sources += [geckoview_source_dir +
     'gfx/RenderTask.java',
     'gfx/SimpleScaleGestureDetector.java',
     'gfx/StackScroller.java',
     'gfx/SubdocumentScrollHelper.java',
     'gfx/SurfaceTextureListener.java',
     'gfx/ViewTransform.java',
     'InputConnectionListener.java',
     'InputMethods.java',
+    'NotificationListener.java',
     'notifications/AppNotificationClient.java',
     'notifications/NotificationClient.java',
     'notifications/NotificationHandler.java',
     'notifications/NotificationHelper.java',
     'notifications/NotificationReceiver.java',
     'notifications/NotificationService.java',
     'notifications/ServiceNotificationClient.java',
     'NSSBridge.java',
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
@@ -28,17 +28,16 @@ import java.util.concurrent.ConcurrentHa
 import android.annotation.SuppressLint;
 import org.mozilla.gecko.annotation.JNITarget;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.gfx.PanZoomController;
-import org.mozilla.gecko.notifications.NotificationClient;
 import org.mozilla.gecko.permissions.Permissions;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoRequest;
 import org.mozilla.gecko.util.HardwareCodecCapabilityUtils;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSContainer;
 import org.mozilla.gecko.util.NativeJSObject;
@@ -177,23 +176,16 @@ public class GeckoAppShell
 
     public static CrashHandler ensureCrashHandling() {
         // Crash handling is automatically enabled when GeckoAppShell is loaded.
         return CRASH_HANDLER;
     }
 
     private static volatile boolean locationHighAccuracyEnabled;
 
-    // Accessed by NotificationHelper. This should be encapsulated.
-    /* package */ static NotificationClient notificationClient;
-
-    public static NotificationClient getNotificationClient() {
-        return notificationClient;
-    }
-
     // See also HardwareUtils.LOW_MEMORY_THRESHOLD_MB.
     private static final int HIGH_MEMORY_DEVICE_THRESHOLD_MB = 768;
 
     static private int sDensityDpi;
     static private int sScreenDepth;
 
     /* Is the value in sVibrationEndTime valid? */
     private static boolean sVibrationMaybePlaying;
@@ -462,17 +454,18 @@ public class GeckoAppShell
     /* package */ static native void onSensorChanged(int hal_type, float x, float y, float z,
                                                      float w, int accuracy, long time);
 
     @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
     /* package */ static native void onLocationChanged(double latitude, double longitude,
                                                        double altitude, float accuracy,
                                                        float bearing, float speed, long time);
 
-    private static class DefaultListeners implements SensorEventListener, LocationListener {
+    private static class DefaultListeners
+            implements SensorEventListener, LocationListener, NotificationListener {
         @Override
         public void onAccuracyChanged(Sensor sensor, int accuracy) {
         }
 
         private static int HalSensorAccuracyFor(int androidAccuracy) {
             switch (androidAccuracy) {
             case SensorManager.SENSOR_STATUS_UNRELIABLE:
                 return GeckoHalDefines.SENSOR_ACCURACY_UNRELIABLE;
@@ -572,36 +565,65 @@ public class GeckoAppShell
         public void onProviderEnabled(String provider)
         {
         }
 
         @Override
         public void onStatusChanged(String provider, int status, Bundle extras)
         {
         }
+
+        @Override // NotificationListener
+        public void showNotification(String name, String cookie, String host,
+                                     String title, String text, String imageUrl) {
+            // Default is to not show the notification, and immediate send close message.
+            GeckoAppShell.onNotificationClose(name);
+        }
+
+        @Override // NotificationListener
+        public void showPersistentNotification(String name, String cookie, String host,
+                                               String title, String text, String imageUrl,
+                                               String data) {
+            // Default is to not show the notification, and immediate send close message.
+            GeckoAppShell.onNotificationClose(name);
+        }
+
+        @Override // NotificationListener
+        public void closeNotification(String name) {
+            // Do nothing.
+        }
     }
 
     private static final DefaultListeners DEFAULT_LISTENERS = new DefaultListeners();
     private static SensorEventListener sSensorListener = DEFAULT_LISTENERS;
     private static LocationListener sLocationListener = DEFAULT_LISTENERS;
+    private static NotificationListener sNotificationListener = DEFAULT_LISTENERS;
 
     public static SensorEventListener getSensorListener() {
         return sSensorListener;
     }
 
     public static void setSensorListener(final SensorEventListener listener) {
-        sSensorListener = listener;
+        sSensorListener = (listener != null) ? listener : DEFAULT_LISTENERS;
     }
 
     public static LocationListener getLocationListener() {
         return sLocationListener;
     }
 
     public static void setLocationListener(final LocationListener listener) {
-        sLocationListener = listener;
+        sLocationListener = (listener != null) ? listener : DEFAULT_LISTENERS;
+    }
+
+    public static NotificationListener getNotificationListener() {
+        return sNotificationListener;
+    }
+
+    public static void setNotificationListener(final NotificationListener listener) {
+        sNotificationListener = (listener != null) ? listener : DEFAULT_LISTENERS;
     }
 
     @WrapForJNI(calledFrom = "gecko")
     private static void enableSensor(int aSensortype) {
         GeckoInterface gi = getGeckoInterface();
         if (gi == null) {
             return;
         }
@@ -916,107 +938,65 @@ public class GeckoAppShell
                                            String title) {
         final GeckoInterface geckoInterface = getGeckoInterface();
         if (geckoInterface == null) {
             return false;
         }
         return geckoInterface.openUriExternal(targetURI, mimeType, packageName, className, action, title);
     }
 
-    /**
-     * Only called from GeckoApp.
-     */
-    public static void setNotificationClient(NotificationClient client) {
-        if (notificationClient == null) {
-            notificationClient = client;
-        } else {
-            Log.d(LOGTAG, "Notification client already set");
-        }
-    }
-
-    private static PendingIntent makePersistentNotificationIntent(int notificationID, String type,
-                                                                  String persistentData) {
-        final Uri.Builder b = new Uri.Builder();
-        final Uri u = b.scheme("notification-event")
-                .path(Integer.toString(notificationID))
-                .appendQueryParameter("type", type)
-                .build();
-        final Intent intent = GeckoService.getIntentToCreateServices(
-                getApplicationContext(), type, persistentData);
-        intent.setData(u);
-
-        return PendingIntent.getService(getApplicationContext(), 0, intent,
-                                        PendingIntent.FLAG_UPDATE_CURRENT);
-    }
-
     @WrapForJNI(dispatchTo = "gecko")
     private static native void notifyAlertListener(String name, String topic);
 
-    @WrapForJNI(calledFrom = "gecko")
-    private static void showAlertNotification(String imageUrl, String alertTitle, String alertText,
-                                              String alertCookie, String alertName, String host,
-                                              String persistentData) {
-        final int notificationID = alertName.hashCode();
-        final PendingIntent clickIntent, closeIntent;
-
-        if (persistentData != null) {
-            clickIntent = makePersistentNotificationIntent(
-                    notificationID, "persistent-notification-click", persistentData);
-            closeIntent = makePersistentNotificationIntent(
-                    notificationID, "persistent-notification-close", persistentData);
-
-        } else {
-            notifyAlertListener(alertName, "alertshow");
+    /**
+     * Called by the NotificationListener to notify Gecko that a notification has been
+     * shown.
+     */
+    public static void onNotificationShow(final String name) {
+        if (GeckoThread.isRunning()) {
+            notifyAlertListener(name, "alertshow");
+        }
+    }
 
-            // The intent to launch when the user clicks the expanded notification
-            final Intent notificationIntent = new Intent(ACTION_ALERT_CALLBACK);
-            notificationIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME,
-                                            AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
-            notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+    /**
+     * Called by the NotificationListener to notify Gecko that a previously shown
+     * notification has been closed.
+     */
+    public static void onNotificationClose(final String name) {
+        if (GeckoThread.isRunning()) {
+            notifyAlertListener(name, "alertfinished");
+        }
+    }
 
-            // Put the strings into the intent as an URI
-            // "alert:?name=<alertName>&app=<appName>&cookie=<cookie>"
-            final Uri.Builder b = new Uri.Builder();
-            final Uri dataUri = b.scheme("alert")
-                    .path(Integer.toString(notificationID))
-                    .appendQueryParameter("name", alertName)
-                    .appendQueryParameter("cookie", alertCookie)
-                    .build();
-            notificationIntent.setData(dataUri);
-
-            clickIntent = PendingIntent.getActivity(getApplicationContext(), 0, notificationIntent,
-                                                    PendingIntent.FLAG_UPDATE_CURRENT);
-            closeIntent = null;
+    /**
+     * Called by the NotificationListener to notify Gecko that a previously shown
+     * notification has been clicked on.
+     */
+    public static void onNotificationClick(final String name) {
+        if (GeckoThread.isRunning()) {
+            notifyAlertListener(name, "alertclickcallback");
         }
-
-        notificationClient.add(notificationID, imageUrl, host, alertTitle,
-                               alertText, clickIntent, closeIntent);
     }
 
     @WrapForJNI(calledFrom = "gecko")
-    private static void closeNotification(String alertName) {
-        notifyAlertListener(alertName, "alertfinished");
+    private static void showNotification(String name, String cookie, String title,
+                                         String text, String host, String imageUrl,
+                                         String persistentData) {
+        if (persistentData == null) {
+            getNotificationListener().showNotification(name, cookie, title, text, host, imageUrl);
+            return;
+        }
 
-        final int notificationID = alertName.hashCode();
-        notificationClient.remove(notificationID);
+        getNotificationListener().showPersistentNotification(
+                name, cookie, title, text, host, imageUrl, persistentData);
     }
 
-    public static void handleNotification(String action, String alertName, String alertCookie) {
-        final int notificationID = alertName.hashCode();
-
-        if (ACTION_ALERT_CALLBACK.equals(action)) {
-            notifyAlertListener(alertName, "alertclickcallback");
-
-            if (notificationClient.isOngoing(notificationID)) {
-                // When clicked, keep the notification if it displays progress
-                return;
-            }
-        }
-
-        closeNotification(alertName);
+    @WrapForJNI(calledFrom = "gecko")
+    private static void closeNotification(String name) {
+        getNotificationListener().closeNotification(name);
     }
 
     @WrapForJNI(calledFrom = "gecko")
     public static int getDpi() {
         if (sDensityDpi == 0) {
             sDensityDpi = getApplicationContext().getResources().getDisplayMetrics().densityDpi;
         }
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoService.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoService.java
@@ -82,18 +82,17 @@ public class GeckoService extends Servic
         }
         EventDispatcher.getInstance().unregisterGeckoThreadListener(EVENT_LISTENER,
                 "Gecko:ScheduleRun");
     }
 
     @Override // Service
     public void onCreate() {
         GeckoAppShell.ensureCrashHandling();
-        GeckoAppShell.setApplicationContext(getApplicationContext());
-        GeckoAppShell.setNotificationClient(new ServiceNotificationClient(getApplicationContext()));
+        GeckoAppShell.setNotificationListener(new ServiceNotificationClient(getApplicationContext()));
         GeckoThread.onResume();
         super.onCreate();
 
         if (DEBUG) {
             Log.d(LOGTAG, "Created");
         }
     }
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/NotificationListener.java
@@ -0,0 +1,17 @@
+/* -*- 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;
+
+public interface NotificationListener
+{
+    void showNotification(String name, String cookie, String title, String text,
+                          String host, String imageUrl);
+
+    void showPersistentNotification(String name, String cookie, String title, String text,
+                                    String host, String imageUrl, String data);
+
+    void closeNotification(String name);
+}
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/notifications/NotificationHelper.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/notifications/NotificationHelper.java
@@ -298,17 +298,17 @@ public final class NotificationHelper im
             }
         }
 
         PendingIntent pi = buildNotificationPendingIntent(message, CLICK_EVENT);
         builder.setContentIntent(pi);
         PendingIntent deletePendingIntent = buildNotificationPendingIntent(message, CLEARED_EVENT);
         builder.setDeleteIntent(deletePendingIntent);
 
-        GeckoAppShell.getNotificationClient().add(id.hashCode(), builder.build());
+        ((NotificationClient) GeckoAppShell.getNotificationListener()).add(id.hashCode(), builder.build());
 
         boolean persistent = message.optBoolean(PERSISTENT_ATTR);
         // We add only not persistent notifications to the list since we want to purge only
         // them when geckoapp is destroyed.
         if (!persistent && !mClearableNotifications.containsKey(id)) {
             mClearableNotifications.put(id, message.toString());
         }
     }
@@ -339,17 +339,17 @@ public final class NotificationHelper im
             Log.i(LOGTAG, "Send " + args.toString());
             GeckoAppShell.notifyObservers("Notification:Event", args.toString());
         } catch (JSONException ex) {
             Log.e(LOGTAG, "sendNotificationWasClosed: error building JSON notification arguments.", ex);
         }
     }
 
     private void closeNotification(String id, String handlerKey, String cookie) {
-        GeckoAppShell.getNotificationClient().remove(id.hashCode());
+        ((NotificationClient) GeckoAppShell.getNotificationListener()).remove(id.hashCode());
         sendNotificationWasClosed(id, handlerKey, cookie);
     }
 
     public void hideNotification(String id, String handlerKey, String cookie) {
         mClearableNotifications.remove(id);
         closeNotification(id, handlerKey, cookie);
     }
 
--- a/widget/android/AndroidAlerts.cpp
+++ b/widget/android/AndroidAlerts.cpp
@@ -79,18 +79,18 @@ AndroidAlerts::ShowPersistentNotificatio
     if (aPersistentData.IsEmpty() && aAlertListener) {
         if (!sAlertInfoMap) {
             sAlertInfoMap = new AlertInfoMap();
         }
         // This will remove any observers already registered for this name.
         sAlertInfoMap->Put(name, new AlertInfo{aAlertListener, cookie});
     }
 
-    java::GeckoAppShell::ShowAlertNotification(
-            imageUrl, title, text, cookie, name, host,
+    java::GeckoAppShell::ShowNotification(
+            name, cookie, title, text, host, imageUrl,
             !aPersistentData.IsEmpty() ? jni::StringParam(aPersistentData)
                                        : jni::StringParam(nullptr));
     return NS_OK;
 }
 
 NS_IMETHODIMP
 AndroidAlerts::CloseAlert(const nsAString& aAlertName,
                           nsIPrincipal* aPrincipal)