Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(7)

Unified Diff: third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/TiclService.java

Issue 1162033004: Pull cacheinvalidations code directory into chromium repo. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/TiclService.java
diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/TiclService.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/TiclService.java
new file mode 100644
index 0000000000000000000000000000000000000000..ff979f158126fd7bb4b6550373c7d4768d7ee952
--- /dev/null
+++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/TiclService.java
@@ -0,0 +1,389 @@
+/*
+ * 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.ticl.android2;
+
+import com.google.ipc.invalidation.common.DigestFunction;
+import com.google.ipc.invalidation.common.ObjectIdDigestUtils;
+import com.google.ipc.invalidation.external.client.types.AckHandle;
+import com.google.ipc.invalidation.external.client.types.Callback;
+import com.google.ipc.invalidation.external.client.types.ErrorInfo;
+import com.google.ipc.invalidation.external.client.types.ObjectId;
+import com.google.ipc.invalidation.external.client.types.SimplePair;
+import com.google.ipc.invalidation.external.client.types.Status;
+import com.google.ipc.invalidation.ticl.InvalidationClientCore;
+import com.google.ipc.invalidation.ticl.PersistenceUtils;
+import com.google.ipc.invalidation.ticl.ProtoWrapperConverter;
+import com.google.ipc.invalidation.ticl.android2.AndroidInvalidationClientImpl.IntentForwardingListener;
+import com.google.ipc.invalidation.ticl.android2.ResourcesFactory.AndroidResources;
+import com.google.ipc.invalidation.ticl.proto.AndroidService.AndroidSchedulerEvent;
+import com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall;
+import com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.RegistrationDowncall;
+import com.google.ipc.invalidation.ticl.proto.AndroidService.InternalDowncall;
+import com.google.ipc.invalidation.ticl.proto.AndroidService.InternalDowncall.CreateClient;
+import com.google.ipc.invalidation.ticl.proto.Client.PersistentTiclState;
+import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ServerToClientMessage;
+import com.google.ipc.invalidation.util.Bytes;
+import com.google.ipc.invalidation.util.ProtoWrapper.ValidationException;
+
+import android.app.IntentService;
+import android.content.Intent;
+
+import java.util.Collection;
+
+
+/**
+ * An {@link IntentService} that manages a single Ticl.
+ * <p>
+ * Concurrency model: {@link IntentService} guarantees that calls to {@link #onHandleIntent} will
+ * be executed serially on a dedicated thread. They may perform blocking work without blocking
+ * the application calling the service.
+ * <p>
+ * This thread will be used as the internal-scheduler thread for the Ticl.
+ *
+ */
+public class TiclService extends IntentService {
+ /** This class must be public so that Android can instantiate it as a service. */
+
+ /** Resources for the created Ticls. */
+ private AndroidResources resources;
+
+ /** The function for computing persistence state digests when rewriting them. */
+ private final DigestFunction digestFn = new ObjectIdDigestUtils.Sha1DigestFunction();
+
+ public TiclService() {
+ super("TiclService");
+
+ // If the process dies during a call to onHandleIntent, redeliver the intent when the service
+ // restarts.
+ setIntentRedelivery(true);
+ }
+
+ /**
+ * Returns the resources to use for a Ticl. Normally, we use a new resources instance
+ * for every call, but for existing tests, we need to be able to override this function
+ * and return the same instance each time.
+ */
+ AndroidResources createResources() {
+ return ResourcesFactory.createResources(this, new AndroidClock.SystemClock(), "TiclService");
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ // TODO: We may want to use wakelocks to prevent the phone from sleeping
+ // before we have finished handling the Intent.
+
+ if (intent == null) {
+ return;
+ }
+
+ // We create resources anew each time.
+ resources = createResources();
+ resources.start();
+ resources.getLogger().fine("onHandleIntent(%s)", intent);
+
+ try {
+ // Dispatch the appropriate handler function based on which extra key is set.
+ if (intent.hasExtra(ProtocolIntents.CLIENT_DOWNCALL_KEY)) {
+ handleClientDowncall(intent.getByteArrayExtra(ProtocolIntents.CLIENT_DOWNCALL_KEY));
+ } else if (intent.hasExtra(ProtocolIntents.INTERNAL_DOWNCALL_KEY)) {
+ handleInternalDowncall(intent.getByteArrayExtra(ProtocolIntents.INTERNAL_DOWNCALL_KEY));
+ } else if (intent.hasExtra(ProtocolIntents.SCHEDULER_KEY)) {
+ handleSchedulerEvent(intent.getByteArrayExtra(ProtocolIntents.SCHEDULER_KEY));
+ } else {
+ resources.getLogger().warning("Received Intent without any recognized extras: %s", intent);
+ }
+ } finally {
+ // Null out resources to prevent accidentally using them in the future before they have been
+ // properly re-created.
+ resources.stop();
+ resources = null;
+ }
+ }
+
+ /** Handles a request to call a function on the ticl. */
+ private void handleClientDowncall(byte[] clientDowncallBytes) {
+ // Parse and validate the request.
+ final ClientDowncall downcall;
+ try {
+ downcall = ClientDowncall.parseFrom(clientDowncallBytes);
+ } catch (ValidationException exception) {
+ resources.getLogger().warning("Failed parsing ClientDowncall from %s: %s",
+ Bytes.toLazyCompactString(clientDowncallBytes), exception.getMessage());
+ return;
+ }
+
+ resources.getLogger().fine("Handle client downcall: %s", downcall);
+
+ // Restore the appropriate Ticl.
+ // TODO: what if this is the "wrong" Ticl?
+ AndroidInvalidationClientImpl ticl = loadExistingTicl();
+ if (ticl == null) {
+ resources.getLogger().warning("Dropping client downcall since no Ticl: %s", downcall);
+ return;
+ }
+
+ // Call the appropriate method.
+ if (downcall.getNullableAck() != null) {
+ ticl.acknowledge(
+ AckHandle.newInstance(downcall.getNullableAck().getAckHandle().getByteArray()));
+ } else if (downcall.hasStart()) {
+ ticl.start();
+ } else if (downcall.hasStop()) {
+ ticl.stop();
+ } else if (downcall.getNullableRegistrations() != null) {
+ RegistrationDowncall regDowncall = downcall.getNullableRegistrations();
+ if (!regDowncall.getRegistrations().isEmpty()) {
+ Collection<ObjectId> objects =
+ ProtoWrapperConverter.convertFromObjectIdProtoCollection(regDowncall.getRegistrations());
+ ticl.register(objects);
+ }
+ if (!regDowncall.getUnregistrations().isEmpty()) {
+ Collection<ObjectId> objects = ProtoWrapperConverter.convertFromObjectIdProtoCollection(
+ regDowncall.getUnregistrations());
+ ticl.unregister(objects);
+ }
+ } else {
+ throw new RuntimeException("Invalid downcall passed validation: " + downcall);
+ }
+ // If we are stopping the Ticl, then just delete its persisted in-memory state, since no
+ // operations on a stopped Ticl are valid. Otherwise, save the Ticl in-memory state to
+ // stable storage.
+ if (downcall.hasStop()) {
+ TiclStateManager.deleteStateFile(this);
+ } else {
+ TiclStateManager.saveTicl(this, resources.getLogger(), ticl);
+ }
+ }
+
+ /** Handles an internal downcall on the Ticl. */
+ private void handleInternalDowncall(byte[] internalDowncallBytes) {
+ // Parse and validate the request.
+ final InternalDowncall downcall;
+ try {
+ downcall = InternalDowncall.parseFrom(internalDowncallBytes);
+ } catch (ValidationException exception) {
+ resources.getLogger().warning("Failed parsing InternalDowncall from %s: %s",
+ Bytes.toLazyCompactString(internalDowncallBytes),
+ exception.getMessage());
+ return;
+ }
+ resources.getLogger().fine("Handle internal downcall: %s", downcall);
+
+ // Message from the data center; just forward it to the Ticl.
+ if (downcall.getNullableServerMessage() != null) {
+ // We deliver the message regardless of whether the Ticl existed, since we'll want to
+ // rewrite persistent state in the case where it did not.
+ // TODO: what if this is the "wrong" Ticl?
+ AndroidInvalidationClientImpl ticl = TiclStateManager.restoreTicl(this, resources);
+ handleServerMessage((ticl != null),
+ downcall.getNullableServerMessage().getData().getByteArray());
+ if (ticl != null) {
+ TiclStateManager.saveTicl(this, resources.getLogger(), ticl);
+ }
+ return;
+ }
+
+ // Network online/offline status change; just forward it to the Ticl.
+ if (downcall.getNullableNetworkStatus() != null) {
+ // Network status changes only make sense for Ticls that do exist.
+ // TODO: what if this is the "wrong" Ticl?
+ AndroidInvalidationClientImpl ticl = TiclStateManager.restoreTicl(this, resources);
+ if (ticl != null) {
+ resources.getNetworkListener().onOnlineStatusChange(
+ downcall.getNullableNetworkStatus().getIsOnline());
+ TiclStateManager.saveTicl(this, resources.getLogger(), ticl);
+ }
+ return;
+ }
+
+ // Client network address change; just forward it to the Ticl.
+ if (downcall.getNetworkAddrChange()) {
+ AndroidInvalidationClientImpl ticl = TiclStateManager.restoreTicl(this, resources);
+ if (ticl != null) {
+ resources.getNetworkListener().onAddressChange();
+ TiclStateManager.saveTicl(this, resources.getLogger(), ticl);
+ }
+ return;
+ }
+
+ // Client creation request (meta operation).
+ if (downcall.getNullableCreateClient() != null) {
+ handleCreateClient(downcall.getNullableCreateClient());
+ return;
+ }
+ throw new RuntimeException(
+ "Invalid internal downcall passed validation: " + downcall);
+ }
+
+ /** Handles a {@code createClient} request. */
+ private void handleCreateClient(CreateClient createClient) {
+ // Ensure no Ticl currently exists.
+ TiclStateManager.deleteStateFile(this);
+
+ // Create the requested Ticl.
+ resources.getLogger().fine("Create client: creating");
+ TiclStateManager.createTicl(this, resources, createClient.getClientType(),
+ createClient.getClientName().getByteArray(), createClient.getClientConfig(),
+ createClient.getSkipStartForTest());
+ }
+
+ /**
+ * Handles a {@code message} for a {@code ticl}. If the {@code ticl} is started, delivers the
+ * message. If the {@code ticl} is not started, drops the message and clears the last message send
+ * time in the Ticl persistent storage so that the Ticl will send a heartbeat the next time it
+ * starts.
+ */
+ private void handleServerMessage(boolean isTiclStarted, byte[] message) {
+ if (isTiclStarted) {
+ // Normal case -- message for a started Ticl. Deliver the message.
+ resources.getNetworkListener().onMessageReceived(message);
+ return;
+ }
+
+ // Even if the client is stopped, attempt to send invalidations if the client is configured to
+ // receive them.
+ maybeSendBackgroundInvalidationIntent(message);
+
+ // The Ticl isn't started. Rewrite persistent storage so that the last-send-time is a long
+ // time ago. The next time the Ticl starts, it will send a message to the data center, which
+ // ensures that it will be marked online and that the dropped message (or an equivalent) will
+ // be delivered.
+ // Android storage implementations are required to execute callbacks inline, so this code
+ // all executes synchronously.
+ resources.getLogger().fine("Message for unstarted Ticl; rewrite state");
+ resources.getStorage().readKey(InvalidationClientCore.CLIENT_TOKEN_KEY,
+ new Callback<SimplePair<Status, byte[]>>() {
+ @Override
+ public void accept(SimplePair<Status, byte[]> result) {
+ byte[] stateBytes = result.second;
+ if (stateBytes == null) {
+ resources.getLogger().info("No persistent state found for client; not rewriting");
+ return;
+ }
+ // Create new state identical to the old state except with a cleared
+ // lastMessageSendTimeMs.
+ PersistentTiclState state = PersistenceUtils.deserializeState(
+ resources.getLogger(), stateBytes, digestFn);
+ if (state == null) {
+ resources.getLogger().warning("Ignoring invalid Ticl state: %s",
+ Bytes.toLazyCompactString(stateBytes));
+ return;
+ }
+ PersistentTiclState.Builder stateBuilder = state.toBuilder();
+ stateBuilder.lastMessageSendTimeMs = 0L;
+ state = stateBuilder.build();
+
+ // Serialize the new state and write it to storage.
+ byte[] newClientState = PersistenceUtils.serializeState(state, digestFn);
+ resources.getStorage().writeKey(InvalidationClientCore.CLIENT_TOKEN_KEY, newClientState,
+ new Callback<Status>() {
+ @Override
+ public void accept(Status status) {
+ if (status.getCode() != Status.Code.SUCCESS) {
+ resources.getLogger().warning(
+ "Failed saving rewritten persistent state to storage");
+ }
+ }
+ });
+ }
+ });
+ }
+
+ /**
+ * If a service is registered to handle them, forward invalidations received while the
+ * invalidation client is stopped.
+ */
+ private void maybeSendBackgroundInvalidationIntent(byte[] message) {
+ // If a service is registered to receive background invalidations, parse the message to see if
+ // any of them should be forwarded.
+ AndroidTiclManifest manifest = new AndroidTiclManifest(getApplicationContext());
+ String backgroundServiceClass =
+ manifest.getBackgroundInvalidationListenerServiceClass();
+ if (backgroundServiceClass != null) {
+ try {
+ ServerToClientMessage s2cMessage = ServerToClientMessage.parseFrom(message);
+ if (s2cMessage.getNullableInvalidationMessage() != null) {
+ Intent intent = ProtocolIntents.newBackgroundInvalidationIntent(
+ s2cMessage.getNullableInvalidationMessage());
+ intent.setClassName(getApplicationContext(), backgroundServiceClass);
+ startService(intent);
+ }
+ } catch (ValidationException exception) {
+ resources.getLogger().info("Failed to parse message: %s", exception.getMessage());
+ }
+ }
+ }
+
+ /** Handles a request to call a particular recurring task on the Ticl. */
+ private void handleSchedulerEvent(byte[] schedulerEventBytes) {
+ // Parse and validate the request.
+ final AndroidSchedulerEvent event;
+ try {
+ event = AndroidSchedulerEvent.parseFrom(schedulerEventBytes);
+ } catch (ValidationException exception) {
+ resources.getLogger().warning("Failed parsing SchedulerEvent from %s: %s",
+ Bytes.toLazyCompactString(schedulerEventBytes), exception.getMessage());
+ return;
+ }
+
+ resources.getLogger().fine("Handle scheduler event: %s", event);
+
+ // Restore the appropriate Ticl.
+ AndroidInvalidationClientImpl ticl = TiclStateManager.restoreTicl(this, resources);
+
+ // If the Ticl didn't exist, drop the event.
+ if (ticl == null) {
+ resources.getLogger().fine("Dropping event %s; Ticl state does not exist",
+ event.getEventName());
+ return;
+ }
+
+ // Invoke the appropriate event.
+ AndroidInternalScheduler ticlScheduler =
+ (AndroidInternalScheduler) resources.getInternalScheduler();
+ ticlScheduler.handleSchedulerEvent(event);
+
+ // Save the Ticl state to persistent storage.
+ TiclStateManager.saveTicl(this, resources.getLogger(), ticl);
+ }
+
+ /**
+ * Returns the existing Ticl from persistent storage, or {@code null} if it does not exist.
+ * If it does not exist, raises an error to the listener. This function should be used
+ * only when loading a Ticl in response to a client-application call, since it raises an error
+ * back to the application.
+ */
+ private AndroidInvalidationClientImpl loadExistingTicl() {
+ AndroidInvalidationClientImpl ticl = TiclStateManager.restoreTicl(this, resources);
+ if (ticl == null) {
+ informListenerOfPermanentError("Client does not exist on downcall");
+ }
+ return ticl;
+ }
+
+ /** Informs the listener of a non-retryable {@code error}. */
+ private void informListenerOfPermanentError(final String error) {
+ ErrorInfo errorInfo = ErrorInfo.newInstance(0, false, error, null);
+ Intent errorIntent = ProtocolIntents.ListenerUpcalls.newErrorIntent(errorInfo);
+ IntentForwardingListener.issueIntent(this, errorIntent);
+ }
+
+ /** Returns the resources used for the current Ticl. */
+ AndroidResources getSystemResourcesForTest() {
+ return resources;
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698