Bug 1252666 - Part 2: Use reflection to start PushService. r?margaret draft
authorNick Alexander <nalexander@mozilla.com>
Mon, 07 Mar 2016 14:37:57 -0800
changeset 338144 7cc3277401d2f80146e0b4d7f17017ba0a2a8f50
parent 338143 c62b567889b3cd5f8b7810b41c97f67a5723e7c7
child 338145 06ba912f3b8d1ca58a414156484cfb2a73b22b1c
push id12443
push usernalexander@mozilla.com
push dateTue, 08 Mar 2016 17:31:25 +0000
reviewersmargaret
bugs1252666
milestone47.0a1
Bug 1252666 - Part 2: Use reflection to start PushService. r?margaret The alternative is to define an interface and two conditional implementations, and then build only one depending on MOZ_ANDROID_GCM. That's what we did for Adjust, and it works; but it's awkward here because the the PushService code is truly part of the browser, and the conditional code is compiled very early (much earlier than the browser). (The Adjust library was built even earlier than the existing conditional code, so this wasn't an issue.) To work around this, we'd want to add conditional code to the main browser, which complicates the build. This is expedient until we get to a proper dependency injection scheme (for example, Dagger). MozReview-Commit-ID: 18usWz8oC3B
mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
mobile/android/base/java/org/mozilla/gecko/push/PushService.java
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
@@ -1,37 +1,36 @@
 /* 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.AdjustConstants;
-import org.mozilla.gecko.AppConstants;
+import android.app.Application;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.squareup.leakcanary.LeakCanary;
+
 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.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;
-import android.os.SystemClock;
-import android.util.Log;
-
-import com.squareup.leakcanary.LeakCanary;
-
 import java.io.File;
+import java.lang.reflect.Method;
 
 public class GeckoApplication extends Application 
     implements ContextGetter {
     private static final String LOG_TAG = "GeckoApplication";
 
     private static volatile GeckoApplication instance;
 
     private boolean mInBackground;
@@ -160,16 +159,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.
+                    try {
+                        final Class<?> clazz = Class.forName("org.mozilla.gecko.push.PushService");
+                        final Method onCreate = clazz.getMethod("onCreate", Context.class);
+                        onCreate.invoke(null, getApplicationContext()); // Method is static.
+                    } 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/push/PushService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/push/PushService.java
@@ -12,16 +12,17 @@ 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.annotation.ReflectionTarget;
 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;
@@ -29,16 +30,17 @@ 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.
  */
+@ReflectionTarget
 public class PushService implements BundleEventListener {
     private static final String LOG_TAG = "GeckoPushService";
 
     public static final String SERVICE_WEBPUSH = "webpush";
 
     private static PushService sInstance;
 
     private static final String[] GECKO_EVENTS = new String[]{
@@ -53,22 +55,25 @@ public class PushService implements Bund
 
     public static synchronized PushService getInstance() {
         if (sInstance == null) {
             throw new IllegalStateException("PushService not yet created!");
         }
         return sInstance;
     }
 
-    public static synchronized PushService createInstance(Context context) {
+    @ReflectionTarget
+    public static synchronized void onCreate(Context context) {
         if (sInstance != null) {
             throw new IllegalStateException("PushService already created!");
         }
         sInstance = new PushService(context);
-        return sInstance;
+
+        sInstance.registerGeckoEventListener();
+        sInstance.onStartup();
     }
 
     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) {
@@ -175,24 +180,24 @@ public class PushService implements Bund
 
             Log.i(LOG_TAG, "Delivering dom/push message to Gecko!");
             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("PushServiceAndroidGCM:ReceivedPushMessage", data.toString()));
         } else {
             Log.e(LOG_TAG, "Message directed to unknown service; dropping: " + subscription.service);
         }
     }
 
-    public static void registerGeckoEventListener() {
+    protected void registerGeckoEventListener() {
         Log.d(LOG_TAG, "Registered Gecko event listener.");
-        EventDispatcher.getInstance().registerBackgroundThreadListener(getInstance(), GECKO_EVENTS);
+        EventDispatcher.getInstance().registerBackgroundThreadListener(this, GECKO_EVENTS);
     }
 
-    public static void unregisterGeckoEventListener() {
+    protected void unregisterGeckoEventListener() {
         Log.d(LOG_TAG, "Unregistered Gecko event listener.");
-        EventDispatcher.getInstance().unregisterBackgroundThreadListener(getInstance(), GECKO_EVENTS);
+        EventDispatcher.getInstance().unregisterBackgroundThreadListener(this, GECKO_EVENTS);
     }
 
     @Override
     public void handleMessage(final String event, final Bundle message, final EventCallback callback) {
         Log.i(LOG_TAG, "Handling event: " + event);
         ThreadUtils.assertOnBackgroundThread();
 
         // We're invoked in response to a Gecko message on a background thread.  We should always