Bug 1075476 - Removing download notification restarts Firefox r?sebastian draft
authorJonathan Almeida (:jonalmeida) <jonalmeida942@gmail.com>
Wed, 25 May 2016 14:18:10 -0700
changeset 380933 86ce0a5fe101ba2ff46c947fb449da5f0513d0a8
parent 380903 a7e793ef71a31643eddd1bf7cb7a80b580cb8284
child 523842 a3593f7ce13747bff622f3bca363b239a20a26c2
push id21349
push userjonalmeida942@gmail.com
push dateThu, 23 Jun 2016 16:53:49 +0000
reviewerssebastian
bugs1075476
milestone50.0a1
Bug 1075476 - Removing download notification restarts Firefox r?sebastian MozReview-Commit-ID: 35pn1nntcsV
mobile/android/base/AndroidManifest.xml.in
mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
mobile/android/base/java/org/mozilla/gecko/NotificationHelper.java
mobile/android/base/java/org/mozilla/gecko/NotificationReceiver.java
mobile/android/base/moz.build
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -112,23 +112,16 @@
                 <action android:name="org.mozilla.gecko.ACTION_ALERT_CALLBACK" />
             </intent-filter>
 
             <intent-filter>
                 <action android:name="org.mozilla.gecko.GUEST_SESSION_INPROGRESS" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
 
-            <!-- Notification API V2 -->
-            <intent-filter>
-                <action android:name="@ANDROID_PACKAGE_NAME@.helperBroadcastAction" />
-                <data android:scheme="moz-notification" />
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-
             <intent-filter>
                 <action android:name="org.mozilla.gecko.UPDATE"/>
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
 
             <intent-filter>
                 <action android:name="android.intent.action.WEB_SEARCH" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -242,16 +235,27 @@
             android:name="org.mozilla.gecko.notifications.WhatsNewReceiver"
             android:exported="false">
             <intent-filter>
                 <action android:name="android.intent.action.PACKAGE_REPLACED" />
                 <data android:scheme="package" android:path="org.mozilla.gecko" />
             </intent-filter>
         </receiver>
 
+        <receiver
+            android:name="org.mozilla.gecko.NotificationReceiver"
+            android:exported="false">
+            <!-- Notification API V2 -->
+            <intent-filter>
+                <action android:name="@ANDROID_PACKAGE_NAME@.helperBroadcastAction" />
+                <data android:scheme="moz-notification" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </receiver>
+
 #include ../services/manifests/FxAccountAndroidManifest_activities.xml.in
 #ifdef MOZ_ANDROID_SEARCH_ACTIVITY
 #include ../search/manifests/SearchAndroidManifest_activities.xml.in
 #endif
 
 #if MOZ_CRASHREPORTER
   <activity android:name="org.mozilla.gecko.CrashReporter"
             android:process="@ANDROID_PACKAGE_NAME@.CrashReporter"
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -1625,18 +1625,16 @@ public abstract class GeckoApp
             if (GeckoThread.isRunning()) {
                 geckoConnected();
                 GeckoAppShell.notifyObservers("Viewport:Flush", null);
             }
         }
 
         if (ACTION_ALERT_CALLBACK.equals(action)) {
             processAlertCallback(intent);
-        } else if (NotificationHelper.HELPER_BROADCAST_ACTION.equals(action)) {
-            NotificationHelper.getInstance(getApplicationContext()).handleNotificationIntent(intent);
         }
     }
 
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
     @Override
     public void onGlobalLayout() {
         if (Versions.preJB) {
             mMainLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
--- a/mobile/android/base/java/org/mozilla/gecko/NotificationHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/NotificationHelper.java
@@ -11,16 +11,17 @@ import java.util.Iterator;
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.mozglue.SafeIntent;
 import org.mozilla.gecko.util.GeckoEventListener;
 
 import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.support.v4.app.NotificationCompat;
 import android.util.Log;
 
 public final class NotificationHelper implements GeckoEventListener {
@@ -40,31 +41,33 @@ public final class NotificationHelper im
     private static final String PROGRESS_VALUE_ATTR = "progress_value";
     private static final String PROGRESS_MAX_ATTR = "progress_max";
     private static final String PROGRESS_INDETERMINATE_ATTR = "progress_indeterminate";
     private static final String LIGHT_ATTR = "light";
     private static final String ONGOING_ATTR = "ongoing";
     private static final String WHEN_ATTR = "when";
     private static final String PRIORITY_ATTR = "priority";
     private static final String LARGE_ICON_ATTR = "largeIcon";
-    private static final String EVENT_TYPE_ATTR = "eventType";
     private static final String ACTIONS_ATTR = "actions";
     private static final String ACTION_ID_ATTR = "buttonId";
     private static final String ACTION_TITLE_ATTR = "title";
     private static final String ACTION_ICON_ATTR = "icon";
     private static final String PERSISTENT_ATTR = "persistent";
     private static final String HANDLER_ATTR = "handlerKey";
     private static final String COOKIE_ATTR = "cookie";
+    static final String EVENT_TYPE_ATTR = "eventType";
 
     private static final String NOTIFICATION_SCHEME = "moz-notification";
 
     private static final String BUTTON_EVENT = "notification-button-clicked";
     private static final String CLICK_EVENT = "notification-clicked";
-    private static final String CLEARED_EVENT = "notification-cleared";
     private static final String CLOSED_EVENT = "notification-closed";
+    static final String CLEARED_EVENT = "notification-cleared";
+
+    static final String ORIGINAL_EXTRA_COMPONENT = "originalComponent";
 
     private final Context mContext;
 
     // Holds a list of notifications that should be cleared if the Fennec Activity is shut down.
     // Will not include ongoing or persistent notifications that are tied to Gecko's lifecycle.
     private HashMap<String, String> mClearableNotifications;
 
     private boolean mInitialized;
@@ -102,69 +105,60 @@ public final class NotificationHelper im
             hideNotification(message);
         }
     }
 
     public boolean isHelperIntent(Intent i) {
         return i.getBooleanExtra(HELPER_NOTIFICATION, false);
     }
 
-    public void handleNotificationIntent(SafeIntent i) {
-        final Uri data = i.getData();
-        if (data == null) {
-            Log.e(LOGTAG, "handleNotificationEvent: empty data");
-            return;
-        }
-        final String id = data.getQueryParameter(ID_ATTR);
-        final String notificationType = data.getQueryParameter(EVENT_TYPE_ATTR);
-        if (id == null || notificationType == null) {
-            Log.e(LOGTAG, "handleNotificationEvent: invalid intent parameters");
-            return;
-        }
+    public static void getArgsAndSendNotificationIntent(SafeIntent intent) {
+        final JSONObject args = new JSONObject();
+        final Uri data = intent.getData();
 
-        // In case the user swiped out the notification, we empty the id set.
-        if (CLEARED_EVENT.equals(notificationType)) {
-            mClearableNotifications.remove(id);
-            // If Gecko isn't running, we throw away events where the notification was cancelled.
-            // i.e. Don't bug the user if they're just closing a bunch of notifications.
-            if (!GeckoThread.isRunning()) {
-                return;
-            }
-        }
-
-        JSONObject args = new JSONObject();
-
-        // The handler and cookie parameters are optional.
-        final String handler = data.getQueryParameter(HANDLER_ATTR);
-        final String cookie = i.getStringExtra(COOKIE_ATTR);
+        final String notificationType = data.getQueryParameter(EVENT_TYPE_ATTR);
 
         try {
-            args.put(ID_ATTR, id);
+            args.put(ID_ATTR, data.getQueryParameter(ID_ATTR));
             args.put(EVENT_TYPE_ATTR, notificationType);
-            args.put(HANDLER_ATTR, handler);
-            args.put(COOKIE_ATTR, cookie);
+            args.put(HANDLER_ATTR, data.getQueryParameter(HANDLER_ATTR));
+            args.put(COOKIE_ATTR, data.getQueryParameter(COOKIE_ATTR));
 
             if (BUTTON_EVENT.equals(notificationType)) {
                 final String actionName = data.getQueryParameter(ACTION_ID_ATTR);
                 args.put(ACTION_ID_ATTR, actionName);
             }
 
             Log.i(LOGTAG, "Send " + args.toString());
             GeckoAppShell.notifyObservers("Notification:Event", args.toString());
         } catch (JSONException e) {
             Log.e(LOGTAG, "Error building JSON notification arguments.", e);
         }
+    }
+
+    public void handleNotificationIntent(SafeIntent i) {
+        final Uri data = i.getData();
+        final String notificationType = data.getQueryParameter(EVENT_TYPE_ATTR);
+        final String id = data.getQueryParameter(ID_ATTR);
+        if (id == null || notificationType == null) {
+            Log.e(LOGTAG, "handleNotificationEvent: invalid intent parameters");
+            return;
+        }
+
+        getArgsAndSendNotificationIntent(i);
 
         // If the notification was clicked, we are closing it. This must be executed after
         // sending the event to js side because when the notification is canceled no event can be
         // handled.
         if (CLICK_EVENT.equals(notificationType) && !i.getBooleanExtra(ONGOING_ATTR, false)) {
+            // The handler and cookie parameters are optional.
+            final String handler = data.getQueryParameter(HANDLER_ATTR);
+            final String cookie = i.getStringExtra(COOKIE_ATTR);
             hideNotification(id, handler, cookie);
         }
-
     }
 
     private Uri.Builder getNotificationBuilder(JSONObject message, String type) {
         Uri.Builder b = new Uri.Builder();
         b.scheme(NOTIFICATION_SCHEME).appendQueryParameter(EVENT_TYPE_ATTR, type);
 
         try {
             final String id = message.getString(ID_ATTR);
@@ -187,25 +181,28 @@ public final class NotificationHelper im
         Intent notificationIntent = new Intent(HELPER_BROADCAST_ACTION);
         final boolean ongoing = message.optBoolean(ONGOING_ATTR);
         notificationIntent.putExtra(ONGOING_ATTR, ongoing);
 
         final Uri dataUri = builder.build();
         notificationIntent.setData(dataUri);
         notificationIntent.putExtra(HELPER_NOTIFICATION, true);
         notificationIntent.putExtra(COOKIE_ATTR, message.optString(COOKIE_ATTR));
-        notificationIntent.setClass(mContext, GeckoAppShell.getGeckoInterface().getActivity().getClass());
+
+        // All intents get routed through the notificationReceiver. That lets us bail if we don't want to start Gecko
+        final ComponentName name = new ComponentName(mContext, GeckoAppShell.getGeckoInterface().getActivity().getClass());
+        notificationIntent.putExtra(ORIGINAL_EXTRA_COMPONENT, name);
+
         return notificationIntent;
     }
 
     private PendingIntent buildNotificationPendingIntent(JSONObject message, String type) {
         Uri.Builder builder = getNotificationBuilder(message, type);
         final Intent notificationIntent = buildNotificationIntent(message, builder);
-        PendingIntent pi = PendingIntent.getActivity(mContext, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
-        return pi;
+        return PendingIntent.getBroadcast(mContext, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
     }
 
     private PendingIntent buildButtonClickPendingIntent(JSONObject message, JSONObject action) {
         Uri.Builder builder = getNotificationBuilder(message, BUTTON_EVENT);
         try {
             // Action name must be in query uri, otherwise buttons pending intents
             // would be collapsed.
             if (action.has(ACTION_ID_ATTR)) {
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/NotificationReceiver.java
@@ -0,0 +1,56 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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 org.mozilla.gecko.mozglue.SafeIntent;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.util.Log;
+
+/**
+ *  Broadcast receiver for Notifications. Will forward them to GeckoApp (and start Gecko) if they're clicked.
+ *  If they're being dismissed, it will not start Gecko, but may forward them to JS if Gecko is running.
+ *  This is also the only entry point for notification intents.
+ */
+public class NotificationReceiver extends BroadcastReceiver {
+    private static final String LOGTAG = "Gecko" + NotificationReceiver.class.getSimpleName();
+
+    public void onReceive(Context context, Intent intent) {
+        final Uri data = intent.getData();
+        if (data == null) {
+            Log.e(LOGTAG, "handleNotificationEvent: empty data");
+            return;
+        }
+
+        final String notificationType = data.getQueryParameter(NotificationHelper.EVENT_TYPE_ATTR);
+        if (notificationType == null) {
+            return;
+        }
+
+        // In case the user swiped out the notification, we empty the id set.
+        if (NotificationHelper.CLEARED_EVENT.equals(notificationType)) {
+            // If Gecko isn't running, we throw away events where the notification was cancelled.
+            // i.e. Don't bug the user if they're just closing a bunch of notifications.
+            if (GeckoThread.isRunning()) {
+                NotificationHelper.getArgsAndSendNotificationIntent(new SafeIntent(intent));
+            }
+            return;
+        }
+
+        forwardMessageToActivity(intent, context);
+    }
+
+    private void forwardMessageToActivity(final Intent intent, final Context context) {
+        final ComponentName name = intent.getExtras().getParcelable(NotificationHelper.ORIGINAL_EXTRA_COMPONENT);
+        intent.setComponent(name);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        context.startActivity(intent);
+    }
+}
\ No newline at end of file
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -474,16 +474,17 @@ gbjar.sources += ['java/org/mozilla/geck
     'menu/MenuItemDefault.java',
     'menu/MenuItemSwitcherLayout.java',
     'menu/MenuPanel.java',
     'menu/MenuPopup.java',
     'MotionEventInterceptor.java',
     'NotificationClient.java',
     'NotificationHandler.java',
     'NotificationHelper.java',
+    'NotificationReceiver.java',
     'notifications/WhatsNewReceiver.java',
     'NotificationService.java',
     'NSSBridge.java',
     'OrderedBroadcastHelper.java',
     'overlays/OverlayConstants.java',
     'overlays/service/OverlayActionService.java',
     'overlays/service/ShareData.java',
     'overlays/service/sharemethods/AddBookmark.java',