1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 package org.chromium.components.gcm_driver;
7 import android.content.Context;
8 import android.content.SharedPreferences;
9 import android.os.AsyncTask;
10 import android.os.Bundle;
11 import android.preference.PreferenceManager;
12 import android.util.Log;
14 import org.chromium.base.CalledByNative;
15 import org.chromium.base.JNINamespace;
16 import org.chromium.base.ThreadUtils;
17 import org.chromium.base.library_loader.ProcessInitException;
18 import org.chromium.content.app.ContentApplication;
19 import org.chromium.content.browser.BrowserStartupController;
21 import java.io.IOException;
22 import java.util.ArrayList;
23 import java.util.List;
26 * This class is the Java counterpart to the C++ GCMDriverAndroid class.
27 * It uses Android's Java GCM APIs to implements GCM registration etc, and
28 * sends back GCM messages over JNI.
30 * Threading model: all calls to/from C++ happen on the UI thread.
33 public class GCMDriver {
34 private static final String TAG = "GCMDriver";
36 private static final String LAST_GCM_APP_ID_KEY = "last_gcm_app_id";
38 // The instance of GCMDriver currently owned by a C++ GCMDriverAndroid, if any.
39 private static GCMDriver sInstance = null;
41 private long mNativeGCMDriverAndroid;
42 private final Context mContext;
44 private GCMDriver(long nativeGCMDriverAndroid, Context context) {
45 mNativeGCMDriverAndroid = nativeGCMDriverAndroid;
50 * Create a GCMDriver object, which is owned by GCMDriverAndroid
53 * @param nativeGCMDriverAndroid The C++ object that owns us.
54 * @param context The app context.
57 private static GCMDriver create(long nativeGCMDriverAndroid,
59 if (sInstance != null) {
60 throw new IllegalStateException("Already instantiated");
62 sInstance = new GCMDriver(nativeGCMDriverAndroid, context);
67 * Called when our C++ counterpart is deleted. Clear the handle to our
68 * native C++ object, ensuring it's never called.
71 private void destroy() {
72 assert sInstance == this;
74 mNativeGCMDriverAndroid = 0;
78 private void register(final String appId, final String[] senderIds) {
80 new AsyncTask<Void, Void, String>() {
82 protected String doInBackground(Void... voids) {
83 // TODO(johnme): Should check if GMS is installed on the device first. Ditto below.
85 String subtype = appId;
86 GoogleCloudMessagingV2 gcm = new GoogleCloudMessagingV2(mContext);
87 String registrationId = gcm.register(subtype, senderIds);
88 return registrationId;
89 } catch (IOException ex) {
90 Log.w(TAG, "GCMv2 registration failed for " + appId, ex);
95 protected void onPostExecute(String registrationId) {
96 nativeOnRegisterFinished(mNativeGCMDriverAndroid, appId, registrationId,
97 !registrationId.isEmpty());
103 private void unregister(final String appId) {
104 new AsyncTask<Void, Void, Boolean>() {
106 protected Boolean doInBackground(Void... voids) {
108 String subtype = appId;
109 GoogleCloudMessagingV2 gcm = new GoogleCloudMessagingV2(mContext);
110 gcm.unregister(subtype);
112 } catch (IOException ex) {
113 Log.w(TAG, "GCMv2 unregistration failed for " + appId, ex);
119 protected void onPostExecute(Boolean success) {
120 nativeOnUnregisterFinished(mNativeGCMDriverAndroid, appId, success);
125 static void onMessageReceived(Context context, final String appId, final Bundle extras) {
126 // TODO(johnme): Store message and redeliver later if Chrome is killed before delivery.
127 ThreadUtils.assertOnUiThread();
128 launchNativeThen(context, new Runnable() {
129 @Override public void run() {
130 final String bundleSubtype = "subtype";
131 final String bundleSenderId = "from";
132 final String bundleCollapseKey = "collapse_key";
133 final String bundleGcmplex = "com.google.ipc.invalidation.gcmmplex.";
135 String senderId = extras.getString(bundleSenderId);
136 String collapseKey = extras.getString(bundleCollapseKey);
138 List<String> dataKeysAndValues = new ArrayList<String>();
139 for (String key : extras.keySet()) {
140 // TODO(johnme): Check there aren't other keys that we need to exclude.
141 if (key.equals(bundleSubtype) || key.equals(bundleSenderId) ||
142 key.equals(bundleCollapseKey) || key.startsWith(bundleGcmplex))
144 dataKeysAndValues.add(key);
145 dataKeysAndValues.add(extras.getString(key));
148 String guessedAppId = GCMListener.UNKNOWN_APP_ID.equals(appId) ? getLastAppId()
150 sInstance.nativeOnMessageReceived(sInstance.mNativeGCMDriverAndroid,
151 guessedAppId, senderId, collapseKey,
152 dataKeysAndValues.toArray(new String[dataKeysAndValues.size()]));
157 static void onMessagesDeleted(Context context, final String appId) {
158 // TODO(johnme): Store event and redeliver later if Chrome is killed before delivery.
159 ThreadUtils.assertOnUiThread();
160 launchNativeThen(context, new Runnable() {
161 @Override public void run() {
162 String guessedAppId = GCMListener.UNKNOWN_APP_ID.equals(appId) ? getLastAppId()
164 sInstance.nativeOnMessagesDeleted(sInstance.mNativeGCMDriverAndroid, guessedAppId);
169 private native void nativeOnRegisterFinished(long nativeGCMDriverAndroid, String appId,
170 String registrationId, boolean success);
171 private native void nativeOnUnregisterFinished(long nativeGCMDriverAndroid, String appId,
173 private native void nativeOnMessageReceived(long nativeGCMDriverAndroid, String appId,
174 String senderId, String collapseKey, String[] dataKeysAndValues);
175 private native void nativeOnMessagesDeleted(long nativeGCMDriverAndroid, String appId);
177 // TODO(johnme): This and setLastAppId are just temporary (crbug.com/350383).
178 private static String getLastAppId() {
179 SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(
181 return settings.getString(LAST_GCM_APP_ID_KEY, "push#unknown_app_id#0");
184 private static void setLastAppId(String appId) {
185 SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(
187 SharedPreferences.Editor editor = settings.edit();
188 editor.putString(LAST_GCM_APP_ID_KEY, appId);
192 private static void launchNativeThen(Context context, Runnable task) {
193 if (sInstance != null) {
198 ContentApplication.initCommandLine(context);
201 BrowserStartupController.get(context).startBrowserProcessesSync(false);
202 if (sInstance != null) {
205 Log.e(TAG, "Started browser process, but failed to instantiate GCMDriver.");
207 } catch (ProcessInitException e) {
208 Log.e(TAG, "Failed to start browser process.", e);
212 // TODO(johnme): Now we should probably exit?