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