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
--- 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