Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / components / gcm_driver / android / java / src / org / chromium / components / gcm_driver / GCMDriver.java
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.
4
5 package org.chromium.components.gcm_driver;
6
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;
13
14 import com.google.android.gcm.GCMRegistrar;
15
16 import org.chromium.base.CalledByNative;
17 import org.chromium.base.JNINamespace;
18 import org.chromium.base.ThreadUtils;
19 import org.chromium.base.library_loader.ProcessInitException;
20 import org.chromium.content.browser.BrowserStartupController;
21
22 import java.util.ArrayList;
23 import java.util.List;
24
25 /**
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.
29  *
30  * Threading model: all calls to/from C++ happen on the UI thread.
31  */
32 @JNINamespace("gcm")
33 public class GCMDriver {
34     private static final String TAG = "GCMDriver";
35
36     private static final String LAST_GCM_APP_ID_KEY = "last_gcm_app_id";
37
38     // The instance of GCMDriver currently owned by a C++ GCMDriverAndroid, if any.
39     private static GCMDriver sInstance = null;
40
41     private long mNativeGCMDriverAndroid;
42     private final Context mContext;
43
44     private GCMDriver(long nativeGCMDriverAndroid, Context context) {
45         mNativeGCMDriverAndroid = nativeGCMDriverAndroid;
46         mContext = context;
47     }
48
49     /**
50      * Create a GCMDriver object, which is owned by GCMDriverAndroid
51      * on the C++ side.
52      *
53      * @param nativeGCMDriverAndroid The C++ object that owns us.
54      * @param context The app context.
55      */
56     @CalledByNative
57     private static GCMDriver create(long nativeGCMDriverAndroid,
58                                     Context context) {
59         if (sInstance != null) {
60             throw new IllegalStateException("Already instantiated");
61         }
62         sInstance = new GCMDriver(nativeGCMDriverAndroid, context);
63         return sInstance;
64     }
65
66     /**
67      * Called when our C++ counterpart is deleted. Clear the handle to our
68      * native C++ object, ensuring it's never called.
69      */
70     @CalledByNative
71     private void destroy() {
72         assert sInstance == this;
73         sInstance = null;
74         mNativeGCMDriverAndroid = 0;
75     }
76
77     @CalledByNative
78     private void register(final String appId, final String[] senderIds) {
79         setLastAppId(appId);
80         new AsyncTask<Void, Void, String>() {
81             @Override
82             protected String doInBackground(Void... voids) {
83                 try {
84                     GCMRegistrar.checkDevice(mContext);
85                 } catch (UnsupportedOperationException ex) {
86                     return ""; // Indicates failure.
87                 }
88                 // TODO(johnme): Move checkManifest call to a test instead.
89                 GCMRegistrar.checkManifest(mContext);
90                 String existingRegistrationId = GCMRegistrar.getRegistrationId(mContext);
91                 if (existingRegistrationId.equals("")) {
92                     // TODO(johnme): Migrate from GCMRegistrar to GoogleCloudMessaging API, both
93                     // here and elsewhere in Chromium.
94                     // TODO(johnme): Pass appId to GCM.
95                     GCMRegistrar.register(mContext, senderIds);
96                     return null; // Indicates pending result.
97                 } else {
98                     Log.i(TAG, "Re-using existing registration ID");
99                     return existingRegistrationId;
100                 }
101             }
102             @Override
103             protected void onPostExecute(String registrationId) {
104                 if (registrationId == null) {
105                     return; // Wait for {@link #onRegisterFinished} to be called.
106                 }
107                 nativeOnRegisterFinished(mNativeGCMDriverAndroid, appId, registrationId,
108                                          !registrationId.isEmpty());
109             }
110         }.execute();
111     }
112
113     private enum UnregisterResult { SUCCESS, FAILED, PENDING }
114
115     @CalledByNative
116     private void unregister(final String appId) {
117         new AsyncTask<Void, Void, UnregisterResult>() {
118             @Override
119             protected UnregisterResult doInBackground(Void... voids) {
120                 try {
121                     GCMRegistrar.checkDevice(mContext);
122                 } catch (UnsupportedOperationException ex) {
123                     return UnregisterResult.FAILED;
124                 }
125                 if (!GCMRegistrar.isRegistered(mContext)) {
126                     return UnregisterResult.SUCCESS;
127                 }
128                 // TODO(johnme): Pass appId to GCM.
129                 GCMRegistrar.unregister(mContext);
130                 return UnregisterResult.PENDING;
131             }
132
133             @Override
134             protected void onPostExecute(UnregisterResult result) {
135                 if (result == UnregisterResult.PENDING) {
136                     return; // Wait for {@link #onUnregisterFinished} to be called.
137                 }
138                 nativeOnUnregisterFinished(mNativeGCMDriverAndroid, appId,
139                         result == UnregisterResult.SUCCESS);
140             }
141         }.execute();
142     }
143
144     static void onRegisterFinished(String appId, String registrationId) {
145         ThreadUtils.assertOnUiThread();
146         // TODO(johnme): If this gets called, did it definitely succeed?
147         // TODO(johnme): Update registrations cache?
148         if (sInstance != null) {
149             sInstance.nativeOnRegisterFinished(sInstance.mNativeGCMDriverAndroid, getLastAppId(),
150                                                registrationId, true);
151         }
152     }
153
154     static void onUnregisterFinished(String appId) {
155         ThreadUtils.assertOnUiThread();
156         // TODO(johnme): If this gets called, did it definitely succeed?
157         // TODO(johnme): Update registrations cache?
158         if (sInstance != null) {
159             sInstance.nativeOnUnregisterFinished(sInstance.mNativeGCMDriverAndroid, getLastAppId(),
160                                                  true);
161         }
162     }
163
164     static void onMessageReceived(Context context, final String appId, final Bundle extras) {
165         final String pushApiDataKey = "data";
166         if (!extras.containsKey(pushApiDataKey)) {
167             // For now on Android only the Push API uses GCMDriver. To avoid double-handling of
168             // messages already handled in Java by other implementations of MultiplexingGcmListener,
169             // and unnecessarily waking up the browser processes for all existing GCM messages that
170             // are received by Chrome on Android, we currently discard messages unless they are
171             // destined for the Push API.
172             // TODO(johnme): Find a better way of distinguishing messages that should be delivered
173             // to native from messages that have already been delivered to Java, for example by
174             // refactoring other implementations of MultiplexingGcmListener to instead register with
175             // this class, and distinguish them based on appId (which also requires GCM to start
176             // sending us the app IDs).
177             return;
178         }
179
180         // TODO(johnme): Store message and redeliver later if Chrome is killed before delivery.
181         ThreadUtils.assertOnUiThread();
182         launchNativeThen(context, new Runnable() {
183             @Override public void run() {
184                 final String bundleSenderId = "from";
185                 final String bundleCollapseKey = "collapse_key";
186                 final String bundleGcmplex = "com.google.ipc.invalidation.gcmmplex.";
187
188                 String senderId = extras.getString(bundleSenderId);
189                 String collapseKey = extras.getString(bundleCollapseKey);
190
191                 List<String> dataKeysAndValues = new ArrayList<String>();
192                 for (String key : extras.keySet()) {
193                     // TODO(johnme): Check there aren't other keys that we need to exclude.
194                     if (key == bundleSenderId || key == bundleCollapseKey ||
195                             key.startsWith(bundleGcmplex))
196                         continue;
197                     dataKeysAndValues.add(key);
198                     dataKeysAndValues.add(extras.getString(key));
199                 }
200
201                 sInstance.nativeOnMessageReceived(sInstance.mNativeGCMDriverAndroid,
202                         getLastAppId(), senderId, collapseKey,
203                         dataKeysAndValues.toArray(new String[dataKeysAndValues.size()]));
204             }
205         });
206     }
207
208     static void onMessagesDeleted(Context context, final String appId) {
209         // TODO(johnme): Store event and redeliver later if Chrome is killed before delivery.
210         ThreadUtils.assertOnUiThread();
211         launchNativeThen(context, new Runnable() {
212             @Override public void run() {
213                 sInstance.nativeOnMessagesDeleted(sInstance.mNativeGCMDriverAndroid,
214                         getLastAppId());
215             }
216         });
217     }
218
219     private native void nativeOnRegisterFinished(long nativeGCMDriverAndroid, String appId,
220             String registrationId, boolean success);
221     private native void nativeOnUnregisterFinished(long nativeGCMDriverAndroid, String appId,
222             boolean success);
223     private native void nativeOnMessageReceived(long nativeGCMDriverAndroid, String appId,
224             String senderId, String collapseKey, String[] dataKeysAndValues);
225     private native void nativeOnMessagesDeleted(long nativeGCMDriverAndroid, String appId);
226
227     // TODO(johnme): This and setLastAppId are just temporary (crbug.com/350383).
228     private static String getLastAppId() {
229         SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(
230                 sInstance.mContext);
231         return settings.getString(LAST_GCM_APP_ID_KEY, "push#unknown_app_id#0");
232     }
233
234     private static void setLastAppId(String appId) {
235         SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(
236                 sInstance.mContext);
237         SharedPreferences.Editor editor = settings.edit();
238         editor.putString(LAST_GCM_APP_ID_KEY, appId);
239         editor.commit();
240     }
241
242     private static void launchNativeThen(Context context, Runnable task) {
243         if (sInstance != null) {
244             task.run();
245             return;
246         }
247
248         // TODO(johnme): Call ChromeMobileApplication.initCommandLine(context) or
249         // ChromeShellApplication.initCommandLine() as appropriate.
250
251         try {
252             BrowserStartupController.get(context).startBrowserProcessesSync(false);
253             if (sInstance != null) {
254                 task.run();
255             } else {
256                 Log.e(TAG, "Started browser process, but failed to instantiate GCMDriver.");
257             }
258         } catch (ProcessInitException e) {
259             Log.e(TAG, "Failed to start browser process.", e);
260             System.exit(-1);
261         }
262
263         // TODO(johnme): Now we should probably exit?
264     }
265 }