| Index: third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListener.java
|
| diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListener.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListener.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..b9a4e9ad807d4758d51400c99ef872c385c9a1c8
|
| --- /dev/null
|
| +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListener.java
|
| @@ -0,0 +1,661 @@
|
| +/*
|
| + * 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;
|
| + }
|
| +}
|
|
|