Commit dd60bcc8 authored by johnme@chromium.org's avatar johnme@chromium.org

Implement GCMDriver.java using MultiplexingGcmListener

Known caveat:
- Since GCM on Android only supports a single registration per native
  app, only a single web app can register at once, and even then only
  if they pass --disable-sync-gcm-in-order-to-try-push-api on the command
  line which disables Chrome Sync etc from receiving GCM messages.

Depends on https://codereview.chromium.org/316963003/

BUG=350384

Review URL: https://codereview.chromium.org/314293006

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@276795 0039d316-1c4b-4281-b951-d872f2087c98
parent afd97b8a
......@@ -168,6 +168,16 @@
</intent-filter>
</receiver>
<!-- GCMDriver multiplexed GCM receiver -->
<service android:exported="false"
android:name="org.chromium.components.gcm_driver.GCMListener"/>
<receiver android:exported="false"
android:name="org.chromium.components.gcm_driver.GCMListener$Receiver">
<intent-filter>
<action android:name="com.google.ipc.invalidation.gcmmplex.EVENT" />
</intent-filter>
</receiver>
<provider android:name="org.chromium.chrome.browser.ChromeBrowserProvider"
android:authorities="org.chromium.chrome.shell"
android:exported="true" />
......
......@@ -107,6 +107,7 @@
'type': 'none',
'dependencies': [
'../base/base.gyp:base',
'../sync/sync.gyp:sync_java',
],
'variables': {
'java_in_dir': 'gcm_driver/android/java',
......
......@@ -5,7 +5,11 @@
package org.chromium.components.gcm_driver;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import com.google.android.gcm.GCMRegistrar;
import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;
......@@ -15,11 +19,16 @@ import java.util.ArrayList;
import java.util.List;
/**
* An implementation of GCMDriver using Android's Java GCM APIs.
* This class is the Java counterpart to the C++ GCMDriverAndroid class.
* It uses Android's Java GCM APIs to implements GCM registration etc, and
* sends back GCM messages over JNI.
*
* Threading model: all calls to/from C++ happen on the UI thread.
*/
@JNINamespace("gcm")
public final class GCMDriver {
public class GCMDriver {
private static final String TAG = "GCMDriver";
// The instance of GCMDriver currently owned by a C++ GCMDriverAndroid, if any.
private static GCMDriver sInstance = null;
......@@ -41,8 +50,9 @@ public final class GCMDriver {
@CalledByNative
private static GCMDriver create(long nativeGCMDriverAndroid,
Context context) {
if (sInstance != null)
if (sInstance != null) {
throw new IllegalStateException("Already instantiated");
}
sInstance = new GCMDriver(nativeGCMDriverAndroid, context);
return sInstance;
}
......@@ -59,32 +69,91 @@ public final class GCMDriver {
}
@CalledByNative
private void register(String appId, String[] senderIds) {
// TODO(johnme): Actually try to register.
nativeOnRegisterFinished(mNativeGCMDriverAndroid, appId, "", false);
private void register(final String appId, final String[] senderIds) {
new AsyncTask<Void, Void, String>() {
@Override
protected String doInBackground(Void... voids) {
try {
GCMRegistrar.checkDevice(mContext);
} catch (UnsupportedOperationException ex) {
return ""; // Indicates failure.
}
// TODO(johnme): Move checkManifest call to a test instead.
GCMRegistrar.checkManifest(mContext);
String existingRegistrationId = GCMRegistrar.getRegistrationId(mContext);
if (existingRegistrationId.equals("")) {
// TODO(johnme): Migrate from GCMRegistrar to GoogleCloudMessaging API, both
// here and elsewhere in Chromium.
// TODO(johnme): Pass appId to GCM.
GCMRegistrar.register(mContext, senderIds);
return null; // Indicates pending result.
} else {
Log.i(TAG, "Re-using existing registration ID");
return existingRegistrationId;
}
}
@Override
protected void onPostExecute(String registrationId) {
if (registrationId == null) {
return; // Wait for {@link #onRegisterFinished} to be called.
}
nativeOnRegisterFinished(mNativeGCMDriverAndroid, appId, registrationId,
!registrationId.isEmpty());
}
}.execute();
}
private enum UnregisterResult { SUCCESS, FAILED, PENDING }
@CalledByNative
private void unregister(String appId) {
// TODO(johnme): Actually try to unregister.
nativeOnUnregisterFinished(mNativeGCMDriverAndroid, appId, false);
private void unregister(final String appId) {
new AsyncTask<Void, Void, UnregisterResult>() {
@Override
protected UnregisterResult doInBackground(Void... voids) {
try {
GCMRegistrar.checkDevice(mContext);
} catch (UnsupportedOperationException ex) {
return UnregisterResult.FAILED;
}
if (!GCMRegistrar.isRegistered(mContext)) {
return UnregisterResult.SUCCESS;
}
// TODO(johnme): Pass appId to GCM.
GCMRegistrar.unregister(mContext);
return UnregisterResult.PENDING;
}
@Override
protected void onPostExecute(UnregisterResult result) {
if (result == UnregisterResult.PENDING) {
return; // Wait for {@link #onUnregisterFinished} to be called.
}
nativeOnUnregisterFinished(mNativeGCMDriverAndroid, appId,
result == UnregisterResult.SUCCESS);
}
}.execute();
}
public static void onRegistered(String appId, String registrationId) {
static void onRegisterFinished(String appId, String registrationId) {
ThreadUtils.assertOnUiThread();
// TODO(johnme): If this gets called, did it definitely succeed?
// TODO(johnme): Update registrations cache?
if (sInstance != null)
if (sInstance != null) {
sInstance.nativeOnRegisterFinished(sInstance.mNativeGCMDriverAndroid, appId,
registrationId, true);
}
}
public static void onUnregistered(String appId) {
static void onUnregisterFinished(String appId) {
ThreadUtils.assertOnUiThread();
// TODO(johnme): If this gets called, did it definitely succeed?
// TODO(johnme): Update registrations cache?
if (sInstance != null)
if (sInstance != null) {
sInstance.nativeOnUnregisterFinished(sInstance.mNativeGCMDriverAndroid, appId, true);
}
}
public static void onMessageReceived(final String appId, final Bundle extras) {
static void onMessageReceived(final String appId, final Bundle extras) {
// TODO(johnme): Store message and redeliver later if Chrome is killed before delivery.
ThreadUtils.assertOnUiThread();
launchNativeThen(new Runnable() {
......@@ -113,7 +182,7 @@ public final class GCMDriver {
});
}
public static void onMessagesDeleted(final String appId) {
static void onMessagesDeleted(final String appId) {
// TODO(johnme): Store event and redeliver later if Chrome is killed before delivery.
ThreadUtils.assertOnUiThread();
launchNativeThen(new Runnable() {
......
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.components.gcm_driver;
import android.content.Intent;
import com.google.ipc.invalidation.external.client.contrib.MultiplexingGcmListener;
import org.chromium.base.ThreadUtils;
/**
* Receives GCM registration events and messages rebroadcast by MultiplexingGcmListener.
*/
public class GCMListener extends MultiplexingGcmListener.AbstractListener {
/**
* Receiver for broadcasts by the multiplexed GCM service. It forwards them to
* GCMListener.
*
* This class is public so that it can be instantiated by the Android runtime.
*/
public static class Receiver extends MultiplexingGcmListener.AbstractListener.Receiver {
@Override
protected Class<?> getServiceClass() {
return GCMListener.class;
}
}
private static final String TAG = "GCMListener";
public GCMListener() {
super(TAG);
}
@Override
protected void onRegistered(final String registrationId) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override public void run() {
// TODO(johnme): Get app ID.
GCMDriver.onRegisterFinished("unknown-app-id", registrationId);
}
});
}
@Override
protected void onUnregistered(String registrationId) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override public void run() {
// TODO(johnme): Get app ID.
GCMDriver.onUnregisterFinished("unknown-app-id");
}
});
}
@Override
protected void onMessage(final Intent intent) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override public void run() {
// TODO(johnme): Get app ID.
GCMDriver.onMessageReceived("unknown-app-id", intent.getExtras());
}
});
}
@Override
protected void onDeletedMessages(int total) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override public void run() {
// TODO(johnme): Get app ID.
GCMDriver.onMessagesDeleted("unknown-app-id");
}
});
}
}
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.sync;
/**
* Contains all of the command line switches that are specific to the sync/
* portion of Chromium on Android.
*/
public abstract class SyncSwitches {
// It's currently necessary for people wanting to try out the Push API to disable Chrome Sync's
// use of GCM (as Android only grants a single registration ID, and they can't both use it).
// Accordingly, this switch lets you disable Sync's ability to receive GCM messages in order to
// make it possible to try out the Push API. Don't use this in production!
// TODO(johnme): Remove this command line switch once disabling sync is no longer necessary.
public static final String DISABLE_SYNC_GCM_IN_ORDER_TO_TRY_PUSH_API =
"disable-sync-gcm-in-order-to-try-push-api";
// Prevent instantiation.
private SyncSwitches() {}
}
......@@ -13,7 +13,9 @@ import android.util.Log;
import com.google.common.annotations.VisibleForTesting;
import com.google.ipc.invalidation.external.client.contrib.MultiplexingGcmListener;
import org.chromium.base.CommandLine;
import org.chromium.base.ObserverList;
import org.chromium.sync.SyncSwitches;
public class ChromeSigninController {
public interface Listener {
......@@ -114,6 +116,11 @@ public class ChromeSigninController {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... arg0) {
if (CommandLine.getInstance().hasSwitch(
SyncSwitches.DISABLE_SYNC_GCM_IN_ORDER_TO_TRY_PUSH_API)) {
Log.w(TAG, "Sync GCM notifications disabled in order to try Push API!");
return null;
}
try {
String regId = MultiplexingGcmListener.initializeGcm(mApplicationContext);
if (!regId.isEmpty())
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment