| /* |
| * Copyright 2011 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.google.ipc.invalidation.external.client.contrib; |
| |
| import com.google.ipc.invalidation.external.client.InvalidationClient; |
| import com.google.ipc.invalidation.external.client.InvalidationClientConfig; |
| import com.google.ipc.invalidation.external.client.InvalidationListener; |
| import com.google.ipc.invalidation.external.client.InvalidationListener.RegistrationState; |
| import com.google.ipc.invalidation.external.client.SystemResources.Logger; |
| import com.google.ipc.invalidation.external.client.android.service.AndroidLogger; |
| import com.google.ipc.invalidation.external.client.types.AckHandle; |
| import com.google.ipc.invalidation.external.client.types.ErrorInfo; |
| import com.google.ipc.invalidation.external.client.types.Invalidation; |
| import com.google.ipc.invalidation.external.client.types.ObjectId; |
| import com.google.ipc.invalidation.ticl.InvalidationClientCore; |
| import com.google.ipc.invalidation.ticl.ProtoWrapperConverter; |
| import com.google.ipc.invalidation.ticl.android2.AndroidClock; |
| import com.google.ipc.invalidation.ticl.android2.AndroidInvalidationListenerIntentMapper; |
| import com.google.ipc.invalidation.ticl.android2.ProtocolIntents; |
| import com.google.ipc.invalidation.ticl.android2.channel.AndroidChannelConstants.AuthTokenConstants; |
| import com.google.ipc.invalidation.ticl.proto.AndroidListenerProtocol; |
| import com.google.ipc.invalidation.ticl.proto.AndroidListenerProtocol.RegistrationCommand; |
| import com.google.ipc.invalidation.ticl.proto.AndroidListenerProtocol.StartCommand; |
| import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP; |
| import com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationMessage; |
| import com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP; |
| import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP; |
| import com.google.ipc.invalidation.util.Bytes; |
| import com.google.ipc.invalidation.util.Preconditions; |
| import com.google.ipc.invalidation.util.ProtoWrapper.ValidationException; |
| |
| import android.app.IntentService; |
| import android.app.PendingIntent; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.concurrent.TimeUnit; |
| |
| |
| /** |
| * Simplified listener contract for Android clients. Takes care of exponential back-off when |
| * register or unregister are called for an object after a failure has occurred. Also suppresses |
| * redundant register requests. |
| * |
| * <p>A sample implementation of an {@link AndroidListener} is shown below: |
| * |
| * <p><code> |
| * class ExampleListener extends AndroidListener { |
| * @Override |
| * public void reissueRegistrations(byte[] clientId) { |
| * List<ObjectId> desiredRegistrations = ...; |
| * register(clientId, desiredRegistrations); |
| * } |
| * |
| * @Override |
| * public void invalidate(Invalidation invalidation, final byte[] ackHandle) { |
| * // Track the most recent version of the object (application-specific) and then acknowledge |
| * // the invalidation. |
| * ... |
| * acknowledge(ackHandle); |
| * } |
| * |
| * @Override |
| * public void informRegistrationFailure(byte[] clientId, ObjectId objectId, |
| * boolean isTransient, String errorMessage) { |
| * // Try again if there is a transient failure and we still care whether the object is |
| * // registered or not. |
| * if (isTransient) { |
| * boolean shouldRetry = ...; |
| * if (shouldRetry) { |
| * boolean shouldBeRegistered = ...; |
| * if (shouldBeRegistered) { |
| * register(clientId, ImmutableList.of(objectId)); |
| * } else { |
| * unregister(clientId, ImmutableList.of(objectId)); |
| * } |
| * } |
| * } |
| * } |
| * |
| * ... |
| * } |
| * </code> |
| * |
| * <p>See {@link com.google.ipc.invalidation.examples.android2} for a complete sample. |
| * |
| */ |
| public abstract class AndroidListener extends IntentService { |
| |
| /** External alarm receiver that allows the listener to respond to alarm intents. */ |
| public static final class AlarmReceiver extends BroadcastReceiver { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| Preconditions.checkNotNull(context); |
| Preconditions.checkNotNull(intent); |
| if (intent.hasExtra(AndroidListenerIntents.EXTRA_REGISTRATION)) { |
| AndroidListenerIntents.issueAndroidListenerIntent(context, intent); |
| } |
| } |
| } |
| |
| /** The logger. */ |
| private static final Logger logger = AndroidLogger.forPrefix(""); |
| |
| /** Initial retry delay for exponential backoff (1 minute). */ |
| |
| static int initialMaxDelayMs = (int) TimeUnit.SECONDS.toMillis(60); |
| |
| /** Maximum delay factor for exponential backoff (6 hours). */ |
| |
| static int maxDelayFactor = 6 * 60; |
| |
| /** The last client ID passed to the ready up-call. */ |
| |
| static Bytes lastClientIdForTest; |
| |
| /** |
| * Invalidation listener implementation. We implement the interface on a private field rather |
| * than directly to avoid leaking methods that should not be directly called by the client |
| * application. The listener must be called only on intent service thread. |
| */ |
| private final InvalidationListener invalidationListener = new InvalidationListener() { |
| @Override |
| public final void ready(final InvalidationClient client) { |
| Bytes clientId = state.getClientId(); |
| AndroidListener.lastClientIdForTest = clientId; |
| AndroidListener.this.ready(clientId.getByteArray()); |
| } |
| |
| @Override |
| public final void reissueRegistrations(final InvalidationClient client, byte[] prefix, |
| int prefixLength) { |
| AndroidListener.this.reissueRegistrations(state.getClientId().getByteArray()); |
| } |
| |
| @Override |
| public final void informRegistrationStatus(final InvalidationClient client, |
| final ObjectId objectId, final RegistrationState regState) { |
| state.informRegistrationSuccess(objectId); |
| AndroidListener.this.informRegistrationStatus(state.getClientId().getByteArray(), objectId, |
| regState); |
| } |
| |
| @Override |
| public final void informRegistrationFailure(final InvalidationClient client, |
| final ObjectId objectId, final boolean isTransient, final String errorMessage) { |
| state.informRegistrationFailure(objectId, isTransient); |
| AndroidListener.this.informRegistrationFailure(state.getClientId().getByteArray(), objectId, |
| isTransient, errorMessage); |
| } |
| |
| @Override |
| public void invalidate(InvalidationClient client, Invalidation invalidation, |
| AckHandle ackHandle) { |
| AndroidListener.this.invalidate(invalidation, ackHandle.getHandleData()); |
| } |
| |
| @Override |
| public void invalidateUnknownVersion(InvalidationClient client, ObjectId objectId, |
| AckHandle ackHandle) { |
| AndroidListener.this.invalidateUnknownVersion(objectId, ackHandle.getHandleData()); |
| } |
| |
| @Override |
| public void invalidateAll(InvalidationClient client, AckHandle ackHandle) { |
| AndroidListener.this.invalidateAll(ackHandle.getHandleData()); |
| } |
| |
| @Override |
| public void informError(InvalidationClient client, ErrorInfo errorInfo) { |
| AndroidListener.this.informError(errorInfo); |
| } |
| }; |
| |
| /** |
| * The internal state of the listener. Lazy initialization, triggered by {@link #onHandleIntent}. |
| */ |
| private AndroidListenerState state; |
| |
| /** The clock to use when scheduling retry call-backs. */ |
| private final AndroidClock clock = new AndroidClock.SystemClock(); |
| |
| /** |
| * The mapper used to route intents to the invalidation listener. Lazy initialization triggered |
| * by {@link #onCreate}. |
| */ |
| private AndroidInvalidationListenerIntentMapper intentMapper; |
| |
| /** Initializes {@link AndroidListener}. */ |
| protected AndroidListener() { |
| super(""); |
| |
| // If the process dies before an intent is handled, setIntentRedelivery(true) ensures that the |
| // last intent is redelivered. This optimization is not necessary for correctness: on restart, |
| // all registrations will be reissued and unacked invalidations will be resent anyways. |
| setIntentRedelivery(true); |
| } |
| |
| /** See specs for {@link InvalidationClient#start}. */ |
| public static Intent createStartIntent(Context context, InvalidationClientConfig config) { |
| Preconditions.checkNotNull(context); |
| Preconditions.checkNotNull(config); |
| Preconditions.checkNotNull(config.clientName); |
| |
| return AndroidListenerIntents.createStartIntent(context, config.clientType, |
| Bytes.fromByteArray(config.clientName), config.allowSuppression); |
| } |
| |
| /** See specs for {@link InvalidationClient#start}. */ |
| public static Intent createStartIntent(Context context, int clientType, byte[] clientName) { |
| Preconditions.checkNotNull(context); |
| Preconditions.checkNotNull(clientName); |
| |
| final boolean allowSuppression = true; |
| return AndroidListenerIntents.createStartIntent(context, clientType, |
| Bytes.fromByteArray(clientName), allowSuppression); |
| } |
| |
| /** See specs for {@link InvalidationClient#stop}. */ |
| public static Intent createStopIntent(Context context) { |
| Preconditions.checkNotNull(context); |
| |
| return AndroidListenerIntents.createStopIntent(context); |
| } |
| |
| /** |
| * See specs for {@link InvalidationClient#register}. |
| * |
| * @param context the context |
| * @param clientId identifier for the client service for which we are registering |
| * @param objectIds the object ids being registered |
| */ |
| public static Intent createRegisterIntent(Context context, byte[] clientId, |
| Iterable<ObjectId> objectIds) { |
| Preconditions.checkNotNull(context); |
| Preconditions.checkNotNull(clientId); |
| Preconditions.checkNotNull(objectIds); |
| |
| final boolean isRegister = true; |
| return AndroidListenerIntents.createRegistrationIntent(context, Bytes.fromByteArray(clientId), |
| objectIds, isRegister); |
| } |
| |
| /** |
| * See specs for {@link InvalidationClient#register}. |
| * |
| * @param clientId identifier for the client service for which we are registering |
| * @param objectIds the object ids being registered |
| */ |
| public void register(byte[] clientId, Iterable<ObjectId> objectIds) { |
| Preconditions.checkNotNull(clientId); |
| Preconditions.checkNotNull(objectIds); |
| |
| Context context = getApplicationContext(); |
| context.startService(createRegisterIntent(context, clientId, objectIds)); |
| } |
| |
| /** |
| * See specs for {@link InvalidationClient#unregister}. |
| * |
| * @param context the context |
| * @param clientId identifier for the client service for which we are unregistering |
| * @param objectIds the object ids being unregistered |
| */ |
| public static Intent createUnregisterIntent(Context context, byte[] clientId, |
| Iterable<ObjectId> objectIds) { |
| Preconditions.checkNotNull(context); |
| Preconditions.checkNotNull(clientId); |
| Preconditions.checkNotNull(objectIds); |
| |
| final boolean isRegister = false; |
| return AndroidListenerIntents.createRegistrationIntent(context, Bytes.fromByteArray(clientId), |
| objectIds, isRegister); |
| } |
| |
| /** |
| * Sets the authorization token and type used by the invalidation client. Call in response to |
| * {@link #requestAuthToken} calls. |
| * |
| * @param pendingIntent pending intent passed to {@link #requestAuthToken} |
| * @param authToken authorization token |
| * @param authType authorization token typo |
| */ |
| public static void setAuthToken(Context context, PendingIntent pendingIntent, String authToken, |
| String authType) { |
| Preconditions.checkNotNull(pendingIntent); |
| Preconditions.checkNotNull(authToken); |
| Preconditions.checkNotNull(authType); |
| |
| AndroidListenerIntents.issueAuthTokenResponse(context, pendingIntent, authToken, authType); |
| } |
| |
| /** |
| * See specs for {@link InvalidationClient#unregister}. |
| * |
| * @param clientId identifier for the client service for which we are registering |
| * @param objectIds the object ids being unregistered |
| */ |
| public void unregister(byte[] clientId, Iterable<ObjectId> objectIds) { |
| Preconditions.checkNotNull(clientId); |
| Preconditions.checkNotNull(objectIds); |
| |
| Context context = getApplicationContext(); |
| context.startService(createUnregisterIntent(context, clientId, objectIds)); |
| } |
| |
| /** See specs for {@link InvalidationClient#acknowledge}. */ |
| public static Intent createAcknowledgeIntent(Context context, byte[] ackHandle) { |
| Preconditions.checkNotNull(context); |
| Preconditions.checkNotNull(ackHandle); |
| |
| return AndroidListenerIntents.createAckIntent(context, ackHandle); |
| } |
| |
| /** See specs for {@link InvalidationClient#acknowledge}. */ |
| public void acknowledge(byte[] ackHandle) { |
| Preconditions.checkNotNull(ackHandle); |
| |
| Context context = getApplicationContext(); |
| context.startService(createAcknowledgeIntent(context, ackHandle)); |
| } |
| |
| /** |
| * See specs for {@link InvalidationListener#ready}. |
| * |
| * @param clientId the client identifier that must be passed to {@link #createRegisterIntent} |
| * and {@link #createUnregisterIntent} |
| */ |
| public abstract void ready(byte[] clientId); |
| |
| /** |
| * See specs for {@link InvalidationListener#reissueRegistrations}. |
| * |
| * @param clientId the client identifier that must be passed to {@link #createRegisterIntent} |
| * and {@link #createUnregisterIntent} |
| */ |
| public abstract void reissueRegistrations(byte[] clientId); |
| |
| /** |
| * See specs for {@link InvalidationListener#informError}. |
| */ |
| public abstract void informError(ErrorInfo errorInfo); |
| |
| /** |
| * See specs for {@link InvalidationListener#invalidate}. |
| * |
| * @param invalidation the invalidation |
| * @param ackHandle event acknowledgment handle |
| */ |
| public abstract void invalidate(Invalidation invalidation, byte[] ackHandle); |
| |
| /** |
| * See specs for {@link InvalidationListener#invalidateUnknownVersion}. |
| * |
| * @param objectId identifier for the object with unknown version |
| * @param ackHandle event acknowledgment handle |
| */ |
| public abstract void invalidateUnknownVersion(ObjectId objectId, byte[] ackHandle); |
| |
| /** |
| * See specs for {@link InvalidationListener#invalidateAll}. |
| * |
| * @param ackHandle event acknowledgment handle |
| */ |
| public abstract void invalidateAll(byte[] ackHandle); |
| |
| /** |
| * Read listener state. |
| * |
| * @return serialized state or {@code null} if it is not available |
| */ |
| public abstract byte[] readState(); |
| |
| /** Write listener state to some location. */ |
| public abstract void writeState(byte[] data); |
| |
| /** |
| * See specs for {@link InvalidationListener#informRegistrationFailure}. |
| */ |
| public abstract void informRegistrationFailure(byte[] clientId, ObjectId objectId, |
| boolean isTransient, String errorMessage); |
| |
| /** |
| * See specs for (@link InvalidationListener#informRegistrationStatus}. |
| */ |
| public abstract void informRegistrationStatus(byte[] clientId, ObjectId objectId, |
| RegistrationState regState); |
| |
| /** |
| * Called when an authorization token is needed. Respond by calling {@link #setAuthToken}. |
| * |
| * @param pendingIntent pending intent that must be used in {@link #setAuthToken} response. |
| * @param invalidAuthToken the existing invalid token or null if none exists. Implementation |
| * should invalidate the token. |
| */ |
| public abstract void requestAuthToken(PendingIntent pendingIntent, |
| String invalidAuthToken); |
| |
| /** |
| * Handles invalidations received while the client is stopped. An implementation may choose to |
| * do work in response to these invalidations (delivered best-effort by the invalidation system). |
| * Not intended for use by most client implementations. |
| */ |
| protected void backgroundInvalidateForInternalUse( |
| @SuppressWarnings("unused") Iterable<Invalidation> invalidations) { |
| // Ignore background invalidations by default. |
| } |
| |
| @Override |
| public void onCreate() { |
| super.onCreate(); |
| |
| // Initialize the intent mapper (now that context is available). |
| intentMapper = new AndroidInvalidationListenerIntentMapper(invalidationListener, this); |
| } |
| |
| /** |
| * Derived classes may override this method to handle custom intents. This is a recommended |
| * pattern for invalidation-related intents, e.g. for registration and unregistration. Derived |
| * classes should call {@code super.onHandleIntent(intent)} for any intents they did not |
| * handle on their own. |
| */ |
| @Override |
| protected void onHandleIntent(Intent intent) { |
| if (intent == null) { |
| return; |
| } |
| |
| // We lazily initialize state in calls to onHandleIntent rather than initializing in onCreate |
| // because onCreate runs on the UI thread and initializeState performs I/O. |
| if (state == null) { |
| initializeState(); |
| } |
| |
| // Handle any intents specific to the AndroidListener. For other intents, defer to the |
| // intentMapper, which handles listener upcalls corresponding to the InvalidationListener |
| // methods. |
| if (!tryHandleAuthTokenRequestIntent(intent) && |
| !tryHandleRegistrationIntent(intent) && |
| !tryHandleStartIntent(intent) && |
| !tryHandleStopIntent(intent) && |
| !tryHandleAckIntent(intent) && |
| !tryHandleBackgroundInvalidationsIntent(intent)) { |
| intentMapper.handleIntent(intent); |
| } |
| |
| // Always check to see if we need to persist state changes after handling an intent. |
| if (state.getIsDirty()) { |
| writeState(state.marshal().toByteArray()); |
| state.resetIsDirty(); |
| } |
| } |
| |
| /** Returns invalidation client that can be used to trigger intents against the TICL service. */ |
| private InvalidationClient getClient() { |
| return intentMapper.client; |
| } |
| |
| /** |
| * Initializes listener state either from persistent proto (if available) or from scratch. |
| */ |
| private void initializeState() { |
| AndroidListenerProtocol.AndroidListenerState proto = getPersistentState(); |
| if (proto != null) { |
| state = new AndroidListenerState(initialMaxDelayMs, maxDelayFactor, proto); |
| } else { |
| state = new AndroidListenerState(initialMaxDelayMs, maxDelayFactor); |
| } |
| } |
| |
| /** |
| * Reads and parses persistent state for the listener. Returns {@code null} if the state does not |
| * exist or is invalid. |
| */ |
| private AndroidListenerProtocol.AndroidListenerState getPersistentState() { |
| // Defer to application code to read the blob containing the state proto. |
| byte[] stateData = readState(); |
| try { |
| if (null != stateData) { |
| AndroidListenerProtocol.AndroidListenerState state = |
| AndroidListenerProtocol.AndroidListenerState.parseFrom(stateData); |
| if (!AndroidListenerProtos.isValidAndroidListenerState(state)) { |
| logger.warning("Invalid listener state."); |
| return null; |
| } |
| return state; |
| } |
| } catch (ValidationException exception) { |
| logger.warning("Failed to parse listener state: %s", exception); |
| } |
| return null; |
| } |
| |
| /** |
| * Tries to handle a request for an authorization token. Returns {@code true} iff the intent is |
| * an auth token request. |
| */ |
| private boolean tryHandleAuthTokenRequestIntent(Intent intent) { |
| if (!AndroidListenerIntents.isAuthTokenRequest(intent)) { |
| return false; |
| } |
| |
| // Check for invalid auth token. Subclass may have to invalidate it if it exists in the call |
| // to getNewAuthToken. |
| String invalidAuthToken = intent.getStringExtra( |
| AuthTokenConstants.EXTRA_INVALIDATE_AUTH_TOKEN); |
| // Intent also includes a pending intent that we can use to pass back our response. |
| PendingIntent pendingIntent = intent.getParcelableExtra( |
| AuthTokenConstants.EXTRA_PENDING_INTENT); |
| if (pendingIntent == null) { |
| logger.warning("Authorization request without pending intent extra."); |
| } else { |
| // Delegate to client application to figure out what the new token should be and the auth |
| // type. |
| requestAuthToken(pendingIntent, invalidAuthToken); |
| } |
| return true; |
| } |
| |
| /** Tries to handle a stop intent. Returns {@code true} iff the intent is a stop intent. */ |
| private boolean tryHandleStopIntent(Intent intent) { |
| if (!AndroidListenerIntents.isStopIntent(intent)) { |
| return false; |
| } |
| getClient().stop(); |
| return true; |
| } |
| |
| /** |
| * Tries to handle a registration intent. Returns {@code true} iff the intent is a registration |
| * intent. |
| */ |
| private boolean tryHandleRegistrationIntent(Intent intent) { |
| RegistrationCommand command = AndroidListenerIntents.findRegistrationCommand(intent); |
| if ((command == null) || !AndroidListenerProtos.isValidRegistrationCommand(command)) { |
| return false; |
| } |
| // Make sure the registration is intended for this client. If not, we ignore it (suggests |
| // there is a new client now). |
| if (!command.getClientId().equals(state.getClientId())) { |
| logger.warning("Ignoring registration request for old client. Old ID = %s, New ID = %s", |
| command.getClientId(), state.getClientId()); |
| return true; |
| } |
| boolean isRegister = command.getIsRegister(); |
| for (ObjectIdP objectIdP : command.getObjectId()) { |
| ObjectId objectId = ProtoWrapperConverter.convertFromObjectIdProto(objectIdP); |
| // We may need to delay the registration command (if it is not already delayed). |
| int delayMs = 0; |
| if (!command.getIsDelayed()) { |
| delayMs = state.getNextDelay(objectId); |
| } |
| if (delayMs == 0) { |
| issueRegistration(objectId, isRegister); |
| } else { |
| AndroidListenerIntents.issueDelayedRegistrationIntent(getApplicationContext(), clock, |
| state.getClientId(), objectId, isRegister, delayMs, state.getNextRequestCode()); |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Called when the client application requests a new registration. If a redundant register request |
| * is made -- i.e. when the application attempts to register an object that is already in the |
| * {@code AndroidListenerState#desiredRegistrations} collection -- the method returns immediately. |
| * Unregister requests are never ignored since we can't reliably determine whether an unregister |
| * request is redundant: our policy on failures of any kind is to remove the registration from |
| * the {@code AndroidListenerState#desiredRegistrations} collection. |
| */ |
| private void issueRegistration(ObjectId objectId, boolean isRegister) { |
| if (isRegister) { |
| if (state.addDesiredRegistration(objectId)) { |
| // Don't bother if we think it's already registered. Note that we remove the object from the |
| // collection when there is a failure. |
| getClient().register(objectId); |
| } |
| } else { |
| // Remove the object ID from the desired registration collection so that subsequent attempts |
| // to re-register are not ignored. |
| state.removeDesiredRegistration(objectId); |
| getClient().unregister(objectId); |
| } |
| } |
| |
| /** Tries to handle a start intent. Returns {@code true} iff the intent is a start intent. */ |
| private boolean tryHandleStartIntent(Intent intent) { |
| StartCommand command = AndroidListenerIntents.findStartCommand(intent); |
| if ((command == null) || !AndroidListenerProtos.isValidStartCommand(command)) { |
| return false; |
| } |
| // Reset the state so that we make no assumptions about desired registrations and can ignore |
| // messages directed at the wrong instance. |
| state = new AndroidListenerState(initialMaxDelayMs, maxDelayFactor); |
| boolean skipStartForTest = false; |
| ClientConfigP clientConfig = InvalidationClientCore.createConfig(); |
| if (command.getAllowSuppression() != clientConfig.getAllowSuppression()) { |
| ClientConfigP.Builder clientConfigBuilder = clientConfig.toBuilder(); |
| clientConfigBuilder.allowSuppression = command.getAllowSuppression(); |
| clientConfig = clientConfigBuilder.build(); |
| } |
| Intent startIntent = ProtocolIntents.InternalDowncalls.newCreateClientIntent( |
| command.getClientType(), command.getClientName(), clientConfig, skipStartForTest); |
| AndroidListenerIntents.issueTiclIntent(getApplicationContext(), startIntent); |
| return true; |
| } |
| |
| /** Tries to handle an ack intent. Returns {@code true} iff the intent is an ack intent. */ |
| private boolean tryHandleAckIntent(Intent intent) { |
| byte[] data = AndroidListenerIntents.findAckHandle(intent); |
| if (data == null) { |
| return false; |
| } |
| getClient().acknowledge(AckHandle.newInstance(data)); |
| return true; |
| } |
| |
| /** |
| * Tries to handle a background invalidation intent. Returns {@code true} iff the intent is a |
| * background invalidation intent. |
| */ |
| private boolean tryHandleBackgroundInvalidationsIntent(Intent intent) { |
| byte[] data = intent.getByteArrayExtra(ProtocolIntents.BACKGROUND_INVALIDATION_KEY); |
| if (data == null) { |
| return false; |
| } |
| try { |
| InvalidationMessage invalidationMessage = InvalidationMessage.parseFrom(data); |
| List<Invalidation> invalidations = new ArrayList<Invalidation>(); |
| for (InvalidationP invalidation : invalidationMessage.getInvalidation()) { |
| invalidations.add(ProtoWrapperConverter.convertFromInvalidationProto(invalidation)); |
| } |
| backgroundInvalidateForInternalUse(invalidations); |
| } catch (ValidationException exception) { |
| logger.info("Failed to parse background invalidation intent payload: %s", |
| exception.getMessage()); |
| } |
| return false; |
| } |
| |
| /** Returns the current state of the listener, for tests. */ |
| AndroidListenerState getStateForTest() { |
| return state; |
| } |
| } |