Upstream version 11.40.277.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 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;
20
21 import java.io.IOException;
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                 // TODO(johnme): Should check if GMS is installed on the device first. Ditto below.
84                 try {
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);
91                     return "";
92                 }
93             }
94             @Override
95             protected void onPostExecute(String registrationId) {
96                 nativeOnRegisterFinished(mNativeGCMDriverAndroid, appId, registrationId,
97                                          !registrationId.isEmpty());
98             }
99         }.execute();
100     }
101
102     @CalledByNative
103     private void unregister(final String appId) {
104         new AsyncTask<Void, Void, Boolean>() {
105             @Override
106             protected Boolean doInBackground(Void... voids) {
107                 try {
108                     String subtype = appId;
109                     GoogleCloudMessagingV2 gcm = new GoogleCloudMessagingV2(mContext);
110                     gcm.unregister(subtype);
111                     return true;
112                 } catch (IOException ex) {
113                     Log.w(TAG, "GCMv2 unregistration failed for " + appId, ex);
114                     return false;
115                 }
116             }
117
118             @Override
119             protected void onPostExecute(Boolean success) {
120                 nativeOnUnregisterFinished(mNativeGCMDriverAndroid, appId, success);
121             }
122         }.execute();
123     }
124
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.";
134
135                 String senderId = extras.getString(bundleSenderId);
136                 String collapseKey = extras.getString(bundleCollapseKey);
137
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))
143                         continue;
144                     dataKeysAndValues.add(key);
145                     dataKeysAndValues.add(extras.getString(key));
146                 }
147
148                 String guessedAppId = GCMListener.UNKNOWN_APP_ID.equals(appId) ? getLastAppId()
149                                                                                : appId;
150                 sInstance.nativeOnMessageReceived(sInstance.mNativeGCMDriverAndroid,
151                         guessedAppId, senderId, collapseKey,
152                         dataKeysAndValues.toArray(new String[dataKeysAndValues.size()]));
153             }
154         });
155     }
156
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()
163                                                                                : appId;
164                 sInstance.nativeOnMessagesDeleted(sInstance.mNativeGCMDriverAndroid, guessedAppId);
165             }
166         });
167     }
168
169     private native void nativeOnRegisterFinished(long nativeGCMDriverAndroid, String appId,
170             String registrationId, boolean success);
171     private native void nativeOnUnregisterFinished(long nativeGCMDriverAndroid, String appId,
172             boolean success);
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);
176
177     // TODO(johnme): This and setLastAppId are just temporary (crbug.com/350383).
178     private static String getLastAppId() {
179         SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(
180                 sInstance.mContext);
181         return settings.getString(LAST_GCM_APP_ID_KEY, "push#unknown_app_id#0");
182     }
183
184     private static void setLastAppId(String appId) {
185         SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(
186                 sInstance.mContext);
187         SharedPreferences.Editor editor = settings.edit();
188         editor.putString(LAST_GCM_APP_ID_KEY, appId);
189         editor.commit();
190     }
191
192     private static void launchNativeThen(Context context, Runnable task) {
193         if (sInstance != null) {
194             task.run();
195             return;
196         }
197
198         ContentApplication.initCommandLine(context);
199
200         try {
201             BrowserStartupController.get(context).startBrowserProcessesSync(false);
202             if (sInstance != null) {
203                 task.run();
204             } else {
205                 Log.e(TAG, "Started browser process, but failed to instantiate GCMDriver.");
206             }
207         } catch (ProcessInitException e) {
208             Log.e(TAG, "Failed to start browser process.", e);
209             System.exit(-1);
210         }
211
212         // TODO(johnme): Now we should probably exit?
213     }
214 }