| Index: third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/ExampleListenerState.java
|
| diff --git a/third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/ExampleListenerState.java b/third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/ExampleListenerState.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..12ce9be5aef21b76ee38d3edfa7b6138d07d774f
|
| --- /dev/null
|
| +++ b/third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/ExampleListenerState.java
|
| @@ -0,0 +1,333 @@
|
| +/*
|
| + * 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.examples.android2;
|
| +
|
| +import com.google.ipc.invalidation.examples.android2.ExampleListenerProto.ExampleListenerStateProto;
|
| +import com.google.ipc.invalidation.examples.android2.ExampleListenerProto.ExampleListenerStateProto.ObjectIdProto;
|
| +import com.google.ipc.invalidation.examples.android2.ExampleListenerProto.ExampleListenerStateProto.ObjectStateProto;
|
| +import com.google.ipc.invalidation.external.client.types.ObjectId;
|
| +import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
|
| +import com.google.protobuf.nano.MessageNano;
|
| +
|
| +import android.util.Base64;
|
| +import android.util.Log;
|
| +
|
| +import java.util.ArrayList;
|
| +import java.util.Date;
|
| +import java.util.HashMap;
|
| +import java.util.List;
|
| +import java.util.Locale;
|
| +import java.util.Map;
|
| +
|
| +
|
| +/**
|
| + * Wrapper around persistent state for {@link ExampleListener}.
|
| + *
|
| + */
|
| +public class ExampleListenerState {
|
| +
|
| + /** Wrapper around persistent state for an object tracked by the {@link ExampleListener}. */
|
| + private static class ObjectState {
|
| + /** Object id for the object being tracked. */
|
| + final ObjectId objectId;
|
| +
|
| + /** Indicates whether the example listener wants to be registered for this object. */
|
| + boolean isRegistered;
|
| +
|
| + /**
|
| + * Payload of the invalidation with the highest version received so far. {@code null} before
|
| + * any invalidations have been received or after an unknown-version invalidation is received.
|
| + */
|
| + byte[] payload;
|
| +
|
| + /**
|
| + * Highest version invalidation received so far. {@code null} before any invalidations have
|
| + * been received or after an unknown-version invalidation is received.
|
| + */
|
| + Long highestVersion;
|
| +
|
| + /** Wall time in milliseconds at which most recent invalidation was received. */
|
| + Long invalidationTimeMillis;
|
| +
|
| + /** Indicates whether the last invalidation received was a background invalidation. */
|
| + boolean isBackground;
|
| +
|
| + ObjectState(ObjectStateProto objectStateProto) {
|
| + objectId = deserializeObjectId(objectStateProto.objectId);
|
| + isRegistered = objectStateProto.isRegistered;
|
| + payload = objectStateProto.payload;
|
| + highestVersion = objectStateProto.highestVersion;
|
| + invalidationTimeMillis = objectStateProto.invalidationTimeMillis;
|
| + isBackground = objectStateProto.isBackground;
|
| + }
|
| +
|
| + ObjectState(ObjectId objectId, boolean isRegistered) {
|
| + this.objectId = objectId;
|
| + this.isRegistered = isRegistered;
|
| + }
|
| +
|
| + ObjectStateProto serialize() {
|
| + ObjectStateProto proto = new ObjectStateProto();
|
| + proto.objectId = serializeObjectId(objectId);
|
| + proto.isRegistered = isRegistered;
|
| + proto.isBackground = isBackground;
|
| + proto.payload = payload;
|
| + proto.highestVersion = highestVersion;
|
| + proto.invalidationTimeMillis = invalidationTimeMillis;
|
| + return proto;
|
| + }
|
| +
|
| + @Override
|
| + public String toString() {
|
| + StringBuilder builder = new StringBuilder();
|
| + toString(builder);
|
| + return builder.toString();
|
| + }
|
| +
|
| + void toString(StringBuilder builder) {
|
| + builder.append(isRegistered ? "REG " : "UNREG ").append(objectId);
|
| + if (payload != null) {
|
| + builder.append(", |payload|=").append(payload.length);
|
| + }
|
| + if (highestVersion != null) {
|
| + builder.append(", highestVersion=").append(highestVersion.longValue());
|
| + }
|
| + if (isBackground) {
|
| + builder.append(", isBackground");
|
| + }
|
| + if (invalidationTimeMillis != null) {
|
| + builder.append(", invalidationTime=").append(new Date(invalidationTimeMillis.longValue()));
|
| + }
|
| + }
|
| + }
|
| +
|
| + /** The tag used for logging in the listener state class. */
|
| + private static final String TAG = "TEA2:ELS";
|
| +
|
| + /** Number of objects we're interested in tracking by default. */
|
| +
|
| + static final int NUM_INTERESTING_OBJECTS = 4;
|
| +
|
| + /** Object source for objects the client is initially tracking. */
|
| + private static final int DEMO_SOURCE = 4;
|
| +
|
| + /** Prefix for object names the client is initially tracking. */
|
| + private static final String OBJECT_ID_PREFIX = "Obj";
|
| +
|
| + /** State for all tracked objects. */
|
| + private final Map<ObjectId, ObjectState> trackedObjects;
|
| +
|
| + /** Client id reported by {@code AndroidListener#ready} call. */
|
| + private byte[] clientId;
|
| +
|
| + private ExampleListenerState(Map<ObjectId, ObjectState> trackedObjects,
|
| + byte[] clientId) {
|
| + if (trackedObjects == null) {
|
| + throw new NullPointerException();
|
| + }
|
| + this.trackedObjects = trackedObjects;
|
| + this.clientId = clientId;
|
| + }
|
| +
|
| + public static ExampleListenerState deserialize(String data) {
|
| + HashMap<ObjectId, ObjectState> trackedObjects = new HashMap<ObjectId, ObjectState>();
|
| + byte[] clientId;
|
| + ExampleListenerStateProto stateProto = tryParseStateProto(data);
|
| + if (stateProto == null) {
|
| + // By default, we're interested in objects with ids Obj1, Obj2, ...
|
| + for (int i = 1; i <= NUM_INTERESTING_OBJECTS; ++i) {
|
| + ObjectId objectId = ObjectId.newInstance(DEMO_SOURCE, (OBJECT_ID_PREFIX + i).getBytes());
|
| + trackedObjects.put(objectId, new ObjectState(objectId, true));
|
| + }
|
| + clientId = null;
|
| + } else {
|
| + // Load interesting objects from state proto.
|
| + for (ObjectStateProto objectStateProto : stateProto.objectState) {
|
| + ObjectState objectState = new ObjectState(objectStateProto);
|
| + trackedObjects.put(objectState.objectId, objectState);
|
| + }
|
| + clientId = stateProto.clientId;
|
| + }
|
| + return new ExampleListenerState(trackedObjects, clientId);
|
| + }
|
| +
|
| + /** Returns proto serialized in data or null if it cannot be decoded. */
|
| + private static ExampleListenerStateProto tryParseStateProto(String data) {
|
| + if (data == null) {
|
| + return null;
|
| + }
|
| + final byte[] bytes;
|
| + try {
|
| + bytes = Base64.decode(data, Base64.DEFAULT);
|
| + } catch (IllegalArgumentException exception) {
|
| + Log.e(TAG, String.format(Locale.ROOT, "Illegal base 64 encoding. data='%s', error='%s'", data,
|
| + exception.getMessage()));
|
| + return null;
|
| + }
|
| + try {
|
| + ExampleListenerStateProto proto =
|
| + MessageNano.mergeFrom(new ExampleListenerStateProto(), bytes);
|
| + return proto;
|
| + } catch (InvalidProtocolBufferNanoException exception) {
|
| + Log.e(TAG, String.format(Locale.ROOT, "Error parsing state bytes. data='%s', error='%s'",
|
| + data, exception.getMessage()));
|
| + return null;
|
| + }
|
| + }
|
| +
|
| + /** Serializes example listener state to string. */
|
| + public String serialize() {
|
| + ExampleListenerStateProto proto = new ExampleListenerStateProto();
|
| + proto.objectState = new ObjectStateProto[trackedObjects.size()];
|
| + int index = 0;
|
| + for (ObjectState objectState : trackedObjects.values()) {
|
| + ObjectStateProto objectStateProto = objectState.serialize();
|
| + proto.objectState[index++] = objectStateProto;
|
| + }
|
| + proto.clientId = clientId;
|
| + return Base64.encodeToString(MessageNano.toByteArray(proto), Base64.DEFAULT);
|
| + }
|
| +
|
| + Iterable<ObjectId> getInterestingObjects() {
|
| + List<ObjectId> interestingObjects = new ArrayList<ObjectId>(trackedObjects.size());
|
| + for (ObjectState objectState : trackedObjects.values()) {
|
| + if (objectState.isRegistered) {
|
| + interestingObjects.add(objectState.objectId);
|
| + }
|
| + }
|
| + return interestingObjects;
|
| + }
|
| +
|
| + byte[] getClientId() {
|
| + return clientId;
|
| + }
|
| +
|
| + /** Sets the client id passed to the example listener via the {@code ready()} call. */
|
| + void setClientId(byte[] value) {
|
| + clientId = value;
|
| + }
|
| +
|
| + /**
|
| + * Returns {@code true} if the state indicates a registration should exist for the given object.
|
| + */
|
| + boolean isInterestedInObject(ObjectId objectId) {
|
| + ObjectState objectState = trackedObjects.get(objectId);
|
| + return (objectState != null) && objectState.isRegistered;
|
| + }
|
| +
|
| + /** Updates state for the given object to indicate it should be registered. */
|
| + boolean addObjectOfInterest(ObjectId objectId) {
|
| + ObjectState objectState = trackedObjects.get(objectId);
|
| + if (objectState == null) {
|
| + objectState = new ObjectState(objectId, true);
|
| + trackedObjects.put(objectId, objectState);
|
| + return true;
|
| + }
|
| +
|
| + if (objectState.isRegistered) {
|
| + return false;
|
| + }
|
| + objectState.isRegistered = true;
|
| + return true;
|
| + }
|
| +
|
| + /** Updates state for the given object to indicate it should not be registered. */
|
| + boolean removeObjectOfInterest(ObjectId objectId) {
|
| + ObjectState objectState = trackedObjects.get(objectId);
|
| + if (objectState == null) {
|
| + return false;
|
| + }
|
| +
|
| + if (objectState.isRegistered) {
|
| + objectState.isRegistered = false;
|
| + return true;
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + /** Updates state for an object after an unknown-version invalidation is received. */
|
| + void informUnknownVersionInvalidation(ObjectId objectId) {
|
| + ObjectState objectState = getObjectStateForInvalidation(objectId);
|
| + objectState.invalidationTimeMillis = System.currentTimeMillis();
|
| + objectState.highestVersion = null;
|
| + objectState.payload = null;
|
| + }
|
| +
|
| + /** Updates state for an object after an invalidation is received. */
|
| + void informInvalidation(ObjectId objectId, long version, byte[] payload,
|
| + boolean isBackground) {
|
| + ObjectState objectState = getObjectStateForInvalidation(objectId);
|
| + if (objectState.highestVersion == null || objectState.highestVersion.longValue() < version) {
|
| + objectState.highestVersion = version;
|
| + objectState.payload = payload;
|
| + objectState.invalidationTimeMillis = System.currentTimeMillis();
|
| + objectState.isBackground = isBackground;
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Updates state when an invalidate all request is received (unknown version is marked for all
|
| + * objects).
|
| + */
|
| + public void informInvalidateAll() {
|
| + for (ObjectState objectState : trackedObjects.values()) {
|
| + informUnknownVersionInvalidation(objectState.objectId);
|
| + }
|
| + }
|
| +
|
| + /** Returns existing object state for an object or updates state. */
|
| + private ObjectState getObjectStateForInvalidation(ObjectId objectId) {
|
| + ObjectState objectState = trackedObjects.get(objectId);
|
| + if (objectState == null) {
|
| + // Invalidation for unregistered object.
|
| + objectState = new ObjectState(objectId, false);
|
| + trackedObjects.put(objectId, objectState);
|
| + }
|
| + return objectState;
|
| + }
|
| +
|
| + /** Returns an object given its serialized form. */
|
| + static ObjectId deserializeObjectId(ObjectIdProto objectIdProto) {
|
| + return ObjectId.newInstance(objectIdProto.source, objectIdProto.name);
|
| + }
|
| +
|
| + /** Serializes the given object id. */
|
| + static ObjectIdProto serializeObjectId(ObjectId objectId) {
|
| + ObjectIdProto proto = new ObjectIdProto();
|
| + proto.source = objectId.getSource();
|
| + proto.name = objectId.getName();
|
| + return proto;
|
| + }
|
| +
|
| + /** Clears all state for the example listener. */
|
| + void clear() {
|
| + trackedObjects.clear();
|
| + clientId = null;
|
| + }
|
| +
|
| + @Override
|
| + public String toString() {
|
| + StringBuilder builder = new StringBuilder();
|
| + if (clientId != null) {
|
| + builder.append("ready!\n");
|
| + }
|
| + for (ObjectState objectState : trackedObjects.values()) {
|
| + objectState.toString(builder);
|
| + builder.append("\n");
|
| + }
|
| + return builder.toString();
|
| + }
|
| +}
|
|
|