blob: c40d8d6729de18a2c82e86b96d4e78c7a544ef0a [file] [log] [blame]
// 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.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;
import org.chromium.base.ThreadUtils;
import org.chromium.base.library_loader.ProcessInitException;
import org.chromium.content.app.ContentApplication;
import org.chromium.content.browser.BrowserStartupController;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* 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 class GCMDriver {
private static final String TAG = "GCMDriver";
private static final String LAST_GCM_APP_ID_KEY = "last_gcm_app_id";
// The instance of GCMDriver currently owned by a C++ GCMDriverAndroid, if any.
private static GCMDriver sInstance = null;
private long mNativeGCMDriverAndroid;
private final Context mContext;
private GCMDriver(long nativeGCMDriverAndroid, Context context) {
mNativeGCMDriverAndroid = nativeGCMDriverAndroid;
mContext = context;
}
/**
* Create a GCMDriver object, which is owned by GCMDriverAndroid
* on the C++ side.
*
* @param nativeGCMDriverAndroid The C++ object that owns us.
* @param context The app context.
*/
@CalledByNative
private static GCMDriver create(long nativeGCMDriverAndroid,
Context context) {
if (sInstance != null) {
throw new IllegalStateException("Already instantiated");
}
sInstance = new GCMDriver(nativeGCMDriverAndroid, context);
return sInstance;
}
/**
* Called when our C++ counterpart is deleted. Clear the handle to our
* native C++ object, ensuring it's never called.
*/
@CalledByNative
private void destroy() {
assert sInstance == this;
sInstance = null;
mNativeGCMDriverAndroid = 0;
}
@CalledByNative
private void register(final String appId, final String[] senderIds) {
setLastAppId(appId);
new AsyncTask<Void, Void, String>() {
@Override
protected String doInBackground(Void... voids) {
// TODO(johnme): Should check if GMS is installed on the device first. Ditto below.
try {
String subtype = appId;
GoogleCloudMessagingV2 gcm = new GoogleCloudMessagingV2(mContext);
String registrationId = gcm.register(subtype, senderIds);
return registrationId;
} catch (IOException ex) {
Log.w(TAG, "GCMv2 registration failed for " + appId, ex);
return "";
}
}
@Override
protected void onPostExecute(String registrationId) {
nativeOnRegisterFinished(mNativeGCMDriverAndroid, appId, registrationId,
!registrationId.isEmpty());
}
}.execute();
}
@CalledByNative
private void unregister(final String appId) {
new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... voids) {
try {
String subtype = appId;
GoogleCloudMessagingV2 gcm = new GoogleCloudMessagingV2(mContext);
gcm.unregister(subtype);
return true;
} catch (IOException ex) {
Log.w(TAG, "GCMv2 unregistration failed for " + appId, ex);
return false;
}
}
@Override
protected void onPostExecute(Boolean success) {
nativeOnUnregisterFinished(mNativeGCMDriverAndroid, appId, success);
}
}.execute();
}
static void onMessageReceived(Context context, final String appId, final Bundle extras) {
// TODO(johnme): Store message and redeliver later if Chrome is killed before delivery.
ThreadUtils.assertOnUiThread();
launchNativeThen(context, new Runnable() {
@Override public void run() {
final String bundleSubtype = "subtype";
final String bundleSenderId = "from";
final String bundleCollapseKey = "collapse_key";
final String bundleGcmplex = "com.google.ipc.invalidation.gcmmplex.";
String senderId = extras.getString(bundleSenderId);
String collapseKey = extras.getString(bundleCollapseKey);
List<String> dataKeysAndValues = new ArrayList<String>();
for (String key : extras.keySet()) {
// TODO(johnme): Check there aren't other keys that we need to exclude.
if (key.equals(bundleSubtype) || key.equals(bundleSenderId) ||
key.equals(bundleCollapseKey) || key.startsWith(bundleGcmplex))
continue;
dataKeysAndValues.add(key);
dataKeysAndValues.add(extras.getString(key));
}
String guessedAppId = GCMListener.UNKNOWN_APP_ID.equals(appId) ? getLastAppId()
: appId;
sInstance.nativeOnMessageReceived(sInstance.mNativeGCMDriverAndroid,
guessedAppId, senderId, collapseKey,
dataKeysAndValues.toArray(new String[dataKeysAndValues.size()]));
}
});
}
static void onMessagesDeleted(Context context, final String appId) {
// TODO(johnme): Store event and redeliver later if Chrome is killed before delivery.
ThreadUtils.assertOnUiThread();
launchNativeThen(context, new Runnable() {
@Override public void run() {
String guessedAppId = GCMListener.UNKNOWN_APP_ID.equals(appId) ? getLastAppId()
: appId;
sInstance.nativeOnMessagesDeleted(sInstance.mNativeGCMDriverAndroid, guessedAppId);
}
});
}
private native void nativeOnRegisterFinished(long nativeGCMDriverAndroid, String appId,
String registrationId, boolean success);
private native void nativeOnUnregisterFinished(long nativeGCMDriverAndroid, String appId,
boolean success);
private native void nativeOnMessageReceived(long nativeGCMDriverAndroid, String appId,
String senderId, String collapseKey, String[] dataKeysAndValues);
private native void nativeOnMessagesDeleted(long nativeGCMDriverAndroid, String appId);
// TODO(johnme): This and setLastAppId are just temporary (crbug.com/350383).
private static String getLastAppId() {
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(
sInstance.mContext);
return settings.getString(LAST_GCM_APP_ID_KEY, "push#unknown_app_id#0");
}
private static void setLastAppId(String appId) {
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(
sInstance.mContext);
SharedPreferences.Editor editor = settings.edit();
editor.putString(LAST_GCM_APP_ID_KEY, appId);
editor.commit();
}
private static void launchNativeThen(Context context, Runnable task) {
if (sInstance != null) {
task.run();
return;
}
ContentApplication.initCommandLine(context);
try {
BrowserStartupController.get(context).startBrowserProcessesSync(false);
if (sInstance != null) {
task.run();
} else {
Log.e(TAG, "Started browser process, but failed to instantiate GCMDriver.");
}
} catch (ProcessInitException e) {
Log.e(TAG, "Failed to start browser process.", e);
System.exit(-1);
}
// TODO(johnme): Now we should probably exit?
}
}