2 * Copyright 2011 Google Inc.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.google.ipc.invalidation.examples.android2;
18 import com.google.ipc.invalidation.examples.android2.ExampleListenerProto.ExampleListenerStateProto;
19 import com.google.ipc.invalidation.examples.android2.ExampleListenerProto.ExampleListenerStateProto.ObjectIdProto;
20 import com.google.ipc.invalidation.examples.android2.ExampleListenerProto.ExampleListenerStateProto.ObjectStateProto;
21 import com.google.ipc.invalidation.external.client.types.ObjectId;
22 import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
23 import com.google.protobuf.nano.MessageNano;
25 import android.util.Base64;
26 import android.util.Log;
28 import java.util.ArrayList;
29 import java.util.Date;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Locale;
37 * Wrapper around persistent state for {@link ExampleListener}.
40 public class ExampleListenerState {
42 /** Wrapper around persistent state for an object tracked by the {@link ExampleListener}. */
43 private static class ObjectState {
44 /** Object id for the object being tracked. */
45 final ObjectId objectId;
47 /** Indicates whether the example listener wants to be registered for this object. */
51 * Payload of the invalidation with the highest version received so far. {@code null} before
52 * any invalidations have been received or after an unknown-version invalidation is received.
57 * Highest version invalidation received so far. {@code null} before any invalidations have
58 * been received or after an unknown-version invalidation is received.
62 /** Wall time in milliseconds at which most recent invalidation was received. */
63 Long invalidationTimeMillis;
65 /** Indicates whether the last invalidation received was a background invalidation. */
68 ObjectState(ObjectStateProto objectStateProto) {
69 objectId = deserializeObjectId(objectStateProto.objectId);
70 isRegistered = objectStateProto.isRegistered;
71 payload = objectStateProto.payload;
72 highestVersion = objectStateProto.highestVersion;
73 invalidationTimeMillis = objectStateProto.invalidationTimeMillis;
74 isBackground = objectStateProto.isBackground;
77 ObjectState(ObjectId objectId, boolean isRegistered) {
78 this.objectId = objectId;
79 this.isRegistered = isRegistered;
82 ObjectStateProto serialize() {
83 ObjectStateProto proto = new ObjectStateProto();
84 proto.objectId = serializeObjectId(objectId);
85 proto.isRegistered = isRegistered;
86 proto.isBackground = isBackground;
87 proto.payload = payload;
88 proto.highestVersion = highestVersion;
89 proto.invalidationTimeMillis = invalidationTimeMillis;
94 public String toString() {
95 StringBuilder builder = new StringBuilder();
97 return builder.toString();
100 void toString(StringBuilder builder) {
101 builder.append(isRegistered ? "REG " : "UNREG ").append(objectId);
102 if (payload != null) {
103 builder.append(", |payload|=").append(payload.length);
105 if (highestVersion != null) {
106 builder.append(", highestVersion=").append(highestVersion.longValue());
109 builder.append(", isBackground");
111 if (invalidationTimeMillis != null) {
112 builder.append(", invalidationTime=").append(new Date(invalidationTimeMillis.longValue()));
117 /** The tag used for logging in the listener state class. */
118 private static final String TAG = "TEA2:ELS";
120 /** Number of objects we're interested in tracking by default. */
122 static final int NUM_INTERESTING_OBJECTS = 4;
124 /** Object source for objects the client is initially tracking. */
125 private static final int DEMO_SOURCE = 4;
127 /** Prefix for object names the client is initially tracking. */
128 private static final String OBJECT_ID_PREFIX = "Obj";
130 /** State for all tracked objects. */
131 private final Map<ObjectId, ObjectState> trackedObjects;
133 /** Client id reported by {@code AndroidListener#ready} call. */
134 private byte[] clientId;
136 private ExampleListenerState(Map<ObjectId, ObjectState> trackedObjects,
138 if (trackedObjects == null) {
139 throw new NullPointerException();
141 this.trackedObjects = trackedObjects;
142 this.clientId = clientId;
145 public static ExampleListenerState deserialize(String data) {
146 HashMap<ObjectId, ObjectState> trackedObjects = new HashMap<ObjectId, ObjectState>();
148 ExampleListenerStateProto stateProto = tryParseStateProto(data);
149 if (stateProto == null) {
150 // By default, we're interested in objects with ids Obj1, Obj2, ...
151 for (int i = 1; i <= NUM_INTERESTING_OBJECTS; ++i) {
152 ObjectId objectId = ObjectId.newInstance(DEMO_SOURCE, (OBJECT_ID_PREFIX + i).getBytes());
153 trackedObjects.put(objectId, new ObjectState(objectId, true));
157 // Load interesting objects from state proto.
158 for (ObjectStateProto objectStateProto : stateProto.objectState) {
159 ObjectState objectState = new ObjectState(objectStateProto);
160 trackedObjects.put(objectState.objectId, objectState);
162 clientId = stateProto.clientId;
164 return new ExampleListenerState(trackedObjects, clientId);
167 /** Returns proto serialized in data or null if it cannot be decoded. */
168 private static ExampleListenerStateProto tryParseStateProto(String data) {
174 bytes = Base64.decode(data, Base64.DEFAULT);
175 } catch (IllegalArgumentException exception) {
176 Log.e(TAG, String.format(Locale.ROOT, "Illegal base 64 encoding. data='%s', error='%s'", data,
177 exception.getMessage()));
181 ExampleListenerStateProto proto =
182 MessageNano.mergeFrom(new ExampleListenerStateProto(), bytes);
184 } catch (InvalidProtocolBufferNanoException exception) {
185 Log.e(TAG, String.format(Locale.ROOT, "Error parsing state bytes. data='%s', error='%s'",
186 data, exception.getMessage()));
191 /** Serializes example listener state to string. */
192 public String serialize() {
193 ExampleListenerStateProto proto = new ExampleListenerStateProto();
194 proto.objectState = new ObjectStateProto[trackedObjects.size()];
196 for (ObjectState objectState : trackedObjects.values()) {
197 ObjectStateProto objectStateProto = objectState.serialize();
198 proto.objectState[index++] = objectStateProto;
200 proto.clientId = clientId;
201 return Base64.encodeToString(MessageNano.toByteArray(proto), Base64.DEFAULT);
204 Iterable<ObjectId> getInterestingObjects() {
205 List<ObjectId> interestingObjects = new ArrayList<ObjectId>(trackedObjects.size());
206 for (ObjectState objectState : trackedObjects.values()) {
207 if (objectState.isRegistered) {
208 interestingObjects.add(objectState.objectId);
211 return interestingObjects;
214 byte[] getClientId() {
218 /** Sets the client id passed to the example listener via the {@code ready()} call. */
219 void setClientId(byte[] value) {
224 * Returns {@code true} if the state indicates a registration should exist for the given object.
226 boolean isInterestedInObject(ObjectId objectId) {
227 ObjectState objectState = trackedObjects.get(objectId);
228 return (objectState != null) && objectState.isRegistered;
231 /** Updates state for the given object to indicate it should be registered. */
232 boolean addObjectOfInterest(ObjectId objectId) {
233 ObjectState objectState = trackedObjects.get(objectId);
234 if (objectState == null) {
235 objectState = new ObjectState(objectId, true);
236 trackedObjects.put(objectId, objectState);
240 if (objectState.isRegistered) {
243 objectState.isRegistered = true;
247 /** Updates state for the given object to indicate it should not be registered. */
248 boolean removeObjectOfInterest(ObjectId objectId) {
249 ObjectState objectState = trackedObjects.get(objectId);
250 if (objectState == null) {
254 if (objectState.isRegistered) {
255 objectState.isRegistered = false;
261 /** Updates state for an object after an unknown-version invalidation is received. */
262 void informUnknownVersionInvalidation(ObjectId objectId) {
263 ObjectState objectState = getObjectStateForInvalidation(objectId);
264 objectState.invalidationTimeMillis = System.currentTimeMillis();
265 objectState.highestVersion = null;
266 objectState.payload = null;
269 /** Updates state for an object after an invalidation is received. */
270 void informInvalidation(ObjectId objectId, long version, byte[] payload,
271 boolean isBackground) {
272 ObjectState objectState = getObjectStateForInvalidation(objectId);
273 if (objectState.highestVersion == null || objectState.highestVersion.longValue() < version) {
274 objectState.highestVersion = version;
275 objectState.payload = payload;
276 objectState.invalidationTimeMillis = System.currentTimeMillis();
277 objectState.isBackground = isBackground;
282 * Updates state when an invalidate all request is received (unknown version is marked for all
285 public void informInvalidateAll() {
286 for (ObjectState objectState : trackedObjects.values()) {
287 informUnknownVersionInvalidation(objectState.objectId);
291 /** Returns existing object state for an object or updates state. */
292 private ObjectState getObjectStateForInvalidation(ObjectId objectId) {
293 ObjectState objectState = trackedObjects.get(objectId);
294 if (objectState == null) {
295 // Invalidation for unregistered object.
296 objectState = new ObjectState(objectId, false);
297 trackedObjects.put(objectId, objectState);
302 /** Returns an object given its serialized form. */
303 static ObjectId deserializeObjectId(ObjectIdProto objectIdProto) {
304 return ObjectId.newInstance(objectIdProto.source, objectIdProto.name);
307 /** Serializes the given object id. */
308 static ObjectIdProto serializeObjectId(ObjectId objectId) {
309 ObjectIdProto proto = new ObjectIdProto();
310 proto.source = objectId.getSource();
311 proto.name = objectId.getName();
315 /** Clears all state for the example listener. */
317 trackedObjects.clear();
322 public String toString() {
323 StringBuilder builder = new StringBuilder();
324 if (clientId != null) {
325 builder.append("ready!\n");
327 for (ObjectState objectState : trackedObjects.values()) {
328 objectState.toString(builder);
329 builder.append("\n");
331 return builder.toString();