b9a4e9ad807d4758d51400c99ef872c385c9a1c8
[platform/framework/web/crosswalk.git] / src / third_party / cacheinvalidation / src / java / com / google / ipc / invalidation / external / client / contrib / AndroidListener.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.external.client.contrib;
17
18 import com.google.ipc.invalidation.external.client.InvalidationClient;
19 import com.google.ipc.invalidation.external.client.InvalidationClientConfig;
20 import com.google.ipc.invalidation.external.client.InvalidationListener;
21 import com.google.ipc.invalidation.external.client.InvalidationListener.RegistrationState;
22 import com.google.ipc.invalidation.external.client.SystemResources.Logger;
23 import com.google.ipc.invalidation.external.client.android.service.AndroidLogger;
24 import com.google.ipc.invalidation.external.client.types.AckHandle;
25 import com.google.ipc.invalidation.external.client.types.ErrorInfo;
26 import com.google.ipc.invalidation.external.client.types.Invalidation;
27 import com.google.ipc.invalidation.external.client.types.ObjectId;
28 import com.google.ipc.invalidation.ticl.InvalidationClientCore;
29 import com.google.ipc.invalidation.ticl.ProtoWrapperConverter;
30 import com.google.ipc.invalidation.ticl.android2.AndroidClock;
31 import com.google.ipc.invalidation.ticl.android2.AndroidInvalidationListenerIntentMapper;
32 import com.google.ipc.invalidation.ticl.android2.ProtocolIntents;
33 import com.google.ipc.invalidation.ticl.android2.channel.AndroidChannelConstants.AuthTokenConstants;
34 import com.google.ipc.invalidation.ticl.proto.AndroidListenerProtocol;
35 import com.google.ipc.invalidation.ticl.proto.AndroidListenerProtocol.RegistrationCommand;
36 import com.google.ipc.invalidation.ticl.proto.AndroidListenerProtocol.StartCommand;
37 import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP;
38 import com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationMessage;
39 import com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP;
40 import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP;
41 import com.google.ipc.invalidation.util.Bytes;
42 import com.google.ipc.invalidation.util.Preconditions;
43 import com.google.ipc.invalidation.util.ProtoWrapper.ValidationException;
44
45 import android.app.IntentService;
46 import android.app.PendingIntent;
47 import android.content.BroadcastReceiver;
48 import android.content.Context;
49 import android.content.Intent;
50
51 import java.util.ArrayList;
52 import java.util.List;
53 import java.util.concurrent.TimeUnit;
54
55
56 /**
57  * Simplified listener contract for Android  clients. Takes care of exponential back-off when
58  * register or unregister are called for an object after a failure has occurred. Also suppresses
59  * redundant register requests.
60  *
61  * <p>A sample implementation of an {@link AndroidListener} is shown below:
62  *
63  * <p><code>
64  * class ExampleListener extends AndroidListener {
65  *   @Override
66  *   public void reissueRegistrations(byte[] clientId) {
67  *     List<ObjectId> desiredRegistrations = ...;
68  *     register(clientId, desiredRegistrations);
69  *   }
70  *
71  *   @Override
72  *   public void invalidate(Invalidation invalidation, final byte[] ackHandle) {
73  *     // Track the most recent version of the object (application-specific) and then acknowledge
74  *     // the invalidation.
75  *     ...
76  *     acknowledge(ackHandle);
77  *   }
78  *
79  *   @Override
80  *   public void informRegistrationFailure(byte[] clientId, ObjectId objectId,
81  *       boolean isTransient, String errorMessage) {
82  *     // Try again if there is a transient failure and we still care whether the object is
83  *     // registered or not.
84  *     if (isTransient) {
85  *       boolean shouldRetry = ...;
86  *       if (shouldRetry) {
87  *         boolean shouldBeRegistered = ...;
88  *         if (shouldBeRegistered) {
89  *           register(clientId, ImmutableList.of(objectId));
90  *         } else {
91  *           unregister(clientId, ImmutableList.of(objectId));
92  *         }
93  *       }
94  *     }
95  *   }
96  *
97  *   ...
98  * }
99  * </code>
100  *
101  * <p>See {@link com.google.ipc.invalidation.examples.android2} for a complete sample.
102  *
103  */
104 public abstract class AndroidListener extends IntentService {
105
106   /** External alarm receiver that allows the listener to respond to alarm intents. */
107   public static final class AlarmReceiver extends BroadcastReceiver {
108     @Override
109     public void onReceive(Context context, Intent intent) {
110       Preconditions.checkNotNull(context);
111       Preconditions.checkNotNull(intent);
112       if (intent.hasExtra(AndroidListenerIntents.EXTRA_REGISTRATION)) {
113         AndroidListenerIntents.issueAndroidListenerIntent(context, intent);
114       }
115     }
116   }
117
118   /** The logger. */
119   private static final Logger logger = AndroidLogger.forPrefix("");
120
121   /** Initial retry delay for exponential backoff (1 minute). */
122   
123   static int initialMaxDelayMs = (int) TimeUnit.SECONDS.toMillis(60);
124
125   /** Maximum delay factor for exponential backoff (6 hours). */
126   
127   static int maxDelayFactor = 6 * 60;
128
129   /** The last client ID passed to the ready up-call. */
130   
131   static Bytes lastClientIdForTest;
132
133   /**
134    * Invalidation listener implementation. We implement the interface on a private field rather
135    * than directly to avoid leaking methods that should not be directly called by the client
136    * application. The listener must be called only on intent service thread.
137    */
138   private final InvalidationListener invalidationListener = new InvalidationListener() {
139     @Override
140     public final void ready(final InvalidationClient client) {
141       Bytes clientId = state.getClientId();
142       AndroidListener.lastClientIdForTest = clientId;
143       AndroidListener.this.ready(clientId.getByteArray());
144     }
145
146     @Override
147     public final void reissueRegistrations(final InvalidationClient client, byte[] prefix,
148         int prefixLength) {
149       AndroidListener.this.reissueRegistrations(state.getClientId().getByteArray());
150     }
151
152     @Override
153     public final void informRegistrationStatus(final InvalidationClient client,
154         final ObjectId objectId, final RegistrationState regState) {
155       state.informRegistrationSuccess(objectId);
156       AndroidListener.this.informRegistrationStatus(state.getClientId().getByteArray(), objectId,
157           regState);
158     }
159
160     @Override
161     public final void informRegistrationFailure(final InvalidationClient client,
162         final ObjectId objectId, final boolean isTransient, final String errorMessage) {
163       state.informRegistrationFailure(objectId, isTransient);
164       AndroidListener.this.informRegistrationFailure(state.getClientId().getByteArray(), objectId,
165           isTransient, errorMessage);
166     }
167
168     @Override
169     public void invalidate(InvalidationClient client, Invalidation invalidation,
170         AckHandle ackHandle) {
171       AndroidListener.this.invalidate(invalidation, ackHandle.getHandleData());
172     }
173
174     @Override
175     public void invalidateUnknownVersion(InvalidationClient client, ObjectId objectId,
176         AckHandle ackHandle) {
177       AndroidListener.this.invalidateUnknownVersion(objectId, ackHandle.getHandleData());
178     }
179
180     @Override
181     public void invalidateAll(InvalidationClient client, AckHandle ackHandle) {
182       AndroidListener.this.invalidateAll(ackHandle.getHandleData());
183     }
184
185     @Override
186     public void informError(InvalidationClient client, ErrorInfo errorInfo) {
187       AndroidListener.this.informError(errorInfo);
188     }
189   };
190
191   /**
192    * The internal state of the listener. Lazy initialization, triggered by {@link #onHandleIntent}.
193    */
194   private AndroidListenerState state;
195
196   /** The clock to use when scheduling retry call-backs. */
197   private final AndroidClock clock = new AndroidClock.SystemClock();
198
199   /**
200    * The mapper used to route intents to the invalidation listener. Lazy initialization triggered
201    * by {@link #onCreate}.
202    */
203   private AndroidInvalidationListenerIntentMapper intentMapper;
204
205   /** Initializes {@link AndroidListener}. */
206   protected AndroidListener() {
207     super("");
208
209     // If the process dies before an intent is handled, setIntentRedelivery(true) ensures that the
210     // last intent is redelivered. This optimization is not necessary for correctness: on restart,
211     // all registrations will be reissued and unacked invalidations will be resent anyways.
212     setIntentRedelivery(true);
213   }
214
215   /** See specs for {@link InvalidationClient#start}. */
216   public static Intent createStartIntent(Context context, InvalidationClientConfig config) {
217     Preconditions.checkNotNull(context);
218     Preconditions.checkNotNull(config);
219     Preconditions.checkNotNull(config.clientName);
220
221     return AndroidListenerIntents.createStartIntent(context, config.clientType,
222         Bytes.fromByteArray(config.clientName), config.allowSuppression);
223   }
224
225   /** See specs for {@link InvalidationClient#start}. */
226   public static Intent createStartIntent(Context context, int clientType, byte[] clientName) {
227     Preconditions.checkNotNull(context);
228     Preconditions.checkNotNull(clientName);
229
230     final boolean allowSuppression = true;
231     return AndroidListenerIntents.createStartIntent(context, clientType,
232         Bytes.fromByteArray(clientName), allowSuppression);
233   }
234
235   /** See specs for {@link InvalidationClient#stop}. */
236   public static Intent createStopIntent(Context context) {
237     Preconditions.checkNotNull(context);
238
239     return AndroidListenerIntents.createStopIntent(context);
240   }
241
242   /**
243    * See specs for {@link InvalidationClient#register}.
244    *
245    * @param context the context
246    * @param clientId identifier for the client service for which we are registering
247    * @param objectIds the object ids being registered
248    */
249   public static Intent createRegisterIntent(Context context, byte[] clientId,
250       Iterable<ObjectId> objectIds) {
251     Preconditions.checkNotNull(context);
252     Preconditions.checkNotNull(clientId);
253     Preconditions.checkNotNull(objectIds);
254
255     final boolean isRegister = true;
256     return AndroidListenerIntents.createRegistrationIntent(context, Bytes.fromByteArray(clientId),
257         objectIds, isRegister);
258   }
259
260   /**
261    * See specs for {@link InvalidationClient#register}.
262    *
263    * @param clientId identifier for the client service for which we are registering
264    * @param objectIds the object ids being registered
265    */
266   public void register(byte[] clientId, Iterable<ObjectId> objectIds) {
267     Preconditions.checkNotNull(clientId);
268     Preconditions.checkNotNull(objectIds);
269
270     Context context = getApplicationContext();
271     context.startService(createRegisterIntent(context, clientId, objectIds));
272   }
273
274   /**
275    * See specs for {@link InvalidationClient#unregister}.
276    *
277    * @param context the context
278    * @param clientId identifier for the client service for which we are unregistering
279    * @param objectIds the object ids being unregistered
280    */
281   public static Intent createUnregisterIntent(Context context, byte[] clientId,
282       Iterable<ObjectId> objectIds) {
283     Preconditions.checkNotNull(context);
284     Preconditions.checkNotNull(clientId);
285     Preconditions.checkNotNull(objectIds);
286
287     final boolean isRegister = false;
288     return AndroidListenerIntents.createRegistrationIntent(context, Bytes.fromByteArray(clientId),
289         objectIds, isRegister);
290   }
291
292   /**
293    * Sets the authorization token and type used by the invalidation client. Call in response to
294    * {@link #requestAuthToken} calls.
295    *
296    * @param pendingIntent pending intent passed to {@link #requestAuthToken}
297    * @param authToken authorization token
298    * @param authType authorization token typo
299    */
300   public static void setAuthToken(Context context, PendingIntent pendingIntent, String authToken,
301       String authType) {
302     Preconditions.checkNotNull(pendingIntent);
303     Preconditions.checkNotNull(authToken);
304     Preconditions.checkNotNull(authType);
305
306     AndroidListenerIntents.issueAuthTokenResponse(context, pendingIntent, authToken, authType);
307   }
308
309   /**
310    * See specs for {@link InvalidationClient#unregister}.
311    *
312    * @param clientId identifier for the client service for which we are registering
313    * @param objectIds the object ids being unregistered
314    */
315   public void unregister(byte[] clientId, Iterable<ObjectId> objectIds) {
316     Preconditions.checkNotNull(clientId);
317     Preconditions.checkNotNull(objectIds);
318
319     Context context = getApplicationContext();
320     context.startService(createUnregisterIntent(context, clientId, objectIds));
321   }
322
323   /** See specs for {@link InvalidationClient#acknowledge}. */
324   public static Intent createAcknowledgeIntent(Context context, byte[] ackHandle) {
325     Preconditions.checkNotNull(context);
326     Preconditions.checkNotNull(ackHandle);
327
328     return AndroidListenerIntents.createAckIntent(context, ackHandle);
329   }
330
331   /** See specs for {@link InvalidationClient#acknowledge}. */
332   public void acknowledge(byte[] ackHandle) {
333     Preconditions.checkNotNull(ackHandle);
334
335     Context context = getApplicationContext();
336     context.startService(createAcknowledgeIntent(context, ackHandle));
337   }
338
339   /**
340    * See specs for {@link InvalidationListener#ready}.
341    *
342    * @param clientId the client identifier that must be passed to {@link #createRegisterIntent}
343    *     and {@link #createUnregisterIntent}
344    */
345   public abstract void ready(byte[] clientId);
346
347   /**
348    * See specs for {@link InvalidationListener#reissueRegistrations}.
349    *
350    * @param clientId the client identifier that must be passed to {@link #createRegisterIntent}
351    *     and {@link #createUnregisterIntent}
352    */
353   public abstract void reissueRegistrations(byte[] clientId);
354
355   /**
356    * See specs for {@link InvalidationListener#informError}.
357    */
358   public abstract void informError(ErrorInfo errorInfo);
359
360   /**
361    * See specs for {@link InvalidationListener#invalidate}.
362    *
363    * @param invalidation the invalidation
364    * @param ackHandle event acknowledgment handle
365    */
366   public abstract void invalidate(Invalidation invalidation, byte[] ackHandle);
367
368   /**
369    * See specs for {@link InvalidationListener#invalidateUnknownVersion}.
370    *
371    * @param objectId identifier for the object with unknown version
372    * @param ackHandle event acknowledgment handle
373    */
374   public abstract void invalidateUnknownVersion(ObjectId objectId, byte[] ackHandle);
375
376   /**
377    * See specs for {@link InvalidationListener#invalidateAll}.
378    *
379    * @param ackHandle event acknowledgment handle
380    */
381   public abstract void invalidateAll(byte[] ackHandle);
382
383   /**
384    * Read listener state.
385    *
386    * @return serialized state or {@code null} if it is not available
387    */
388   public abstract byte[] readState();
389
390   /** Write listener state to some location. */
391   public abstract void writeState(byte[] data);
392
393   /**
394    * See specs for {@link InvalidationListener#informRegistrationFailure}.
395    */
396   public abstract void informRegistrationFailure(byte[] clientId, ObjectId objectId,
397       boolean isTransient, String errorMessage);
398
399   /**
400    * See specs for (@link InvalidationListener#informRegistrationStatus}.
401    */
402   public abstract void informRegistrationStatus(byte[] clientId, ObjectId objectId,
403       RegistrationState regState);
404
405   /**
406    * Called when an authorization token is needed. Respond by calling {@link #setAuthToken}.
407    *
408    * @param pendingIntent pending intent that must be used in {@link #setAuthToken} response.
409    * @param invalidAuthToken the existing invalid token or null if none exists. Implementation
410    *     should invalidate the token.
411    */
412   public abstract void requestAuthToken(PendingIntent pendingIntent,
413       String invalidAuthToken);
414
415   /**
416    * Handles invalidations received while the client is stopped. An implementation may choose to
417    * do work in response to these invalidations (delivered best-effort by the invalidation system).
418    * Not intended for use by most client implementations.
419    */
420   protected void backgroundInvalidateForInternalUse(
421       @SuppressWarnings("unused") Iterable<Invalidation> invalidations) {
422     // Ignore background invalidations by default.
423   }
424
425   @Override
426   public void onCreate() {
427     super.onCreate();
428
429     // Initialize the intent mapper (now that context is available).
430     intentMapper = new AndroidInvalidationListenerIntentMapper(invalidationListener, this);
431   }
432
433   /**
434    * Derived classes may override this method to handle custom intents. This is a recommended
435    * pattern for invalidation-related intents, e.g. for registration and unregistration. Derived
436    * classes should call {@code super.onHandleIntent(intent)} for any intents they did not
437    * handle on their own.
438    */
439   @Override
440   protected void onHandleIntent(Intent intent) {
441     if (intent == null) {
442       return;
443     }
444
445     // We lazily initialize state in calls to onHandleIntent rather than initializing in onCreate
446     // because onCreate runs on the UI thread and initializeState performs I/O.
447     if (state == null) {
448       initializeState();
449     }
450
451     // Handle any intents specific to the AndroidListener. For other intents, defer to the
452     // intentMapper, which handles listener upcalls corresponding to the InvalidationListener
453     // methods.
454     if (!tryHandleAuthTokenRequestIntent(intent) &&
455         !tryHandleRegistrationIntent(intent) &&
456         !tryHandleStartIntent(intent) &&
457         !tryHandleStopIntent(intent) &&
458         !tryHandleAckIntent(intent) &&
459         !tryHandleBackgroundInvalidationsIntent(intent)) {
460       intentMapper.handleIntent(intent);
461     }
462
463     // Always check to see if we need to persist state changes after handling an intent.
464     if (state.getIsDirty()) {
465       writeState(state.marshal().toByteArray());
466       state.resetIsDirty();
467     }
468   }
469
470   /** Returns invalidation client that can be used to trigger intents against the TICL service. */
471   private InvalidationClient getClient() {
472     return intentMapper.client;
473   }
474
475   /**
476    * Initializes listener state either from persistent proto (if available) or from scratch.
477    */
478   private void initializeState() {
479     AndroidListenerProtocol.AndroidListenerState proto = getPersistentState();
480     if (proto != null) {
481       state = new AndroidListenerState(initialMaxDelayMs, maxDelayFactor, proto);
482     } else {
483       state = new AndroidListenerState(initialMaxDelayMs, maxDelayFactor);
484     }
485   }
486
487   /**
488    * Reads and parses persistent state for the listener. Returns {@code null} if the state does not
489    * exist or is invalid.
490    */
491   private AndroidListenerProtocol.AndroidListenerState getPersistentState() {
492     // Defer to application code to read the blob containing the state proto.
493     byte[] stateData = readState();
494     try {
495       if (null != stateData) {
496         AndroidListenerProtocol.AndroidListenerState state =
497             AndroidListenerProtocol.AndroidListenerState.parseFrom(stateData);
498         if (!AndroidListenerProtos.isValidAndroidListenerState(state)) {
499           logger.warning("Invalid listener state.");
500           return null;
501         }
502         return state;
503       }
504     } catch (ValidationException exception) {
505       logger.warning("Failed to parse listener state: %s", exception);
506     }
507     return null;
508   }
509
510   /**
511    * Tries to handle a request for an authorization token. Returns {@code true} iff the intent is
512    * an auth token request.
513    */
514   private boolean tryHandleAuthTokenRequestIntent(Intent intent) {
515     if (!AndroidListenerIntents.isAuthTokenRequest(intent)) {
516       return false;
517     }
518
519     // Check for invalid auth token. Subclass may have to invalidate it if it exists in the call
520     // to getNewAuthToken.
521     String invalidAuthToken = intent.getStringExtra(
522         AuthTokenConstants.EXTRA_INVALIDATE_AUTH_TOKEN);
523     // Intent also includes a pending intent that we can use to pass back our response.
524     PendingIntent pendingIntent = intent.getParcelableExtra(
525         AuthTokenConstants.EXTRA_PENDING_INTENT);
526     if (pendingIntent == null) {
527       logger.warning("Authorization request without pending intent extra.");
528     } else {
529       // Delegate to client application to figure out what the new token should be and the auth
530       // type.
531       requestAuthToken(pendingIntent, invalidAuthToken);
532     }
533     return true;
534   }
535
536   /** Tries to handle a stop intent. Returns {@code true} iff the intent is a stop intent. */
537   private boolean tryHandleStopIntent(Intent intent) {
538     if (!AndroidListenerIntents.isStopIntent(intent)) {
539       return false;
540     }
541     getClient().stop();
542     return true;
543   }
544
545   /**
546    * Tries to handle a registration intent. Returns {@code true} iff the intent is a registration
547    * intent.
548    */
549   private boolean tryHandleRegistrationIntent(Intent intent) {
550     RegistrationCommand command = AndroidListenerIntents.findRegistrationCommand(intent);
551     if ((command == null) || !AndroidListenerProtos.isValidRegistrationCommand(command)) {
552       return false;
553     }
554     // Make sure the registration is intended for this client. If not, we ignore it (suggests
555     // there is a new client now).
556     if (!command.getClientId().equals(state.getClientId())) {
557       logger.warning("Ignoring registration request for old client. Old ID = %s, New ID = %s",
558           command.getClientId(), state.getClientId());
559       return true;
560     }
561     boolean isRegister = command.getIsRegister();
562     for (ObjectIdP objectIdP : command.getObjectId()) {
563       ObjectId objectId = ProtoWrapperConverter.convertFromObjectIdProto(objectIdP);
564       // We may need to delay the registration command (if it is not already delayed).
565       int delayMs = 0;
566       if (!command.getIsDelayed()) {
567         delayMs = state.getNextDelay(objectId);
568       }
569       if (delayMs == 0) {
570         issueRegistration(objectId, isRegister);
571       } else {
572         AndroidListenerIntents.issueDelayedRegistrationIntent(getApplicationContext(), clock,
573             state.getClientId(), objectId, isRegister, delayMs, state.getNextRequestCode());
574       }
575     }
576     return true;
577   }
578
579   /**
580    * Called when the client application requests a new registration. If a redundant register request
581    * is made -- i.e. when the application attempts to register an object that is already in the
582    * {@code AndroidListenerState#desiredRegistrations} collection -- the method returns immediately.
583    * Unregister requests are never ignored since we can't reliably determine whether an unregister
584    * request is redundant: our policy on failures of any kind is to remove the registration from
585    * the {@code AndroidListenerState#desiredRegistrations} collection.
586    */
587   private void issueRegistration(ObjectId objectId, boolean isRegister) {
588     if (isRegister) {
589       if (state.addDesiredRegistration(objectId)) {
590         // Don't bother if we think it's already registered. Note that we remove the object from the
591         // collection when there is a failure.
592         getClient().register(objectId);
593       }
594     } else {
595       // Remove the object ID from the desired registration collection so that subsequent attempts
596       // to re-register are not ignored.
597       state.removeDesiredRegistration(objectId);
598       getClient().unregister(objectId);
599     }
600   }
601
602   /** Tries to handle a start intent. Returns {@code true} iff the intent is a start intent. */
603   private boolean tryHandleStartIntent(Intent intent) {
604     StartCommand command = AndroidListenerIntents.findStartCommand(intent);
605     if ((command == null) || !AndroidListenerProtos.isValidStartCommand(command)) {
606       return false;
607     }
608     // Reset the state so that we make no assumptions about desired registrations and can ignore
609     // messages directed at the wrong instance.
610     state = new AndroidListenerState(initialMaxDelayMs, maxDelayFactor);
611     boolean skipStartForTest = false;
612     ClientConfigP clientConfig = InvalidationClientCore.createConfig();
613     if (command.getAllowSuppression() != clientConfig.getAllowSuppression()) {
614       ClientConfigP.Builder clientConfigBuilder = clientConfig.toBuilder();
615       clientConfigBuilder.allowSuppression = command.getAllowSuppression();
616       clientConfig = clientConfigBuilder.build();
617     }
618     Intent startIntent = ProtocolIntents.InternalDowncalls.newCreateClientIntent(
619         command.getClientType(), command.getClientName(), clientConfig, skipStartForTest);
620     AndroidListenerIntents.issueTiclIntent(getApplicationContext(), startIntent);
621     return true;
622   }
623
624   /** Tries to handle an ack intent. Returns {@code true} iff the intent is an ack intent. */
625   private boolean tryHandleAckIntent(Intent intent) {
626     byte[] data = AndroidListenerIntents.findAckHandle(intent);
627     if (data == null) {
628       return false;
629     }
630     getClient().acknowledge(AckHandle.newInstance(data));
631     return true;
632   }
633
634   /**
635    * Tries to handle a background invalidation intent. Returns {@code true} iff the intent is a
636    * background invalidation intent.
637    */
638   private boolean tryHandleBackgroundInvalidationsIntent(Intent intent) {
639     byte[] data = intent.getByteArrayExtra(ProtocolIntents.BACKGROUND_INVALIDATION_KEY);
640     if (data == null) {
641       return false;
642     }
643     try {
644       InvalidationMessage invalidationMessage = InvalidationMessage.parseFrom(data);
645       List<Invalidation> invalidations = new ArrayList<Invalidation>();
646       for (InvalidationP invalidation : invalidationMessage.getInvalidation()) {
647         invalidations.add(ProtoWrapperConverter.convertFromInvalidationProto(invalidation));
648       }
649       backgroundInvalidateForInternalUse(invalidations);
650     } catch (ValidationException exception) {
651       logger.info("Failed to parse background invalidation intent payload: %s",
652           exception.getMessage());
653     }
654     return false;
655   }
656
657   /** Returns the current state of the listener, for tests. */
658   AndroidListenerState getStateForTest() {
659     return state;
660   }
661 }