Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / cacheinvalidation / src / java / com / google / ipc / invalidation / ticl / android2 / TiclService.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
17 package com.google.ipc.invalidation.ticl.android2;
18
19 import com.google.ipc.invalidation.common.DigestFunction;
20 import com.google.ipc.invalidation.common.ObjectIdDigestUtils;
21 import com.google.ipc.invalidation.external.client.types.AckHandle;
22 import com.google.ipc.invalidation.external.client.types.Callback;
23 import com.google.ipc.invalidation.external.client.types.ErrorInfo;
24 import com.google.ipc.invalidation.external.client.types.ObjectId;
25 import com.google.ipc.invalidation.external.client.types.SimplePair;
26 import com.google.ipc.invalidation.external.client.types.Status;
27 import com.google.ipc.invalidation.ticl.InvalidationClientCore;
28 import com.google.ipc.invalidation.ticl.PersistenceUtils;
29 import com.google.ipc.invalidation.ticl.ProtoWrapperConverter;
30 import com.google.ipc.invalidation.ticl.android2.AndroidInvalidationClientImpl.IntentForwardingListener;
31 import com.google.ipc.invalidation.ticl.android2.ResourcesFactory.AndroidResources;
32 import com.google.ipc.invalidation.ticl.proto.AndroidService.AndroidSchedulerEvent;
33 import com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall;
34 import com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.RegistrationDowncall;
35 import com.google.ipc.invalidation.ticl.proto.AndroidService.InternalDowncall;
36 import com.google.ipc.invalidation.ticl.proto.AndroidService.InternalDowncall.CreateClient;
37 import com.google.ipc.invalidation.ticl.proto.Client.PersistentTiclState;
38 import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ServerToClientMessage;
39 import com.google.ipc.invalidation.util.Bytes;
40 import com.google.ipc.invalidation.util.ProtoWrapper.ValidationException;
41
42 import android.app.IntentService;
43 import android.content.Intent;
44
45 import java.util.Collection;
46
47
48 /**
49  * An {@link IntentService} that manages a single Ticl.
50  * <p>
51  * Concurrency model: {@link IntentService} guarantees that calls to {@link #onHandleIntent} will
52  * be executed serially on a dedicated thread. They may perform blocking work without blocking
53  * the application calling the service.
54  * <p>
55  * This thread will be used as the internal-scheduler thread for the Ticl.
56  *
57  */
58 public class TiclService extends IntentService {
59   /** This class must be public so that Android can instantiate it as a service. */
60
61   /** Resources for the created Ticls. */
62   private AndroidResources resources;
63
64   /** The function for computing persistence state digests when rewriting them. */
65   private final DigestFunction digestFn = new ObjectIdDigestUtils.Sha1DigestFunction();
66
67   public TiclService() {
68     super("TiclService");
69
70     // If the process dies during a call to onHandleIntent, redeliver the intent when the service
71     // restarts.
72     setIntentRedelivery(true);
73   }
74
75   /**
76    * Returns the resources to use for a Ticl. Normally, we use a new resources instance
77    * for every call, but for existing  tests, we need to be able to override this function
78    * and return the same instance each time.
79    */
80   AndroidResources createResources() {
81     return ResourcesFactory.createResources(this, new AndroidClock.SystemClock(), "TiclService");
82   }
83
84   @Override
85   protected void onHandleIntent(Intent intent) {
86     // TODO: We may want to use wakelocks to prevent the phone from sleeping
87     // before we have finished handling the Intent.
88
89     if (intent == null) {
90       return;
91     }
92
93     // We create resources anew each time.
94     resources = createResources();
95     resources.start();
96     resources.getLogger().fine("onHandleIntent(%s)", intent);
97
98     try {
99       // Dispatch the appropriate handler function based on which extra key is set.
100       if (intent.hasExtra(ProtocolIntents.CLIENT_DOWNCALL_KEY)) {
101         handleClientDowncall(intent.getByteArrayExtra(ProtocolIntents.CLIENT_DOWNCALL_KEY));
102       } else if (intent.hasExtra(ProtocolIntents.INTERNAL_DOWNCALL_KEY)) {
103         handleInternalDowncall(intent.getByteArrayExtra(ProtocolIntents.INTERNAL_DOWNCALL_KEY));
104       } else if (intent.hasExtra(ProtocolIntents.SCHEDULER_KEY)) {
105         handleSchedulerEvent(intent.getByteArrayExtra(ProtocolIntents.SCHEDULER_KEY));
106       } else {
107         resources.getLogger().warning("Received Intent without any recognized extras: %s", intent);
108       }
109     } finally {
110       // Null out resources to prevent accidentally using them in the future before they have been
111       // properly re-created.
112       resources.stop();
113       resources = null;
114     }
115   }
116
117   /** Handles a request to call a function on the ticl. */
118   private void handleClientDowncall(byte[] clientDowncallBytes) {
119     // Parse and validate the request.
120     final ClientDowncall downcall;
121     try {
122       downcall = ClientDowncall.parseFrom(clientDowncallBytes);
123     } catch (ValidationException exception) {
124       resources.getLogger().warning("Failed parsing ClientDowncall from %s: %s",
125           Bytes.toLazyCompactString(clientDowncallBytes), exception.getMessage());
126       return;
127     }
128
129     resources.getLogger().fine("Handle client downcall: %s", downcall);
130
131     // Restore the appropriate Ticl.
132     // TODO: what if this is the "wrong" Ticl?
133     AndroidInvalidationClientImpl ticl = loadExistingTicl();
134     if (ticl == null) {
135       resources.getLogger().warning("Dropping client downcall since no Ticl: %s", downcall);
136       return;
137     }
138
139     // Call the appropriate method.
140     if (downcall.getNullableAck() != null) {
141       ticl.acknowledge(
142           AckHandle.newInstance(downcall.getNullableAck().getAckHandle().getByteArray()));
143     } else if (downcall.hasStart()) {
144       ticl.start();
145     } else if (downcall.hasStop()) {
146       ticl.stop();
147     } else if (downcall.getNullableRegistrations() != null) {
148       RegistrationDowncall regDowncall = downcall.getNullableRegistrations();
149       if (!regDowncall.getRegistrations().isEmpty()) {
150         Collection<ObjectId> objects =
151             ProtoWrapperConverter.convertFromObjectIdProtoCollection(regDowncall.getRegistrations());
152         ticl.register(objects);
153       }
154       if (!regDowncall.getUnregistrations().isEmpty()) {
155         Collection<ObjectId> objects = ProtoWrapperConverter.convertFromObjectIdProtoCollection(
156             regDowncall.getUnregistrations());
157         ticl.unregister(objects);
158       }
159     } else {
160       throw new RuntimeException("Invalid downcall passed validation: " + downcall);
161     }
162     // If we are stopping the Ticl, then just delete its persisted in-memory state, since no
163     // operations on a stopped Ticl are valid. Otherwise, save the Ticl in-memory state to
164     // stable storage.
165     if (downcall.hasStop()) {
166       TiclStateManager.deleteStateFile(this);
167     } else {
168       TiclStateManager.saveTicl(this, resources.getLogger(), ticl);
169     }
170   }
171
172   /** Handles an internal downcall on the Ticl. */
173   private void handleInternalDowncall(byte[] internalDowncallBytes) {
174     // Parse and validate the request.
175     final InternalDowncall downcall;
176     try {
177       downcall = InternalDowncall.parseFrom(internalDowncallBytes);
178     } catch (ValidationException exception) {
179       resources.getLogger().warning("Failed parsing InternalDowncall from %s: %s",
180           Bytes.toLazyCompactString(internalDowncallBytes),
181           exception.getMessage());
182       return;
183     }
184     resources.getLogger().fine("Handle internal downcall: %s", downcall);
185
186     // Message from the data center; just forward it to the Ticl.
187     if (downcall.getNullableServerMessage() != null) {
188       // We deliver the message regardless of whether the Ticl existed, since we'll want to
189       // rewrite persistent state in the case where it did not.
190       // TODO: what if this is the "wrong" Ticl?
191       AndroidInvalidationClientImpl ticl = TiclStateManager.restoreTicl(this, resources);
192       handleServerMessage((ticl != null),
193           downcall.getNullableServerMessage().getData().getByteArray());
194       if (ticl != null) {
195         TiclStateManager.saveTicl(this, resources.getLogger(), ticl);
196       }
197       return;
198     }
199
200     // Network online/offline status change; just forward it to the Ticl.
201     if (downcall.getNullableNetworkStatus() != null) {
202       // Network status changes only make sense for Ticls that do exist.
203       // TODO: what if this is the "wrong" Ticl?
204       AndroidInvalidationClientImpl ticl = TiclStateManager.restoreTicl(this, resources);
205       if (ticl != null) {
206         resources.getNetworkListener().onOnlineStatusChange(
207             downcall.getNullableNetworkStatus().getIsOnline());
208         TiclStateManager.saveTicl(this, resources.getLogger(), ticl);
209       }
210       return;
211     }
212
213     // Client network address change; just forward it to the Ticl.
214     if (downcall.getNetworkAddrChange()) {
215       AndroidInvalidationClientImpl ticl = TiclStateManager.restoreTicl(this, resources);
216       if (ticl != null) {
217         resources.getNetworkListener().onAddressChange();
218         TiclStateManager.saveTicl(this, resources.getLogger(), ticl);
219       }
220       return;
221     }
222
223     // Client creation request (meta operation).
224     if (downcall.getNullableCreateClient() != null) {
225       handleCreateClient(downcall.getNullableCreateClient());
226       return;
227     }
228     throw new RuntimeException(
229         "Invalid internal downcall passed validation: " + downcall);
230   }
231
232   /** Handles a {@code createClient} request. */
233   private void handleCreateClient(CreateClient createClient) {
234     // Ensure no Ticl currently exists.
235     TiclStateManager.deleteStateFile(this);
236
237     // Create the requested Ticl.
238     resources.getLogger().fine("Create client: creating");
239     TiclStateManager.createTicl(this, resources, createClient.getClientType(),
240         createClient.getClientName().getByteArray(), createClient.getClientConfig(),
241         createClient.getSkipStartForTest());
242   }
243
244   /**
245    * Handles a {@code message} for a {@code ticl}. If the {@code ticl} is started, delivers the
246    * message. If the {@code ticl} is not started, drops the message and clears the last message send
247    * time in the Ticl persistent storage so that the Ticl will send a heartbeat the next time it
248    * starts.
249    */
250   private void handleServerMessage(boolean isTiclStarted, byte[] message) {
251     if (isTiclStarted) {
252       // Normal case -- message for a started Ticl. Deliver the message.
253       resources.getNetworkListener().onMessageReceived(message);
254       return;
255     }
256
257     // Even if the client is stopped, attempt to send invalidations if the client is configured to
258     // receive them.
259     maybeSendBackgroundInvalidationIntent(message);
260
261     // The Ticl isn't started. Rewrite persistent storage so that the last-send-time is a long
262     // time ago. The next time the Ticl starts, it will send a message to the data center, which
263     // ensures that it will be marked online and that the dropped message (or an equivalent) will
264     // be delivered.
265     // Android storage implementations are required to execute callbacks inline, so this code
266     // all executes synchronously.
267     resources.getLogger().fine("Message for unstarted Ticl; rewrite state");
268     resources.getStorage().readKey(InvalidationClientCore.CLIENT_TOKEN_KEY,
269         new Callback<SimplePair<Status, byte[]>>() {
270       @Override
271       public void accept(SimplePair<Status, byte[]> result) {
272         byte[] stateBytes = result.second;
273         if (stateBytes == null) {
274           resources.getLogger().info("No persistent state found for client; not rewriting");
275           return;
276         }
277         // Create new state identical to the old state except with a cleared
278         // lastMessageSendTimeMs.
279         PersistentTiclState state = PersistenceUtils.deserializeState(
280             resources.getLogger(), stateBytes, digestFn);
281         if (state == null) {
282           resources.getLogger().warning("Ignoring invalid Ticl state: %s",
283               Bytes.toLazyCompactString(stateBytes));
284           return;
285         }
286         PersistentTiclState.Builder stateBuilder = state.toBuilder();
287         stateBuilder.lastMessageSendTimeMs = 0L;
288         state = stateBuilder.build();
289
290         // Serialize the new state and write it to storage.
291         byte[] newClientState = PersistenceUtils.serializeState(state, digestFn);
292         resources.getStorage().writeKey(InvalidationClientCore.CLIENT_TOKEN_KEY, newClientState,
293             new Callback<Status>() {
294               @Override
295               public void accept(Status status) {
296                 if (status.getCode() != Status.Code.SUCCESS) {
297                   resources.getLogger().warning(
298                       "Failed saving rewritten persistent state to storage");
299                 }
300               }
301         });
302       }
303     });
304   }
305
306   /**
307    * If a service is registered to handle them, forward invalidations received while the
308    * invalidation client is stopped.
309    */
310   private void maybeSendBackgroundInvalidationIntent(byte[] message) {
311     // If a service is registered to receive background invalidations, parse the message to see if
312     // any of them should be forwarded.
313     AndroidTiclManifest manifest = new AndroidTiclManifest(getApplicationContext());
314     String backgroundServiceClass =
315         manifest.getBackgroundInvalidationListenerServiceClass();
316     if (backgroundServiceClass != null) {
317       try {
318         ServerToClientMessage s2cMessage = ServerToClientMessage.parseFrom(message);
319         if (s2cMessage.getNullableInvalidationMessage() != null) {
320           Intent intent = ProtocolIntents.newBackgroundInvalidationIntent(
321               s2cMessage.getNullableInvalidationMessage());
322           intent.setClassName(getApplicationContext(), backgroundServiceClass);
323           startService(intent);
324         }
325       } catch (ValidationException exception) {
326         resources.getLogger().info("Failed to parse message: %s", exception.getMessage());
327       }
328     }
329   }
330
331   /** Handles a request to call a particular recurring task on the Ticl. */
332   private void handleSchedulerEvent(byte[] schedulerEventBytes) {
333     // Parse and validate the request.
334     final AndroidSchedulerEvent event;
335     try {
336       event = AndroidSchedulerEvent.parseFrom(schedulerEventBytes);
337     } catch (ValidationException exception) {
338       resources.getLogger().warning("Failed parsing SchedulerEvent from %s: %s",
339           Bytes.toLazyCompactString(schedulerEventBytes), exception.getMessage());
340       return;
341     }
342
343     resources.getLogger().fine("Handle scheduler event: %s", event);
344
345     // Restore the appropriate Ticl.
346     AndroidInvalidationClientImpl ticl = TiclStateManager.restoreTicl(this, resources);
347
348     // If the Ticl didn't exist, drop the event.
349     if (ticl == null) {
350       resources.getLogger().fine("Dropping event %s; Ticl state does not exist",
351           event.getEventName());
352       return;
353     }
354
355     // Invoke the appropriate event.
356     AndroidInternalScheduler ticlScheduler =
357         (AndroidInternalScheduler) resources.getInternalScheduler();
358     ticlScheduler.handleSchedulerEvent(event);
359
360     // Save the Ticl state to persistent storage.
361     TiclStateManager.saveTicl(this, resources.getLogger(), ticl);
362   }
363
364   /**
365    * Returns the existing Ticl from persistent storage, or {@code null} if it does not exist.
366    * If it does not exist, raises an error to the listener. This function should be used
367    * only when loading a Ticl in response to a client-application call, since it raises an error
368    * back to the application.
369    */
370   private AndroidInvalidationClientImpl loadExistingTicl() {
371     AndroidInvalidationClientImpl ticl = TiclStateManager.restoreTicl(this, resources);
372     if (ticl == null) {
373       informListenerOfPermanentError("Client does not exist on downcall");
374     }
375     return ticl;
376   }
377
378   /** Informs the listener of a non-retryable {@code error}. */
379   private void informListenerOfPermanentError(final String error) {
380     ErrorInfo errorInfo = ErrorInfo.newInstance(0, false, error, null);
381     Intent errorIntent = ProtocolIntents.ListenerUpcalls.newErrorIntent(errorInfo);
382     IntentForwardingListener.issueIntent(this, errorIntent);
383   }
384
385   /** Returns the resources used for the current Ticl. */
386   AndroidResources getSystemResourcesForTest() {
387     return resources;
388   }
389 }