Set target to android 18
[fonbot.git] / lib / com / google / android / gcm / GCMRegistrar.java
CommitLineData
8dfb76c9
MG
1/*
2 * Copyright 2012 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.android.gcm;
18
19import android.app.PendingIntent;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.content.SharedPreferences;
24import android.content.SharedPreferences.Editor;
25import android.content.pm.PackageInfo;
26import android.content.pm.PackageManager;
27import android.content.pm.PackageManager.NameNotFoundException;
28import android.os.Build;
29import android.util.Log;
30
31/**
32 * Utilities for device registration.
33 * <p>
34 * <strong>Note:</strong> this class uses a private {@link SharedPreferences}
35 * object to keep track of the registration token.
36 */
37public final class GCMRegistrar {
38
39 /**
40 * Default lifespan (7 days) of the {@link #isRegisteredOnServer(Context)}
41 * flag until it is considered expired.
42 */
43 // NOTE: cannot use TimeUnit.DAYS because it's not available on API Level 8
44 public static final long DEFAULT_ON_SERVER_LIFESPAN_MS =
45 1000 * 3600 * 24 * 7;
46
47 private static final String TAG = "GCMRegistrar";
48 private static final String BACKOFF_MS = "backoff_ms";
49 private static final String GSF_PACKAGE = "com.google.android.gsf";
50 private static final String PREFERENCES = "com.google.android.gcm";
51 private static final int DEFAULT_BACKOFF_MS = 3000;
52 private static final String PROPERTY_REG_ID = "regId";
53 private static final String PROPERTY_APP_VERSION = "appVersion";
54 /**
55 * {@link GCMBroadcastReceiver} instance used to handle the retry intent.
56 *
57 * <p>
58 * This instance cannot be the same as the one defined in the manifest
59 * because it needs a different permission.
60 */
61 private static GCMBroadcastReceiver sRetryReceiver;
62
63 private static String sRetryReceiverClassName;
64
65 /**
66 * Checks if the device has the proper dependencies installed.
67 * <p>
68 * This method should be called when the application starts to verify that
69 * the device supports GCM.
70 *
71 * @param context application context.
72 * @throws UnsupportedOperationException if the device does not support GCM.
73 */
74 public static void checkDevice(final Context context) {
75 final int version = Build.VERSION.SDK_INT;
76 if (version < 8) {
77 throw new UnsupportedOperationException("Device must be at least " +
78 "API Level 8 (instead of " + version + ")");
79 }
80 final PackageManager packageManager = context.getPackageManager();
81 try {
82 packageManager.getPackageInfo(GSF_PACKAGE, 0);
83 } catch (NameNotFoundException e) {
84 throw new UnsupportedOperationException(
85 "Device does not have package " + GSF_PACKAGE);
86 }
87 }
88
89 /**
90 * Initiate messaging registration for the current application.
91 * <p>
92 * The result will be returned as an
93 * {@link GCMConstants#INTENT_FROM_GCM_REGISTRATION_CALLBACK} intent with
94 * either a {@link GCMConstants#EXTRA_REGISTRATION_ID} or
95 * {@link GCMConstants#EXTRA_ERROR}.
96 *
97 * @param context application context.
98 * @param senderIds Google Project ID of the accounts authorized to send
99 * messages to this application.
100 * @throws IllegalStateException if device does not have all GCM
101 * dependencies installed.
102 */
103 public static void register(Context context, String... senderIds) {
104 GCMRegistrar.resetBackoff(context);
105 internalRegister(context, senderIds);
106 }
107
108 static void internalRegister(Context context, String... senderIds) {
109 String flatSenderIds = getFlatSenderIds(senderIds);
110 Log.v(TAG, "Registering app " + context.getPackageName() +
111 " of senders " + flatSenderIds);
112 Intent intent = new Intent(GCMConstants.INTENT_TO_GCM_REGISTRATION);
113 intent.setPackage(GSF_PACKAGE);
114 intent.putExtra(GCMConstants.EXTRA_APPLICATION_PENDING_INTENT,
115 PendingIntent.getBroadcast(context, 0, new Intent(), 0));
116 intent.putExtra(GCMConstants.EXTRA_SENDER, flatSenderIds);
117 context.startService(intent);
118 }
119
120 static String getFlatSenderIds(String... senderIds) {
121 if (senderIds == null || senderIds.length == 0) {
122 throw new IllegalArgumentException("No senderIds");
123 }
124 StringBuilder builder = new StringBuilder(senderIds[0]);
125 for (int i = 1; i < senderIds.length; i++) {
126 builder.append(',').append(senderIds[i]);
127 }
128 return builder.toString();
129 }
130
131 /**
132 * Clear internal resources.
133 *
134 * <p>
135 * This method should be called by the main activity's {@code onDestroy()}
136 * method.
137 */
138 public static synchronized void onDestroy(Context context) {
139 if (sRetryReceiver != null) {
140 Log.v(TAG, "Unregistering receiver");
141 context.unregisterReceiver(sRetryReceiver);
142 sRetryReceiver = null;
143 }
144 }
145
146 static void internalUnregister(Context context) {
147 Log.v(TAG, "Unregistering app " + context.getPackageName());
148 Intent intent = new Intent(GCMConstants.INTENT_TO_GCM_UNREGISTRATION);
149 intent.setPackage(GSF_PACKAGE);
150 intent.putExtra(GCMConstants.EXTRA_APPLICATION_PENDING_INTENT,
151 PendingIntent.getBroadcast(context, 0, new Intent(), 0));
152 context.startService(intent);
153 }
154
155 /**
156 * Lazy initializes the {@link GCMBroadcastReceiver} instance.
157 */
158 static synchronized void setRetryBroadcastReceiver(Context context) {
159 if (sRetryReceiver == null) {
160 if (sRetryReceiverClassName == null) {
161 // should never happen
162 Log.e(TAG, "internal error: retry receiver class not set yet");
163 sRetryReceiver = new GCMBroadcastReceiver();
164 } else {
165 Class<?> clazz;
166 try {
167 clazz = Class.forName(sRetryReceiverClassName);
168 sRetryReceiver = (GCMBroadcastReceiver) clazz.newInstance();
169 } catch (Exception e) {
170 Log.e(TAG, "Could not create instance of " +
171 sRetryReceiverClassName + ". Using " +
172 GCMBroadcastReceiver.class.getName() +
173 " directly.");
174 sRetryReceiver = new GCMBroadcastReceiver();
175 }
176 }
177 String category = context.getPackageName();
178 IntentFilter filter = new IntentFilter(
179 GCMConstants.INTENT_FROM_GCM_LIBRARY_RETRY);
180 filter.addCategory(category);
181 // must use a permission that is defined on manifest for sure
182 String permission = category + ".permission.C2D_MESSAGE";
183 Log.v(TAG, "Registering receiver");
184 context.registerReceiver(sRetryReceiver, filter, permission, null);
185 }
186 }
187
188 /**
189 * Sets the name of the retry receiver class.
190 */
191 static void setRetryReceiverClassName(String className) {
192 Log.v(TAG, "Setting the name of retry receiver class to " + className);
193 sRetryReceiverClassName = className;
194 }
195
196 /**
197 * Gets the current registration id for application on GCM service.
198 * <p>
199 * If result is empty, the registration has failed.
200 *
201 * @return registration id, or empty string if the registration is not
202 * complete.
203 */
204 public static String getRegistrationId(Context context) {
205 final SharedPreferences prefs = getGCMPreferences(context);
206 String registrationId = prefs.getString(PROPERTY_REG_ID, "");
207 // check if app was updated; if so, it must clear registration id to
208 // avoid a race condition if GCM sends a message
209 int oldVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
210 int newVersion = getAppVersion(context);
211 if (oldVersion != Integer.MIN_VALUE && oldVersion != newVersion) {
212 Log.v(TAG, "App version changed from " + oldVersion + " to " +
213 newVersion + "; resetting registration id");
214 clearRegistrationId(context);
215 registrationId = "";
216 }
217 return registrationId;
218 }
219
220 /**
221 * Checks whether the application was successfully registered on GCM
222 * service.
223 */
224 static boolean isRegistered(Context context) {
225 return getRegistrationId(context).length() > 0;
226 }
227
228 /**
229 * Clears the registration id in the persistence store.
230 *
231 * @param context application's context.
232 * @return old registration id.
233 */
234 static String clearRegistrationId(Context context) {
235 return setRegistrationId(context, "");
236 }
237
238 /**
239 * Sets the registration id in the persistence store.
240 *
241 * @param context application's context.
242 * @param regId registration id
243 */
244 static String setRegistrationId(Context context, String regId) {
245 final SharedPreferences prefs = getGCMPreferences(context);
246 String oldRegistrationId = prefs.getString(PROPERTY_REG_ID, "");
247 int appVersion = getAppVersion(context);
248 Log.v(TAG, "Saving regId on app version " + appVersion);
249 Editor editor = prefs.edit();
250 editor.putString(PROPERTY_REG_ID, regId);
251 editor.putInt(PROPERTY_APP_VERSION, appVersion);
252 editor.commit();
253 return oldRegistrationId;
254 }
255
256 /**
257 * Gets the application version.
258 */
259 private static int getAppVersion(Context context) {
260 try {
261 PackageInfo packageInfo = context.getPackageManager()
262 .getPackageInfo(context.getPackageName(), 0);
263 return packageInfo.versionCode;
264 } catch (NameNotFoundException e) {
265 // should never happen
266 throw new RuntimeException("Coult not get package name: " + e);
267 }
268 }
269
270 /**
271 * Resets the backoff counter.
272 * <p>
273 * This method should be called after a GCM call succeeds.
274 *
275 * @param context application's context.
276 */
277 static void resetBackoff(Context context) {
278 Log.d(TAG, "resetting backoff for " + context.getPackageName());
279 setBackoff(context, DEFAULT_BACKOFF_MS);
280 }
281
282 /**
283 * Gets the current backoff counter.
284 *
285 * @param context application's context.
286 * @return current backoff counter, in milliseconds.
287 */
288 static int getBackoff(Context context) {
289 final SharedPreferences prefs = getGCMPreferences(context);
290 return prefs.getInt(BACKOFF_MS, DEFAULT_BACKOFF_MS);
291 }
292
293 /**
294 * Sets the backoff counter.
295 * <p>
296 * This method should be called after a GCM call fails, passing an
297 * exponential value.
298 *
299 * @param context application's context.
300 * @param backoff new backoff counter, in milliseconds.
301 */
302 static void setBackoff(Context context, int backoff) {
303 final SharedPreferences prefs = getGCMPreferences(context);
304 Editor editor = prefs.edit();
305 editor.putInt(BACKOFF_MS, backoff);
306 editor.commit();
307 }
308
309 private static SharedPreferences getGCMPreferences(Context context) {
310 return context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE);
311 }
312
313 private GCMRegistrar() {
314 throw new UnsupportedOperationException();
315 }
316}
This page took 0.024205 seconds and 4 git commands to generate.