Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / cacheinvalidation / src / javaexample / com / google / ipc / invalidation / examples / android2 / ExampleListener.java
1 /*
2  * Copyright 2011 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 package com.google.ipc.invalidation.examples.android2;
17
18 import com.google.ipc.invalidation.examples.android2.ExampleListenerProto.ExampleListenerStateProto.ObjectIdProto;
19 import com.google.ipc.invalidation.external.client.InvalidationClientConfig;
20 import com.google.ipc.invalidation.external.client.InvalidationListener.RegistrationState;
21 import com.google.ipc.invalidation.external.client.contrib.AndroidListener;
22 import com.google.ipc.invalidation.external.client.types.ErrorInfo;
23 import com.google.ipc.invalidation.external.client.types.Invalidation;
24 import com.google.ipc.invalidation.external.client.types.ObjectId;
25 import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
26 import com.google.protobuf.nano.MessageNano;
27
28 import android.accounts.Account;
29 import android.accounts.AccountManager;
30 import android.accounts.AccountManagerFuture;
31 import android.accounts.AuthenticatorException;
32 import android.accounts.OperationCanceledException;
33 import android.app.PendingIntent;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.content.SharedPreferences;
37 import android.content.SharedPreferences.Editor;
38 import android.os.Bundle;
39 import android.util.Base64;
40 import android.util.Log;
41
42 import java.io.IOException;
43 import java.util.ArrayList;
44 import java.util.List;
45 import java.util.Locale;
46
47
48 /**
49  * Implements the service that handles invalidation client events for this application. It maintains
50  * state for all objects tracked by the listener (see {@link ExampleListenerState}). By default, the
51  * listener registers an interest in a small number of objects when started, but it responds to
52  * registration intents from the main activity (see {@link #createRegisterIntent} and
53  * {@link #createUnregisterIntent}) so that registrations can be dynamically managed.
54  * <p>
55  * Many errors cases in this example implementation are handled by logging errors, which is not the
56  * appropriate response in a real application where retries or user notification may be needed.
57  *
58  */
59 public final class ExampleListener extends AndroidListener {
60
61   /** The account type value for Google accounts */
62   private static final String GOOGLE_ACCOUNT_TYPE = "com.google";
63
64   /**
65    * This is the authentication token type that's used for invalidation client communication to the
66    * server. For real applications, it would generally match the authorization type used by the
67    * application.
68    */
69   private static final String AUTH_TYPE = "android";
70
71   /** Name used for shared preferences. */
72   private static final String PREFERENCES_NAME = "example_listener";
73
74   /** Key used for {@link AndroidListener} state in shared preferences. */
75   private static final String ANDROID_LISTENER_STATE_KEY = "android_listener_state";
76
77   /** Key used for {@link ExampleListener} state in shared preferences. */
78   private static final String EXAMPLE_LISTENER_STATE_KEY = "example_listener_state";
79
80   /** The tag used for logging in the listener. */
81   private static final String TAG = "TEA2:EL";
82
83   /** Ticl client configuration. */
84   private static final int CLIENT_TYPE = 4; // Demo client ID.
85   private static final byte[] CLIENT_NAME = "TEA2:eetrofoot".getBytes();
86
87   // Intent constants.
88   private static final String START_INTENT_ACTION = TAG + ":START";
89   private static final String STOP_INTENT_ACTION = TAG + ":STOP";
90   private static final String REGISTER_INTENT_ACTION = TAG + ":REGISTER";
91   private static final String UNREGISTER_INTENT_ACTION = TAG + ":UNREGISTER";
92   private static final String OBJECT_ID_EXTRA = "oid";
93
94   /** Persistent state for the example listener. */
95   private ExampleListenerState exampleListenerState;
96
97   public ExampleListener() {
98     super();
99   }
100
101   @Override
102   public void onCreate() {
103     super.onCreate();
104
105     // Deserialize persistent state.
106     String data = getSharedPreferences().getString(EXAMPLE_LISTENER_STATE_KEY, null);
107     exampleListenerState = ExampleListenerState.deserialize(data);
108   }
109
110   @Override
111   public void onHandleIntent(Intent intent) {
112     if (intent == null) {
113       return;
114     }
115
116     boolean handled = tryHandleRegistrationIntent(intent);
117     handled = handled || tryHandleStartIntent(intent);
118     handled = handled || tryHandleStopIntent(intent);
119     if (!handled) {
120       super.onHandleIntent(intent);
121     }
122   }
123
124   @Override
125   public void informError(ErrorInfo errorInfo) {
126     Log.e(TAG, "informError: " + errorInfo);
127
128     /***********************************************************************************************
129      * YOUR CODE HERE
130      *
131      * Handling of permanent failures is application-specific.
132      **********************************************************************************************/
133   }
134
135   @Override
136   public void ready(byte[] clientId) {
137     Log.i(TAG, "ready()");
138     exampleListenerState.setClientId(clientId);
139     writeExampleListenerState();
140   }
141
142   @Override
143   public void reissueRegistrations(byte[] clientId) {
144     Log.i(TAG, "reissueRegistrations()");
145     register(clientId, exampleListenerState.getInterestingObjects());
146   }
147
148   @Override
149   public void invalidate(Invalidation invalidation, byte[] ackHandle) {
150     Log.i(TAG, "invalidate: " + invalidation);
151
152     exampleListenerState.informInvalidation(invalidation.getObjectId(), invalidation.getVersion(),
153         invalidation.getPayload(), /* isBackground */ false);
154     writeExampleListenerState();
155
156     // Do real work here based upon the invalidation
157
158     acknowledge(ackHandle);
159   }
160
161   @Override
162   public void invalidateUnknownVersion(ObjectId objectId, byte[] ackHandle) {
163     Log.i(TAG, "invalidateUnknownVersion: " + objectId);
164
165     exampleListenerState.informUnknownVersionInvalidation(objectId);
166     writeExampleListenerState();
167
168     // In a real app, the application backend would need to be consulted for object state.
169
170     acknowledge(ackHandle);
171   }
172
173   @Override
174   public void invalidateAll(byte[] ackHandle) {
175     Log.i(TAG, "invalidateAll");
176
177     // Do real work here based upon the invalidation.
178     exampleListenerState.informInvalidateAll();
179     writeExampleListenerState();
180
181     acknowledge(ackHandle);
182   }
183
184
185   @Override
186   public byte[] readState() {
187     Log.i(TAG, "readState");
188     SharedPreferences sharedPreferences = getSharedPreferences();
189     String data = sharedPreferences.getString(ANDROID_LISTENER_STATE_KEY, null);
190     return (data != null) ? Base64.decode(data, Base64.DEFAULT) : null;
191   }
192
193   @Override
194   public void writeState(byte[] data) {
195     Log.i(TAG, "writeState");
196     Editor editor = getSharedPreferences().edit();
197     editor.putString(ANDROID_LISTENER_STATE_KEY, Base64.encodeToString(data, Base64.DEFAULT));
198     if (!editor.commit()) {
199       Log.e(TAG, "failed to write state");  // In a real app, this case would need to handled.
200     }
201   }
202
203   @Override
204   public void requestAuthToken(PendingIntent pendingIntent,
205       String invalidAuthToken) {
206     Log.i(TAG, "requestAuthToken");
207
208     // In response to requestAuthToken, we need to get an auth token and inform the invalidation
209     // client of the result through a call to setAuthToken. In this example, we block until a
210     // result is available. It is also possible to invoke setAuthToken in a callback or when
211     // handling an intent.
212     AccountManager accountManager = AccountManager.get(getApplicationContext());
213
214     // Invalidate the old token if necessary.
215     if (invalidAuthToken != null) {
216       accountManager.invalidateAuthToken(GOOGLE_ACCOUNT_TYPE, invalidAuthToken);
217     }
218
219     // Choose an (arbitrary in this example) account for which to retrieve an authentication token.
220     Account account = getAccount(accountManager);
221
222     try {
223       // There are three possible outcomes of the call to getAuthToken:
224       //
225       // 1. Authentication failure (null result).
226       // 2. The user needs to sign in or give permission for the account. In such cases, the result
227       //    includes an intent that can be started to retrieve credentials from the user.
228       // 3. The response includes the auth token, in which case we can inform the invalidation
229       //    client.
230       //
231       // In the first case, we simply log and return. The response to such errors is application-
232       // specific. For instance, the application may prompt the user to choose another account.
233       //
234       // In the second case, we start an intent to ask for user credentials so that they are
235       // available to the application if there is a future request. An application should listen for
236       // the LOGIN_ACCOUNTS_CHANGED_ACTION broadcast intent to trigger a response to the
237       // invalidation client after the user has responded. Otherwise, it may take several minutes
238       // for the invalidation client to start.
239       //
240       // In the third case, success!, we pass the authorization token and type to the invalidation
241       // client using the setAuthToken method.
242       AccountManagerFuture<Bundle> future = accountManager.getAuthToken(account, AUTH_TYPE,
243           new Bundle(), false, null, null);
244       Bundle result = future.getResult();
245       if (result == null) {
246         // If the result is null, it means that authentication was not possible.
247         Log.w(TAG, "Auth token - getAuthToken returned null");
248         return;
249       }
250       if (result.containsKey(AccountManager.KEY_INTENT)) {
251         Log.i(TAG, "Starting intent to get auth credentials");
252
253         // Need to start intent to get credentials.
254         Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
255         int flags = intent.getFlags();
256         flags |= Intent.FLAG_ACTIVITY_NEW_TASK;
257         intent.setFlags(flags);
258         getApplicationContext().startActivity(intent);
259         return;
260       }
261
262       Log.i(TAG, "Passing auth token to invalidation client");
263       String authToken = result.getString(AccountManager.KEY_AUTHTOKEN);
264       setAuthToken(getApplicationContext(), pendingIntent, authToken, AUTH_TYPE);
265     } catch (OperationCanceledException e) {
266       Log.w(TAG, "Auth token - operation cancelled", e);
267     } catch (AuthenticatorException e) {
268       Log.w(TAG, "Auth token - authenticator exception", e);
269     } catch (IOException e) {
270       Log.w(TAG, "Auth token - IO exception", e);
271     }
272   }
273
274   /** Returns any Google account enabled on the device. */
275   private static Account getAccount(AccountManager accountManager) {
276     if (accountManager == null) {
277       throw new NullPointerException();
278     }
279     for (Account acct : accountManager.getAccounts()) {
280       if (GOOGLE_ACCOUNT_TYPE.equals(acct.type)) {
281         return acct;
282       }
283     }
284     throw new RuntimeException("No google account enabled.");
285   }
286
287   @Override
288   public void informRegistrationFailure(byte[] clientId, ObjectId objectId, boolean isTransient,
289       String errorMessage) {
290     Log.e(TAG, "Registration failure!");
291     if (isTransient) {
292       // Retry immediately on transient failures. The base AndroidListener will handle exponential
293       // backoff if there are repeated failures.
294       List<ObjectId> objectIds = new ArrayList<ObjectId>();
295       objectIds.add(objectId);
296       if (exampleListenerState.isInterestedInObject(objectId)) {
297         Log.i(TAG, "Retrying registration of " + objectId);
298         register(clientId, objectIds);
299       } else {
300         Log.i(TAG, "Retrying unregistration of " + objectId);
301         unregister(clientId, objectIds);
302       }
303     }
304   }
305
306   @Override
307   public void informRegistrationStatus(byte[] clientId, ObjectId objectId,
308       RegistrationState regState) {
309     Log.i(TAG, "informRegistrationStatus");
310
311     List<ObjectId> objectIds = new ArrayList<ObjectId>();
312     objectIds.add(objectId);
313     if (regState == RegistrationState.REGISTERED) {
314       if (!exampleListenerState.isInterestedInObject(objectId)) {
315         Log.i(TAG, "Unregistering for object we're no longer interested in");
316         unregister(clientId, objectIds);
317         writeExampleListenerState();
318       }
319     } else {
320       if (exampleListenerState.isInterestedInObject(objectId)) {
321         Log.i(TAG, "Registering for an object");
322         register(clientId, objectIds);
323         writeExampleListenerState();
324       }
325     }
326   }
327
328   @Override
329   protected void backgroundInvalidateForInternalUse(Iterable<Invalidation> invalidations) {
330     for (Invalidation invalidation : invalidations) {
331       Log.i(TAG, "background invalidate: " + invalidation);
332       exampleListenerState.informInvalidation(invalidation.getObjectId(), invalidation.getVersion(),
333           invalidation.getPayload(), /* isBackground */ true);
334       writeExampleListenerState();
335     }
336   }
337
338   /** Creates an intent that registers an interest in object invalidations for {@code objectId}. */
339   public static Intent createRegisterIntent(Context context, ObjectId objectId) {
340     return createRegistrationIntent(context, objectId, /* isRegister */ true);
341   }
342
343   /** Creates an intent that unregisters for invalidations for {@code objectId}. */
344   public static Intent createUnregisterIntent(Context context, ObjectId objectId) {
345     return createRegistrationIntent(context, objectId, /* isRegister */ false);
346   }
347
348   private static Intent createRegistrationIntent(Context context, ObjectId objectId,
349       boolean isRegister) {
350     Intent intent = new Intent();
351     intent.setAction(isRegister ? REGISTER_INTENT_ACTION : UNREGISTER_INTENT_ACTION);
352     intent.putExtra(OBJECT_ID_EXTRA, serializeObjectId(objectId));
353     intent.setClass(context, ExampleListener.class);
354     return intent;
355   }
356
357   /** Creates an intent that starts the invalidation client. */
358   public static Intent createStartIntent(Context context) {
359     Intent intent = new Intent();
360     intent.setAction(START_INTENT_ACTION);
361     intent.setClass(context, ExampleListener.class);
362     return intent;
363   }
364
365   /** Creates an intent that stops the invalidation client. */
366   public static Intent createStopIntent(Context context) {
367     Intent intent = new Intent();
368     intent.setAction(STOP_INTENT_ACTION);
369     intent.setClass(context, ExampleListener.class);
370     return intent;
371   }
372
373   private boolean tryHandleRegistrationIntent(Intent intent) {
374     final boolean isRegister;
375     if (REGISTER_INTENT_ACTION.equals(intent.getAction())) {
376       isRegister = true;
377     } else if (UNREGISTER_INTENT_ACTION.equals(intent.getAction())) {
378       isRegister = false;
379     } else {
380       // Not a registration intent.
381       return false;
382     }
383
384     // Try to parse object id extra.
385     ObjectId objectId = parseObjectIdExtra(intent);
386     if (objectId == null) {
387       Log.e(TAG, "Registration intent without valid object id extra");
388       return false;
389     }
390
391     // Update example listener state.
392     if (isRegister) {
393       exampleListenerState.addObjectOfInterest(objectId);
394     } else {
395       exampleListenerState.removeObjectOfInterest(objectId);
396     }
397     writeExampleListenerState();
398
399     // If the client is ready, perform registration now.
400     byte[] clientId = exampleListenerState.getClientId();
401     if (clientId == null) {
402       Log.i(TAG, "Deferring registration until client is ready");
403     } else {
404       // Perform registration immediately if we have been assigned a client id.
405       List<ObjectId> objectIds = new ArrayList<ObjectId>(1);
406       objectIds.add(objectId);
407       if (isRegister) {
408         register(clientId, objectIds);
409       } else {
410         unregister(clientId, objectIds);
411       }
412     }
413     return true;
414   }
415
416   private boolean tryHandleStartIntent(Intent intent) {
417     if (START_INTENT_ACTION.equals(intent.getAction())) {
418       // Clear the client id since a new one will be provided after the client has started.
419       exampleListenerState.setClientId(null);
420       writeExampleListenerState();
421
422       // Setting this to true allows us to see invalidations that may suppress older invalidations.
423       // When this flag is 'false', AndroidListener#invalidateUnknownVersion is called instead of
424       // AndroidListener#invalidate when suppression has potentially occurred.
425       final boolean allowSuppression = true;
426       InvalidationClientConfig config = new InvalidationClientConfig(CLIENT_TYPE, CLIENT_NAME,
427           "ExampleListener", allowSuppression);
428       startService(AndroidListener.createStartIntent(this, config));
429       return true;
430     }
431     return false;
432   }
433
434   private boolean tryHandleStopIntent(Intent intent) {
435     if (STOP_INTENT_ACTION.equals(intent.getAction())) {
436       // Clear the client id since the client is no longer available.
437       exampleListenerState.setClientId(null);
438       writeExampleListenerState();
439       startService(AndroidListener.createStopIntent(this));
440       return true;
441     }
442     return false;
443   }
444
445   private void writeExampleListenerState() {
446     Editor editor = getSharedPreferences().edit();
447     editor.putString(EXAMPLE_LISTENER_STATE_KEY, exampleListenerState.serialize());
448     if (!editor.commit()) {
449       // In a real app, this case would need to handled.
450       Log.e(TAG, "failed to write example listener state");
451     }
452     MainActivity.State.setInfo(exampleListenerState.toString());
453   }
454
455   private static byte[] serializeObjectId(ObjectId objectId) {
456     return MessageNano.toByteArray(ExampleListenerState.serializeObjectId(objectId));
457   }
458
459   private static ObjectId parseObjectIdExtra(Intent intent) {
460     byte[] bytes = intent.getByteArrayExtra(OBJECT_ID_EXTRA);
461     if (bytes == null) {
462       return null;
463     }
464     try {
465       ObjectIdProto proto = MessageNano.mergeFrom(new ObjectIdProto(), bytes);
466       return ExampleListenerState.deserializeObjectId(proto);
467     } catch (InvalidProtocolBufferNanoException exception) {
468       Log.e(TAG, String.format(Locale.ROOT, "Error parsing object id. error='%s'",
469           exception.getMessage()));
470       return null;
471     }
472   }
473
474   private SharedPreferences getSharedPreferences() {
475     return getApplicationContext().getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE);
476   }
477 }