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

Unified Diff: sync/android/java/src/org/chromium/sync/notifier/InvalidationService.java

Issue 459513002: Massive refactor of the Android invalidation code. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase. Created 6 years, 3 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: sync/android/java/src/org/chromium/sync/notifier/InvalidationService.java
diff --git a/sync/android/java/src/org/chromium/sync/notifier/InvalidationService.java b/sync/android/java/src/org/chromium/sync/notifier/InvalidationService.java
deleted file mode 100644
index 37a8e6a8fdb0bb1e7ffbdc5613b786f176f37941..0000000000000000000000000000000000000000
--- a/sync/android/java/src/org/chromium/sync/notifier/InvalidationService.java
+++ /dev/null
@@ -1,507 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.sync.notifier;
-
-import android.accounts.Account;
-import android.app.PendingIntent;
-import android.content.ContentResolver;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.ipc.invalidation.external.client.InvalidationListener.RegistrationState;
-import com.google.ipc.invalidation.external.client.contrib.AndroidListener;
-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.protos.ipc.invalidation.Types.ClientType;
-
-import org.chromium.base.ApplicationStatus;
-import org.chromium.base.CollectionUtil;
-import org.chromium.sync.internal_api.pub.base.ModelType;
-import org.chromium.sync.notifier.InvalidationPreferences.EditContext;
-import org.chromium.sync.signin.AccountManagerHelper;
-import org.chromium.sync.signin.ChromeSigninController;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Random;
-import java.util.Set;
-
-import javax.annotation.Nullable;
-
-/**
- * Service that controls notifications for sync.
- * <p>
- * This service serves two roles. On the one hand, it is a client for the notification system
- * used to trigger sync. It receives invalidations and converts them into
- * {@link ContentResolver#requestSync} calls, and it supplies the notification system with the set
- * of desired registrations when requested.
- * <p>
- * On the other hand, this class is controller for the notification system. It starts it and stops
- * it, and it requests that it perform (un)registrations as the set of desired sync types changes.
- * <p>
- * This class is an {@code IntentService}. All methods are assumed to be executing on its single
- * execution thread.
- *
- * @author dsmyers@google.com
- */
-public class InvalidationService extends AndroidListener {
- /* This class must be public because it is exposed as a service. */
-
- /** Notification client typecode. */
- @VisibleForTesting
- static final int CLIENT_TYPE = ClientType.Type.CHROME_SYNC_ANDROID_VALUE;
-
- private static final String TAG = "InvalidationService";
-
- private static final Random RANDOM = new Random();
-
- /**
- * Whether the underlying notification client has been started. This boolean is updated when a
- * start or stop intent is issued to the underlying client, not when the intent is actually
- * processed.
- */
- private static boolean sIsClientStarted;
-
- /**
- * The id of the client in use, if any. May be {@code null} if {@link #sIsClientStarted} is
- * true if the client has not yet gone ready.
- */
- @Nullable private static byte[] sClientId;
-
- @Override
- public void onHandleIntent(Intent intent) {
- // Ensure that a client is or is not running, as appropriate, and that it is for the
- // correct account. ensureAccount will stop the client if account is non-null and doesn't
- // match the stored account. Then, if a client should be running, ensureClientStartState
- // will start a new one if needed. I.e., these two functions work together to restart the
- // client when the account changes.
- Account account = intent.hasExtra(InvalidationIntentProtocol.EXTRA_ACCOUNT) ?
- (Account) intent.getParcelableExtra(InvalidationIntentProtocol.EXTRA_ACCOUNT)
- : null;
-
- ensureAccount(account);
- ensureClientStartState();
-
- // Handle the intent.
- if (InvalidationIntentProtocol.isStop(intent) && sIsClientStarted) {
- // If the intent requests that the client be stopped, stop it.
- stopClient();
- } else if (InvalidationIntentProtocol.isRegisteredTypesChange(intent)) {
- // If the intent requests a change in registrations, change them.
- List<String> regTypes = intent.getStringArrayListExtra(
- InvalidationIntentProtocol.EXTRA_REGISTERED_TYPES);
- setRegisteredTypes(regTypes != null ? new HashSet<String>(regTypes) : null,
- InvalidationIntentProtocol.getRegisteredObjectIds(intent));
- } else {
- // Otherwise, we don't recognize the intent. Pass it to the notification client service.
- super.onHandleIntent(intent);
- }
- }
-
- @Override
- public void invalidate(Invalidation invalidation, byte[] ackHandle) {
- byte[] payload = invalidation.getPayload();
- String payloadStr = (payload == null) ? null : new String(payload);
- requestSync(invalidation.getObjectId(), invalidation.getVersion(), payloadStr);
- acknowledge(ackHandle);
- }
-
- @Override
- public void invalidateUnknownVersion(ObjectId objectId, byte[] ackHandle) {
- requestSync(objectId, null, null);
- acknowledge(ackHandle);
- }
-
- @Override
- public void invalidateAll(byte[] ackHandle) {
- requestSync(null, null, null);
- acknowledge(ackHandle);
- }
-
- @Override
- public void informRegistrationFailure(
- byte[] clientId, ObjectId objectId, boolean isTransient, String errorMessage) {
- Log.w(TAG, "Registration failure on " + objectId + " ; transient = " + isTransient
- + ": " + errorMessage);
- if (isTransient) {
- // Retry immediately on transient failures. The base AndroidListener will handle
- // exponential backoff if there are repeated failures.
- List<ObjectId> objectIdAsList = CollectionUtil.newArrayList(objectId);
- if (readRegistrationsFromPrefs().contains(objectId)) {
- register(clientId, objectIdAsList);
- } else {
- unregister(clientId, objectIdAsList);
- }
- }
- }
-
- @Override
- public void informRegistrationStatus(
- byte[] clientId, ObjectId objectId, RegistrationState regState) {
- Log.d(TAG, "Registration status for " + objectId + ": " + regState);
- List<ObjectId> objectIdAsList = CollectionUtil.newArrayList(objectId);
- boolean registrationisDesired = readRegistrationsFromPrefs().contains(objectId);
- if (regState == RegistrationState.REGISTERED) {
- if (!registrationisDesired) {
- Log.i(TAG, "Unregistering for object we're no longer interested in");
- unregister(clientId, objectIdAsList);
- }
- } else {
- if (registrationisDesired) {
- Log.i(TAG, "Registering for an object");
- register(clientId, objectIdAsList);
- }
- }
- }
-
- @Override
- public void informError(ErrorInfo errorInfo) {
- Log.w(TAG, "Invalidation client error:" + errorInfo);
- if (!errorInfo.isTransient() && sIsClientStarted) {
- // It is important not to stop the client if it is already stopped. Otherwise, the
- // possibility exists to go into an infinite loop if the stop call itself triggers an
- // error (e.g., because no client actually exists).
- stopClient();
- }
- }
-
- @Override
- public void ready(byte[] clientId) {
- setClientId(clientId);
-
- // We might have accumulated some registrations to do while we were waiting for the client
- // to become ready.
- reissueRegistrations(clientId);
- }
-
- @Override
- public void reissueRegistrations(byte[] clientId) {
- Set<ObjectId> desiredRegistrations = readRegistrationsFromPrefs();
- if (!desiredRegistrations.isEmpty()) {
- register(clientId, desiredRegistrations);
- }
- }
-
- @Override
- public void requestAuthToken(final PendingIntent pendingIntent,
- @Nullable String invalidAuthToken) {
- @Nullable Account account = ChromeSigninController.get(this).getSignedInUser();
- if (account == null) {
- // This should never happen, because this code should only be run if a user is
- // signed-in.
- Log.w(TAG, "No signed-in user; cannot send message to data center");
- return;
- }
-
- // Attempt to retrieve a token for the user. This method will also invalidate
- // invalidAuthToken if it is non-null.
- AccountManagerHelper.get(this).getNewAuthTokenFromForeground(
- account, invalidAuthToken, getOAuth2ScopeWithType(),
- new AccountManagerHelper.GetAuthTokenCallback() {
- @Override
- public void tokenAvailable(String token) {
- if (token != null) {
- setAuthToken(InvalidationService.this.getApplicationContext(),
- pendingIntent, token, getOAuth2ScopeWithType());
- }
- }
- });
- }
-
- @Override
- public void writeState(byte[] data) {
- InvalidationPreferences invPreferences = new InvalidationPreferences(this);
- EditContext editContext = invPreferences.edit();
- invPreferences.setInternalNotificationClientState(editContext, data);
- invPreferences.commit(editContext);
- }
-
- @Override
- @Nullable public byte[] readState() {
- return new InvalidationPreferences(this).getInternalNotificationClientState();
- }
-
- /**
- * Ensures that the client is running or not running as appropriate, based on the value of
- * {@link #shouldClientBeRunning}.
- */
- private void ensureClientStartState() {
- final boolean shouldClientBeRunning = shouldClientBeRunning();
- if (!shouldClientBeRunning && sIsClientStarted) {
- // Stop the client if it should not be running and is.
- stopClient();
- } else if (shouldClientBeRunning && !sIsClientStarted) {
- // Start the client if it should be running and isn't.
- startClient();
- }
- }
-
- /**
- * If {@code intendedAccount} is non-{@null} and differs from the account stored in preferences,
- * then stops the existing client (if any) and updates the stored account.
- */
- private void ensureAccount(@Nullable Account intendedAccount) {
- if (intendedAccount == null) {
- return;
- }
- InvalidationPreferences invPrefs = new InvalidationPreferences(this);
- if (!intendedAccount.equals(invPrefs.getSavedSyncedAccount())) {
- if (sIsClientStarted) {
- stopClient();
- }
- setAccount(intendedAccount);
- }
- }
-
- /**
- * Starts a new client, destroying any existing client. {@code owningAccount} is the account
- * of the user for which the client is being created; it will be persisted using
- * {@link InvalidationPreferences#setAccount}.
- */
- private void startClient() {
- byte[] clientName = InvalidationClientNameProvider.get().getInvalidatorClientName();
- Intent startIntent = AndroidListener.createStartIntent(this, CLIENT_TYPE, clientName);
- startService(startIntent);
- setIsClientStarted(true);
- }
-
- /** Stops the notification client. */
- private void stopClient() {
- startService(AndroidListener.createStopIntent(this));
- setIsClientStarted(false);
- setClientId(null);
- }
-
- /** Sets the saved sync account in {@link InvalidationPreferences} to {@code owningAccount}. */
- private void setAccount(Account owningAccount) {
- InvalidationPreferences invPrefs = new InvalidationPreferences(this);
- EditContext editContext = invPrefs.edit();
- invPrefs.setAccount(editContext, owningAccount);
- invPrefs.commit(editContext);
- }
-
- /**
- * Reads the saved sync types from storage (if any) and returns a set containing the
- * corresponding object ids.
- */
- private Set<ObjectId> readSyncRegistrationsFromPrefs() {
- Set<String> savedTypes = new InvalidationPreferences(this).getSavedSyncedTypes();
- if (savedTypes == null) return Collections.emptySet();
- else return ModelType.syncTypesToObjectIds(savedTypes);
- }
-
- /**
- * Reads the saved non-sync object ids from storage (if any) and returns a set containing the
- * corresponding object ids.
- */
- private Set<ObjectId> readNonSyncRegistrationsFromPrefs() {
- Set<ObjectId> objectIds = new InvalidationPreferences(this).getSavedObjectIds();
- if (objectIds == null) return Collections.emptySet();
- else return objectIds;
- }
-
- /**
- * Reads the object registrations from storage (if any) and returns a set containing the
- * corresponding object ids.
- */
- @VisibleForTesting
- Set<ObjectId> readRegistrationsFromPrefs() {
- return joinRegistrations(readSyncRegistrationsFromPrefs(),
- readNonSyncRegistrationsFromPrefs());
- }
-
- /**
- * Join Sync object registrations with non-Sync object registrations to get the full set of
- * desired object registrations.
- */
- private static Set<ObjectId> joinRegistrations(Set<ObjectId> syncRegistrations,
- Set<ObjectId> nonSyncRegistrations) {
- if (nonSyncRegistrations.isEmpty()) {
- return syncRegistrations;
- }
- if (syncRegistrations.isEmpty()) {
- return nonSyncRegistrations;
- }
- Set<ObjectId> registrations = new HashSet<ObjectId>(
- syncRegistrations.size() + nonSyncRegistrations.size());
- registrations.addAll(syncRegistrations);
- registrations.addAll(nonSyncRegistrations);
- return registrations;
- }
-
- /**
- * Sets the types for which notifications are required to {@code syncTypes}. {@code syncTypes}
- * is either a list of specific types or the special wildcard type
- * {@link ModelType#ALL_TYPES_TYPE}. Also registers for additional objects specified by
- * {@code objectIds}. Either parameter may be null if the corresponding registrations are not
- * changing.
- * <p>
- * @param syncTypes
- */
- private void setRegisteredTypes(Set<String> syncTypes, Set<ObjectId> objectIds) {
- // If we have a ready client and will be making registration change calls on it, then
- // read the current registrations from preferences before we write the new values, so that
- // we can take the diff of the two registration sets and determine which registration change
- // calls to make.
- Set<ObjectId> existingSyncRegistrations = (sClientId == null) ?
- null : readSyncRegistrationsFromPrefs();
- Set<ObjectId> existingNonSyncRegistrations = (sClientId == null) ?
- null : readNonSyncRegistrationsFromPrefs();
-
- // Write the new sync types/object ids to preferences. We do not expand the syncTypes to
- // take into account the ALL_TYPES_TYPE at this point; we want to persist the wildcard
- // unexpanded.
- InvalidationPreferences prefs = new InvalidationPreferences(this);
- EditContext editContext = prefs.edit();
- if (syncTypes != null) {
- prefs.setSyncTypes(editContext, syncTypes);
- }
- if (objectIds != null) {
- prefs.setObjectIds(editContext, objectIds);
- }
- prefs.commit(editContext);
-
- // If we do not have a ready invalidation client, we cannot change its registrations, so
- // return. Later, when the client is ready, we will supply the new registrations.
- if (sClientId == null) {
- return;
- }
-
- // We do have a ready client. Unregister any existing registrations not present in the
- // new set and register any elements in the new set not already present. This call does
- // expansion of the ALL_TYPES_TYPE wildcard.
- // NOTE: syncTypes MUST NOT be used below this line, since it contains an unexpanded
- // wildcard.
- // When computing the desired set of object ids, if only sync types were provided, then
- // keep the existing non-sync types, and vice-versa.
- Set<ObjectId> desiredSyncRegistrations = syncTypes != null ?
- ModelType.syncTypesToObjectIds(syncTypes) : existingSyncRegistrations;
- Set<ObjectId> desiredNonSyncRegistrations = objectIds != null ?
- objectIds : existingNonSyncRegistrations;
- Set<ObjectId> desiredRegistrations = joinRegistrations(desiredNonSyncRegistrations,
- desiredSyncRegistrations);
- Set<ObjectId> existingRegistrations = joinRegistrations(existingNonSyncRegistrations,
- existingSyncRegistrations);
-
- Set<ObjectId> unregistrations = new HashSet<ObjectId>();
- Set<ObjectId> registrations = new HashSet<ObjectId>();
- computeRegistrationOps(existingRegistrations, desiredRegistrations,
- registrations, unregistrations);
- unregister(sClientId, unregistrations);
- register(sClientId, registrations);
- }
-
- /**
- * Computes the set of (un)registrations to perform so that the registrations active in the
- * Ticl will be {@code desiredRegs}, given that {@existingRegs} already exist.
- *
- * @param regAccumulator registrations to perform
- * @param unregAccumulator unregistrations to perform.
- */
- @VisibleForTesting
- static void computeRegistrationOps(Set<ObjectId> existingRegs, Set<ObjectId> desiredRegs,
- Set<ObjectId> regAccumulator, Set<ObjectId> unregAccumulator) {
-
- // Registrations to do are elements in the new set but not the old set.
- regAccumulator.addAll(desiredRegs);
- regAccumulator.removeAll(existingRegs);
-
- // Unregistrations to do are elements in the old set but not the new set.
- unregAccumulator.addAll(existingRegs);
- unregAccumulator.removeAll(desiredRegs);
- }
-
- /**
- * Requests that the sync system perform a sync.
- *
- * @param objectId the object that changed, if known.
- * @param version the version of the object that changed, if known.
- * @param payload the payload of the change, if known.
- */
- private void requestSync(@Nullable ObjectId objectId, @Nullable Long version,
- @Nullable String payload) {
- // Construct the bundle to supply to the native sync code.
- Bundle bundle = new Bundle();
- if (objectId == null && version == null && payload == null) {
- // Use an empty bundle in this case for compatibility with the v1 implementation.
- } else {
- if (objectId != null) {
- bundle.putInt("objectSource", objectId.getSource());
- bundle.putString("objectId", new String(objectId.getName()));
- }
- // We use "0" as the version if we have an unknown-version invalidation. This is OK
- // because the native sync code special-cases zero and always syncs for invalidations at
- // that version (Tango defines a special UNKNOWN_VERSION constant with this value).
- bundle.putLong("version", (version == null) ? 0 : version);
- bundle.putString("payload", (payload == null) ? "" : payload);
- }
- Account account = ChromeSigninController.get(this).getSignedInUser();
- String contractAuthority = SyncStatusHelper.get(this).getContractAuthority();
- requestSyncFromContentResolver(bundle, account, contractAuthority);
- }
-
- /**
- * Calls {@link ContentResolver#requestSync(Account, String, Bundle)} to trigger a sync. Split
- * into a separate method so that it can be overriden in tests.
- */
- @VisibleForTesting
- void requestSyncFromContentResolver(
- Bundle bundle, Account account, String contractAuthority) {
- Log.d(TAG, "Request sync: " + account + " / " + contractAuthority + " / "
- + bundle.keySet());
- ContentResolver.requestSync(account, contractAuthority, bundle);
- }
-
- /**
- * Returns whether the notification client should be running, i.e., whether Chrome is in the
- * foreground and sync is enabled.
- */
- @VisibleForTesting
- boolean shouldClientBeRunning() {
- return isSyncEnabled() && isChromeInForeground();
- }
-
- /** Returns whether sync is enabled. LLocal method so it can be overridden in tests. */
- @VisibleForTesting
- boolean isSyncEnabled() {
- return SyncStatusHelper.get(getApplicationContext()).isSyncEnabled();
- }
-
- /**
- * Returns whether Chrome is in the foreground. Local method so it can be overridden in tests.
- */
- @VisibleForTesting
- boolean isChromeInForeground() {
- return ApplicationStatus.hasVisibleActivities();
- }
-
- /** Returns whether the notification client has been started, for tests. */
- @VisibleForTesting
- static boolean getIsClientStartedForTest() {
- return sIsClientStarted;
- }
-
- /** Returns the notification client id, for tests. */
- @VisibleForTesting
- @Nullable static byte[] getClientIdForTest() {
- return sClientId;
- }
-
- private static String getOAuth2ScopeWithType() {
- return "oauth2:" + SyncStatusHelper.CHROME_SYNC_OAUTH2_SCOPE;
- }
-
- private static void setClientId(byte[] clientId) {
- sClientId = clientId;
- }
-
- private static void setIsClientStarted(boolean isStarted) {
- sIsClientStarted = isStarted;
- }
-}

Powered by Google App Engine
This is Rietveld 408576698