Bug 1207714 - Part 4: Add singleton PushService. r=rnewman
authorNick Alexander <nalexander@mozilla.com>
Wed, 02 Mar 2016 16:44:23 -0800
changeset 322955 0d112a6d618c9730bddd665f65d75eb60873e9b6
parent 322954 15826b103fd0ac87294bc8c0e913f9916aa0283c
child 322956 65955cf5a546876c516aa38ac2486015a41b291f
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrnewman
bugs1207714
milestone47.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 1207714 - Part 4: Add singleton PushService. r=rnewman MozReview-Commit-ID: CFSINSP7uFp
mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
mobile/android/base/java/org/mozilla/gecko/gcm/GcmInstanceIDListenerService.java
mobile/android/base/java/org/mozilla/gecko/gcm/GcmMessageListenerService.java
mobile/android/base/java/org/mozilla/gecko/push/PushManager.java
mobile/android/base/java/org/mozilla/gecko/push/PushService.java
mobile/android/base/moz.build
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
@@ -8,16 +8,17 @@ import org.mozilla.gecko.AdjustConstants
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.LocalBrowserDB;
 import org.mozilla.gecko.dlc.DownloadContentService;
 import org.mozilla.gecko.home.HomePanelsManager;
 import org.mozilla.gecko.lwt.LightweightTheme;
 import org.mozilla.gecko.mdns.MulticastDNSManager;
+import org.mozilla.gecko.push.PushService;
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.app.Application;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.res.Configuration;
@@ -160,16 +161,36 @@ public class GeckoApplication extends Ap
                 return new LocalBrowserDB(profileName);
             }
         });
 
         GeckoService.register();
 
         super.onCreate();
 
+        if (AppConstants.MOZ_ANDROID_GCM) {
+            // TODO: only run in main process.
+            ThreadUtils.postToBackgroundThread(new Runnable() {
+                @Override
+                public void run() {
+                    // It's fine to throw GCM initialization onto a background thread; the registration process requires
+                    // network access, so is naturally asynchronous.  This, of course, races against Gecko page load of
+                    // content requiring GCM-backed services, like Web Push.  There's nothing to be done here.
+                    PushService.createInstance(context);
+
+                    try {
+                        PushService.getInstance().onStartup();
+                    } catch (Exception e) {
+                        Log.e(LOG_TAG, "Got exception during startup; ignoring.", e);
+                        return;
+                    }
+                }
+            });
+        }
+
         if (AppConstants.MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE) {
             DownloadContentService.startStudy(this);
         }
     }
 
     public boolean isApplicationInBackground() {
         return mInBackground;
     }
--- a/mobile/android/base/java/org/mozilla/gecko/gcm/GcmInstanceIDListenerService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/gcm/GcmInstanceIDListenerService.java
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.gcm;
 
 import android.util.Log;
 
 import com.google.android.gms.iid.InstanceIDListenerService;
 
+import org.mozilla.gecko.push.PushService;
 import org.mozilla.gecko.util.ThreadUtils;
 
 /**
  * This service is notified by the on-device Google Play Services library if an
  * in-use token needs to be updated.  We simply pass through to AndroidPushService.
  */
 public class GcmInstanceIDListenerService extends InstanceIDListenerService {
     /**
@@ -22,13 +23,13 @@ public class GcmInstanceIDListenerServic
      * InstanceID provider.
      */
     @Override
     public void onTokenRefresh() {
         Log.d("GeckoPushGCM", "Token refresh request received.  Processing on background thread.");
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
-                // TODO: PushService.getInstance().onRefresh();
+                PushService.getInstance().onRefresh();
             }
         });
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/gcm/GcmMessageListenerService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/gcm/GcmMessageListenerService.java
@@ -5,16 +5,17 @@
 
 package org.mozilla.gecko.gcm;
 
 import android.os.Bundle;
 import android.util.Log;
 
 import com.google.android.gms.gcm.GcmListenerService;
 
+import org.mozilla.gecko.push.PushService;
 import org.mozilla.gecko.util.ThreadUtils;
 
 /**
  * This service actually handles messages directed from the on-device Google
  * Play Services package.  We simply route them to the AndroidPushService.
  */
 public class GcmMessageListenerService extends GcmListenerService {
     /**
@@ -24,13 +25,13 @@ public class GcmMessageListenerService e
      * @param bundle Data bundle containing message data as key/value pairs.
      */
     @Override
     public void onMessageReceived(final String from, final Bundle bundle) {
         Log.d("GeckoPushGCM", "Message received.  Processing on background thread.");
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
-                // PushService.getInstance().onMessageReceived(bundle);
+                PushService.getInstance().onMessageReceived(bundle);
             }
         });
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/push/PushManager.java
+++ b/mobile/android/base/java/org/mozilla/gecko/push/PushManager.java
@@ -66,17 +66,17 @@ public class PushManager {
             }
         }
         return null;
     }
 
     public Map<String, PushSubscription> allSubscriptionsForProfile(String profileName) {
         final PushRegistration registration = state.getRegistration(profileName);
         if (registration == null) {
-            return Collections.EMPTY_MAP;
+            return Collections.emptyMap();
         }
         return Collections.unmodifiableMap(registration.subscriptions);
     }
 
     public PushRegistration registerUserAgent(final @NonNull String profileName, final long now) throws ProfileNeedsConfigurationException, AutopushClientException, PushClient.LocalException, GcmTokenClient.NeedsGooglePlayServicesException, IOException {
         Log.i(LOG_TAG, "Registering user agent for profile named: " + profileName);
         return advanceRegistration(profileName, now);
     }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/push/PushService.java
@@ -0,0 +1,129 @@
+/* -*- 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.push;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.util.Log;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoEvent;
+import org.mozilla.gecko.GeckoProfile;
+import org.mozilla.gecko.GeckoThread;
+import org.mozilla.gecko.gcm.GcmTokenClient;
+import org.mozilla.gecko.push.autopush.AutopushClientException;
+import org.mozilla.gecko.util.BundleEventListener;
+import org.mozilla.gecko.util.EventCallback;
+import org.mozilla.gecko.util.ThreadUtils;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * Class that handles messages used in the Google Cloud Messaging and DOM push API integration.
+ * <p/>
+ * This singleton services Gecko messages from dom/push/PushServiceAndroidGCM.jsm and Google Cloud
+ * Messaging requests.
+ * <p/>
+ * It's worth noting that we allow the DOM push API in restricted profiles.
+ */
+public class PushService {
+    private static final String LOG_TAG = "GeckoPushService";
+
+    public static final String SERVICE_WEBPUSH = "webpush";
+
+    private static PushService sInstance;
+
+    public static synchronized PushService getInstance() {
+        if (sInstance == null) {
+            throw new IllegalStateException("PushService not yet created!");
+        }
+        return sInstance;
+    }
+
+    public static synchronized PushService createInstance(Context context) {
+        if (sInstance != null) {
+            throw new IllegalStateException("PushService already created!");
+        }
+        sInstance = new PushService(context);
+        return sInstance;
+    }
+
+    protected final PushManager pushManager;
+
+    public PushService(Context context) {
+        pushManager = new PushManager(new PushState(context, "GeckoPushState.json"), new GcmTokenClient(context), new PushManager.PushClientFactory() {
+            @Override
+            public PushClient getPushClient(String autopushEndpoint, boolean debug) {
+                return new PushClient(autopushEndpoint);
+            }
+        });
+    }
+
+    public void onStartup() {
+        Log.i(LOG_TAG, "Starting up.");
+        ThreadUtils.assertOnBackgroundThread();
+
+        try {
+            pushManager.startup(System.currentTimeMillis());
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "Got exception during startup; ignoring.", e);
+            return;
+        }
+    }
+
+    public void onRefresh() {
+        Log.i(LOG_TAG, "Google Play Services requested GCM token refresh; invalidating GCM token and running startup again.");
+        ThreadUtils.assertOnBackgroundThread();
+
+        pushManager.invalidateGcmToken();
+        try {
+            pushManager.startup(System.currentTimeMillis());
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "Got exception during startup; ignoring.", e);
+            return;
+        }
+    }
+
+    public void onMessageReceived(final @NonNull Bundle bundle) {
+        Log.i(LOG_TAG, "Google Play Services GCM message received; delivering.");
+        ThreadUtils.assertOnBackgroundThread();
+
+        final String chid = bundle.getString("chid");
+        if (chid == null) {
+            Log.w(LOG_TAG, "No chid found; ignoring message.");
+            return;
+        }
+
+        final PushRegistration registration = pushManager.registrationForSubscription(chid);
+        if (registration == null) {
+            Log.w(LOG_TAG, "Cannot find registration corresponding to subscription for chid: " + chid + "; ignoring message.");
+            return;
+        }
+
+        final PushSubscription subscription = registration.getSubscription(chid);
+        if (subscription == null) {
+            // This should never happen.  There's not much to be done; in the future, perhaps we
+            // could try to drop the remote subscription?
+            Log.e(LOG_TAG, "No subscription found for chid: " + chid + "; ignoring message.");
+            return;
+        }
+
+        Log.i(LOG_TAG, "Message directed to service: " + subscription.service);
+
+        if (SERVICE_WEBPUSH.equals(subscription.service)) {
+            // Nothing yet.
+            Log.i(LOG_TAG, "Message directed to unimplemented service; ignoring: " + subscription.service);
+            return;
+        } else {
+            Log.e(LOG_TAG, "Message directed to unknown service; dropping: " + subscription.service);
+        }
+    }
+}
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -647,16 +647,30 @@ gbjar.extra_jars += [
     CONFIG['ANDROID_SUPPORT_V4_AAR_LIB'],
     CONFIG['ANDROID_SUPPORT_V4_AAR_INTERNAL_LIB'],
     'constants.jar'
 ]
 if CONFIG['MOZ_CRASHREPORTER']:
     gbjar.sources += [ 'java/org/mozilla/gecko/CrashReporter.java' ]
     ANDROID_RES_DIRS += [ 'crashreporter/res' ]
 
+if CONFIG['MOZ_ANDROID_GCM']:
+    gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
+        'gcm/GcmInstanceIDListenerService.java',
+        'gcm/GcmMessageListenerService.java',
+        'gcm/GcmTokenClient.java',
+        'push/Fetched.java',
+        'push/PushClient.java',
+        'push/PushManager.java',
+        'push/PushRegistration.java',
+        'push/PushService.java',
+        'push/PushState.java',
+        'push/PushSubscription.java',
+    ]]
+
 if (CONFIG['MOZ_ANDROID_MAX_SDK_VERSION']):
     max_sdk_version = int(CONFIG['MOZ_ANDROID_MAX_SDK_VERSION'])
 else:
     max_sdk_version = 999
 
 # Only bother to include new tablet code if we're building for tablet-capable
 # OS releases.
 if max_sdk_version >= 11: