Bug 1207714 - Part 4: Add singleton PushService. r?rnewman
MozReview-Commit-ID: CFSINSP7uFp
--- 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);
}
});
}
}
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
@@ -646,16 +646,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: