1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.chrome.browser.invalidation;
6
7import android.accounts.Account;
8import android.content.Context;
9import android.content.Intent;
10
11import org.chromium.base.ApplicationState;
12import org.chromium.base.ApplicationStatus;
13import org.chromium.base.CalledByNative;
14import org.chromium.base.VisibleForTesting;
15import org.chromium.sync.internal_api.pub.base.ModelType;
16import org.chromium.sync.notifier.InvalidationClientNameProvider;
17import org.chromium.sync.notifier.InvalidationIntentProtocol;
18import org.chromium.sync.notifier.InvalidationPreferences;
19import org.chromium.sync.notifier.InvalidationService;
20import org.chromium.sync.notifier.SyncStatusHelper;
21
22import java.util.Set;
23
24/**
25 * Controller used to send start, stop, and registration-change commands to the invalidation
26 * client library used by Sync.
27 */
28public class InvalidationController implements ApplicationStatus.ApplicationStateListener {
29    private static final Object LOCK = new Object();
30
31    private static InvalidationController sInstance;
32
33    private final Context mContext;
34
35    /**
36     * Sets the types for which the client should register for notifications.
37     *
38     * @param account  Account of the user.
39     * @param allTypes If {@code true}, registers for all types, and {@code types} is ignored
40     * @param types    Set of types for which to register. Ignored if {@code allTypes == true}.
41     */
42    public void setRegisteredTypes(Account account, boolean allTypes, Set<ModelType> types) {
43        Intent registerIntent =
44                InvalidationIntentProtocol.createRegisterIntent(account, allTypes, types);
45        registerIntent.setClass(mContext, InvalidationService.class);
46        mContext.startService(registerIntent);
47    }
48
49    /**
50     * Reads all stored preferences and calls
51     * {@link #setRegisteredTypes(android.accounts.Account, boolean, java.util.Set)} with the stored
52     * values, refreshing the set of types with {@code types}. It can be used on startup of Chrome
53     * to ensure we always have a set of registrations consistent with the native code.
54     * @param types    Set of types for which to register.
55     */
56    public void refreshRegisteredTypes(Set<ModelType> types) {
57        InvalidationPreferences invalidationPreferences = new InvalidationPreferences(mContext);
58        Set<String> savedSyncedTypes = invalidationPreferences.getSavedSyncedTypes();
59        Account account = invalidationPreferences.getSavedSyncedAccount();
60        boolean allTypes = savedSyncedTypes != null &&
61                savedSyncedTypes.contains(ModelType.ALL_TYPES_TYPE);
62        setRegisteredTypes(account, allTypes, types);
63    }
64
65    /**
66     * Sets object ids for which the client should register for notification. This is intended for
67     * registering non-Sync types; Sync types are registered with {@code setRegisteredTypes}.
68     *
69     * @param objectSources The sources of the objects.
70     * @param objectNames   The names of the objects.
71     */
72    @CalledByNative
73    public void setRegisteredObjectIds(int[] objectSources, String[] objectNames) {
74        InvalidationPreferences invalidationPreferences = new InvalidationPreferences(mContext);
75        Account account = invalidationPreferences.getSavedSyncedAccount();
76        Intent registerIntent =
77                InvalidationIntentProtocol.createRegisterIntent(
78                        account, objectSources, objectNames);
79        registerIntent.setClass(mContext, InvalidationService.class);
80        mContext.startService(registerIntent);
81    }
82
83    /**
84     * Starts the invalidation client.
85     */
86    public void start() {
87        Intent intent = new Intent(mContext, InvalidationService.class);
88        mContext.startService(intent);
89    }
90
91    /**
92     * Stops the invalidation client.
93     */
94    public void stop() {
95        Intent intent = new Intent(mContext, InvalidationService.class);
96        intent.putExtra(InvalidationIntentProtocol.EXTRA_STOP, true);
97        mContext.startService(intent);
98    }
99
100    /**
101     * Returns the instance that will use {@code context} to issue intents.
102     *
103     * Calling this method will create the instance if it does not yet exist.
104     */
105    @CalledByNative
106    public static InvalidationController get(Context context) {
107        synchronized (LOCK) {
108            if (sInstance == null) {
109                sInstance = new InvalidationController(context);
110            }
111            return sInstance;
112        }
113    }
114
115    /**
116     * Creates an instance using {@code context} to send intents.
117     */
118    @VisibleForTesting
119    InvalidationController(Context context) {
120        Context appContext = context.getApplicationContext();
121        if (appContext == null) throw new NullPointerException("Unable to get application context");
122        mContext = appContext;
123        ApplicationStatus.registerApplicationStateListener(this);
124    }
125
126    @Override
127    public void onApplicationStateChange(int newState) {
128        if (SyncStatusHelper.get(mContext).isSyncEnabled()) {
129            if (newState == ApplicationState.HAS_RUNNING_ACTIVITIES) {
130                start();
131            } else if (newState == ApplicationState.HAS_PAUSED_ACTIVITIES) {
132                stop();
133            }
134        }
135    }
136
137    /**
138     * Fetches the Invalidator client name.
139     *
140     * Note that there is a naming discrepancy here.  In C++, we refer to the invalidation client
141     * identifier that is unique for every invalidation client instance in an account as the client
142     * ID.  In Java, we call it the client name.
143     */
144    @CalledByNative
145    public byte[] getInvalidatorClientId() {
146        return InvalidationClientNameProvider.get().getInvalidatorClientName();
147    }
148}
149