<commit_before>// Copyright (c) 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.common.collect.Lists;
import com.google.common.collect.Sets;
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.ActivityStatus;
import org.chromium.sync.internal_api.pub.base.ModelType;
import org.chromium.sync.notifier.InvalidationController.IntentProtocol;
import org.chromium.sync.notifier.InvalidationPreferences.EditContext;
import org.chromium.sync.signin.AccountManagerHelper;

import java.util.Collection;
import java.util.Collections;
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 <EMAIL>
 */
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.class.getSimpleName();

    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(IntentProtocol.EXTRA_ACCOUNT) ?
                (Account) intent.getParcelableExtra(IntentProtocol.EXTRA_ACCOUNT) : null;
        ensureAccount(account);
        ensureClientStartState();

        // Handle the intent.
        if (IntentProtocol.isStop(intent) && sIsClientStarted) {
            // If the intent requests that the client be stopped, stop it.
            stopClient();
        } else if (IntentProtocol.isRegisteredTypesChange(intent)) {
            // If the intent requests a change in registrations, change them.
            List<String> regTypes =
                    intent.getStringArrayListExtra(IntentProtocol.EXTRA_REGISTERED_TYPES);
            setRegisteredTypes(Sets.newHashSet(regTypes));
        } 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) {
        requestSync(invalidation.getObjectId(), invalidation.getVersion(),
                new String(invalidation.getPayload()));
        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 = Lists.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 = Lists.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 reissueRegistrations(byte[] clientId) {
        Set<ObjectId> desiredRegistrations = readRegistrationsFromPrefs();
        if (!desiredRegistrations.isEmpty()) {
            register(clientId, desiredRegistrations);
        }
        // TODO(dsmyers): [misc] reissue registrations is not guaranteed to be called by spec,
        // although it will happen in practice. This code should be changed not to rely on it.
        // Bug: https://code.google.com/p/chromium/issues/detail?id=172390
        setClientId(clientId);
    }

    @Override
    public void requestAuthToken(PendingIntent pendingIntent, @Nullable String invalidAuthToken) {<commit_msg>Fix InvalidationService build error<commit_after>// Copyright (c) 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.common.collect.Lists;
import com.google.common.collect.Sets;
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.ActivityStatus;
import org.chromium.sync.internal_api.pub.base.ModelType;
import org.chromium.sync.notifier.InvalidationController.IntentProtocol;
import org.chromium.sync.notifier.InvalidationPreferences.EditContext;
import org.chromium.sync.signin.AccountManagerHelper;

import java.util.Collection;
import java.util.Collections;
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 <EMAIL>
 */
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.class.getSimpleName();

    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(IntentProtocol.EXTRA_ACCOUNT) ?
                (Account) intent.getParcelableExtra(IntentProtocol.EXTRA_ACCOUNT) : null;
        ensureAccount(account);
        ensureClientStartState();

        // Handle the intent.
        if (IntentProtocol.isStop(intent) && sIsClientStarted) {
            // If the intent requests that the client be stopped, stop it.
            stopClient();
        } else if (IntentProtocol.isRegisteredTypesChange(intent)) {
            // If the intent requests a change in registrations, change them.
            List<String> regTypes =
                    intent.getStringArrayListExtra(IntentProtocol.EXTRA_REGISTERED_TYPES);
            setRegisteredTypes(Sets.newHashSet(regTypes));
        } 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) {
        requestSync(invalidation.getObjectId(), invalidation.getVersion(),
                new String(invalidation.getPayload()));
        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 = Lists.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 = Lists.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) {
    }

    @Override
    public void reissueRegistrations(byte[] clientId) {
        Set<ObjectId> desiredRegistrations = readRegistrationsFromPrefs();
        if (!desiredRegistrations.isEmpty()) {
            register(clientId, desiredRegistrations);
        }
        // TODO(dsmyers): [misc] reissue registrations is not guaranteed to be called by spec,
        // although it will happen in practice. This code should be changed not to rely on it.
        // Bug: https://code.google.com/p/chromium/issues/detail?id=172390
        setClientId(clientId);
    }

    @Override
    public void requestAuthToken(PendingIntent pendingIntent, @Nullable String invalidAuthToken) {
{'fraction_non_alphanumeric': 0.08458886618998979, 'fraction_numerical': 0.0012768130745658835, 'mean_word_length': 3.024922918807811, 'pattern_counts': {'":': 4, '<': 19, '<?xml version=': 0, '>': 19, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5014187', 'n_tokens_mistral': 4118, 'n_tokens_neox': 4026, 'n_words': 1312}