ProfileSyncService.java revision 58537e28ecd584eab876aee8be7156509866d23a
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.sync;
6
7import android.content.Context;
8import android.util.Log;
9
10import com.google.common.annotations.VisibleForTesting;
11
12import org.chromium.base.CalledByNative;
13import org.chromium.base.ThreadUtils;
14import org.chromium.chrome.browser.identity.UniqueIdentificationGenerator;
15import org.chromium.sync.internal_api.pub.SyncDecryptionPassphraseType;
16import org.chromium.sync.internal_api.pub.base.ModelType;
17
18import java.util.HashSet;
19import java.util.List;
20import java.util.Set;
21import java.util.concurrent.CopyOnWriteArrayList;
22
23/**
24 * Android wrapper of the ProfileSyncService which provides access from the Java layer.
25 * <p/>
26 * This class mostly wraps native classes, but it make a few business logic decisions, both in Java
27 * and in native.
28 * <p/>
29 * Only usable from the UI thread as the native ProfileSyncService requires its access to be in the
30 * UI thread.
31 * <p/>
32 * See chrome/browser/sync/profile_sync_service.h for more details.
33 */
34public class ProfileSyncService {
35
36    public interface SyncStateChangedListener {
37        // Invoked when the underlying sync status has changed.
38        public void syncStateChanged();
39    }
40
41    private static final String TAG = "ProfileSyncService";
42
43    @VisibleForTesting
44    public static final String SESSION_TAG_PREFIX = "session_sync";
45
46    private static ProfileSyncService sSyncSetupManager;
47
48    @VisibleForTesting
49    protected final Context mContext;
50
51    // Sync state changes more often than listeners are added/removed, so using CopyOnWrite.
52    private final List<SyncStateChangedListener> mListeners =
53            new CopyOnWriteArrayList<SyncStateChangedListener>();
54
55    // Native ProfileSyncServiceAndroid object. Can not be final since we set it to 0 in destroy().
56    private final int mNativeProfileSyncServiceAndroid;
57
58    /**
59     * A helper method for retrieving the application-wide SyncSetupManager.
60     * <p/>
61     * Can only be accessed on the main thread.
62     *
63     * @param context the ApplicationContext is retrieved from the context used as an argument.
64     * @return a singleton instance of the SyncSetupManager
65     */
66    public static ProfileSyncService get(Context context) {
67        ThreadUtils.assertOnUiThread();
68        if (sSyncSetupManager == null) {
69            sSyncSetupManager = new ProfileSyncService(context);
70        }
71        return sSyncSetupManager;
72    }
73
74    /**
75     * This is called pretty early in our application. Avoid any blocking operations here.
76     */
77    private ProfileSyncService(Context context) {
78        ThreadUtils.assertOnUiThread();
79        // We should store the application context, as we outlive any activity which may create us.
80        mContext = context.getApplicationContext();
81
82        // This may cause us to create ProfileSyncService even if sync has not
83        // been set up, but ProfileSyncService::Startup() won't be called until
84        // credentials are available.
85        mNativeProfileSyncServiceAndroid = nativeInit();
86    }
87
88    @CalledByNative
89    private static int getProfileSyncServiceAndroid(Context context) {
90        return get(context).mNativeProfileSyncServiceAndroid;
91    }
92
93    /**
94     * If we are currently in the process of setting up sync, this method clears the
95     * sync setup in progress flag.
96     */
97    @VisibleForTesting
98    public void finishSyncFirstSetupIfNeeded() {
99        if (isFirstSetupInProgress()) {
100            setSyncSetupCompleted();
101            setSetupInProgress(false);
102        }
103    }
104
105    public void signOut() {
106        nativeSignOutSync(mNativeProfileSyncServiceAndroid);
107    }
108
109    /**
110     * Signs in to sync, using the currently signed-in account.
111     */
112    public void syncSignIn() {
113        nativeSignInSync(mNativeProfileSyncServiceAndroid);
114        // Notify listeners right away that the sync state has changed (native side does not do
115        // this)
116        syncStateChanged();
117    }
118
119    /**
120     * Signs in to sync, using the existing auth token.
121     */
122    @Deprecated
123    public void syncSignIn(String account) {
124        syncSignIn();
125    }
126
127    /**
128     * Signs in to sync.
129     *
130     * @param account   The username of the account that is signing in.
131     * @param authToken Not used. ProfileSyncService switched to OAuth2 tokens.
132     * Deprecated. Use syncSignIn instead.
133     */
134    @Deprecated
135    public void syncSignInWithAuthToken(String account, String authToken) {
136        syncSignIn(account);
137    }
138
139    public void requestSyncFromNativeChrome(
140            int objectSource, String objectId, long version, String payload) {
141        ThreadUtils.assertOnUiThread();
142        nativeNudgeSyncer(
143                mNativeProfileSyncServiceAndroid, objectSource, objectId, version, payload);
144    }
145
146    public void requestSyncFromNativeChromeForAllTypes() {
147        ThreadUtils.assertOnUiThread();
148        nativeNudgeSyncerForAllTypes(mNativeProfileSyncServiceAndroid);
149    }
150
151    /**
152     * Nudge the syncer to start a new sync cycle.
153     */
154    @VisibleForTesting
155    public void requestSyncCycleForTest() {
156        ThreadUtils.assertOnUiThread();
157        requestSyncFromNativeChromeForAllTypes();
158    }
159
160    public String querySyncStatus() {
161        ThreadUtils.assertOnUiThread();
162        return nativeQuerySyncStatusSummary(mNativeProfileSyncServiceAndroid);
163    }
164
165    /**
166     * Sets the the machine tag used by session sync to a unique value.
167     */
168    public void setSessionsId(UniqueIdentificationGenerator generator) {
169        ThreadUtils.assertOnUiThread();
170        String uniqueTag = generator.getUniqueId(null);
171        if (uniqueTag.isEmpty()) {
172            Log.e(TAG, "Unable to get unique tag for sync. " +
173                    "This may lead to unexpected tab sync behavior.");
174            return;
175        }
176        String sessionTag = SESSION_TAG_PREFIX + uniqueTag;
177        if (!nativeSetSyncSessionsId(mNativeProfileSyncServiceAndroid, sessionTag)) {
178            Log.e(TAG, "Unable to write session sync tag. " +
179                    "This may lead to unexpected tab sync behavior.");
180        }
181    }
182
183    /**
184     * Checks if a password or a passphrase is required for decryption of sync data.
185     * <p/>
186     * Returns NONE if the state is unavailable, or decryption passphrase/password is not required.
187     *
188     * @return the enum describing the decryption passphrase type required
189     */
190    public SyncDecryptionPassphraseType getSyncDecryptionPassphraseTypeIfRequired() {
191        // ProfileSyncService::IsUsingSecondaryPassphrase() requires the sync backend to be
192        // initialized, and that happens just after OnPassphraseRequired(). Therefore, we need to
193        // guard that call with a check of the sync backend since we can not be sure which
194        // passphrase type we should tell the user we need.
195        // This is tracked in:
196        // http://code.google.com/p/chromium/issues/detail?id=108127
197        if (isSyncInitialized() && isPassphraseRequiredForDecryption()) {
198            return getSyncDecryptionPassphraseType();
199        }
200        return SyncDecryptionPassphraseType.NONE;
201    }
202
203    /**
204     * Returns the actual passphrase type being used for encryption. The sync backend must be
205     * running (isSyncInitialized() returns true) before calling this function.
206     * <p/>
207     * This method should only be used if you want to know the raw value. For checking whether we
208     * should ask the user for a passphrase, you should instead use
209     * getSyncDecryptionPassphraseTypeIfRequired().
210     */
211    public SyncDecryptionPassphraseType getSyncDecryptionPassphraseType() {
212        assert isSyncInitialized();
213        int passphraseType = nativeGetPassphraseType(mNativeProfileSyncServiceAndroid);
214        return SyncDecryptionPassphraseType.fromInternalValue(passphraseType);
215    }
216
217    public boolean isSyncKeystoreMigrationDone() {
218        assert isSyncInitialized();
219        return nativeIsSyncKeystoreMigrationDone(mNativeProfileSyncServiceAndroid);
220    }
221
222    /**
223     * Returns true if the current explicit passphrase time is defined.
224     */
225    public boolean hasExplicitPassphraseTime() {
226        assert isSyncInitialized();
227        return nativeHasExplicitPassphraseTime(mNativeProfileSyncServiceAndroid);
228    }
229
230    public String getSyncEnterGooglePassphraseBodyWithDateText() {
231        assert isSyncInitialized();
232        return nativeGetSyncEnterGooglePassphraseBodyWithDateText(mNativeProfileSyncServiceAndroid);
233    }
234
235    public String getSyncEnterCustomPassphraseBodyWithDateText() {
236        assert isSyncInitialized();
237        return nativeGetSyncEnterCustomPassphraseBodyWithDateText(mNativeProfileSyncServiceAndroid);
238    }
239
240    public String getCurrentSignedInAccountText() {
241        assert isSyncInitialized();
242        return nativeGetCurrentSignedInAccountText(mNativeProfileSyncServiceAndroid);
243    }
244
245    public String getSyncEnterCustomPassphraseBodyText() {
246        return nativeGetSyncEnterCustomPassphraseBodyText(mNativeProfileSyncServiceAndroid);
247    }
248
249    /**
250     * Checks if sync is currently set to use a custom passphrase. The sync backend must be running
251     * (isSyncInitialized() returns true) before calling this function.
252     *
253     * @return true if sync is using a custom passphrase.
254     */
255    public boolean isUsingSecondaryPassphrase() {
256        assert isSyncInitialized();
257        return nativeIsUsingSecondaryPassphrase(mNativeProfileSyncServiceAndroid);
258    }
259
260    /**
261     * Checks if we need a passphrase to decrypt a currently-enabled data type. This returns false
262     * if a passphrase is needed for a type that is not currently enabled.
263     *
264     * @return true if we need a passphrase.
265     */
266    public boolean isPassphraseRequiredForDecryption() {
267        assert isSyncInitialized();
268        return nativeIsPassphraseRequiredForDecryption(mNativeProfileSyncServiceAndroid);
269    }
270
271    /**
272     * Checks if we need a passphrase to decrypt any data type (including types that aren't
273     * currently enabled or supported, such as passwords). This API is used to determine if we
274     * need to provide a decryption passphrase before we can re-encrypt with a custom passphrase.
275     *
276     * @return true if we need a passphrase for some type.
277     */
278    public boolean isPassphraseRequiredForExternalType() {
279        assert isSyncInitialized();
280        return nativeIsPassphraseRequiredForExternalType(mNativeProfileSyncServiceAndroid);
281    }
282
283    /**
284     * Checks if the sync backend is running.
285     *
286     * @return true if sync is initialized/running.
287     */
288    public boolean isSyncInitialized() {
289        return nativeIsSyncInitialized(mNativeProfileSyncServiceAndroid);
290    }
291
292    /**
293     * Checks if the first sync setup is currently in progress.
294     *
295     * @return true if first sync setup is in progress
296     */
297    public boolean isFirstSetupInProgress() {
298        return nativeIsFirstSetupInProgress(mNativeProfileSyncServiceAndroid);
299    }
300
301    /**
302     * Checks if the all the data types are encrypted.
303     *
304     * @return true if all data types are encrypted, false if only passwords are encrypted.
305     */
306    public boolean isEncryptEverythingEnabled() {
307        assert isSyncInitialized();
308        return nativeIsEncryptEverythingEnabled(mNativeProfileSyncServiceAndroid);
309    }
310
311    /**
312     * Turns on encryption of all data types. This only takes effect after sync configuration is
313     * completed and setPreferredDataTypes() is invoked.
314     */
315    public void enableEncryptEverything() {
316        assert isSyncInitialized();
317        nativeEnableEncryptEverything(mNativeProfileSyncServiceAndroid);
318    }
319
320    public void setEncryptionPassphrase(String passphrase, boolean isGaia) {
321        assert isSyncInitialized();
322        nativeSetEncryptionPassphrase(mNativeProfileSyncServiceAndroid, passphrase, isGaia);
323    }
324
325    public boolean isCryptographerReady() {
326        assert isSyncInitialized();
327        return nativeIsCryptographerReady(mNativeProfileSyncServiceAndroid);
328    }
329
330    public boolean setDecryptionPassphrase(String passphrase) {
331        assert isSyncInitialized();
332        return nativeSetDecryptionPassphrase(mNativeProfileSyncServiceAndroid, passphrase);
333    }
334
335    public GoogleServiceAuthError.State getAuthError() {
336        int authErrorCode = nativeGetAuthError(mNativeProfileSyncServiceAndroid);
337        return GoogleServiceAuthError.State.fromCode(authErrorCode);
338    }
339
340    /**
341     * Gets the set of data types that are currently enabled to sync.
342     *
343     * @return Set of enabled types.
344     */
345    public Set<ModelType> getPreferredDataTypes() {
346        long modelTypeSelection =
347            nativeGetEnabledDataTypes(mNativeProfileSyncServiceAndroid);
348        Set<ModelType> syncTypes = new HashSet<ModelType>();
349        if ((modelTypeSelection & ModelTypeSelection.AUTOFILL) != 0) {
350          syncTypes.add(ModelType.AUTOFILL);
351        }
352        if ((modelTypeSelection & ModelTypeSelection.AUTOFILL_PROFILE) != 0) {
353          syncTypes.add(ModelType.AUTOFILL_PROFILE);
354        }
355        if ((modelTypeSelection & ModelTypeSelection.BOOKMARK) != 0) {
356          syncTypes.add(ModelType.BOOKMARK);
357        }
358        if ((modelTypeSelection & ModelTypeSelection.EXPERIMENTS) != 0) {
359          syncTypes.add(ModelType.EXPERIMENTS);
360        }
361        if ((modelTypeSelection & ModelTypeSelection.NIGORI) != 0) {
362          syncTypes.add(ModelType.NIGORI);
363        }
364        if ((modelTypeSelection & ModelTypeSelection.PASSWORD) != 0) {
365          syncTypes.add(ModelType.PASSWORD);
366        }
367        if ((modelTypeSelection & ModelTypeSelection.SESSION) != 0) {
368          syncTypes.add(ModelType.SESSION);
369        }
370        if ((modelTypeSelection & ModelTypeSelection.TYPED_URL) != 0) {
371          syncTypes.add(ModelType.TYPED_URL);
372        }
373        if ((modelTypeSelection & ModelTypeSelection.HISTORY_DELETE_DIRECTIVE) != 0) {
374          syncTypes.add(ModelType.HISTORY_DELETE_DIRECTIVE);
375        }
376        if ((modelTypeSelection & ModelTypeSelection.DEVICE_INFO) != 0) {
377          syncTypes.add(ModelType.DEVICE_INFO);
378        }
379        if ((modelTypeSelection & ModelTypeSelection.PROXY_TABS) != 0) {
380          syncTypes.add(ModelType.PROXY_TABS);
381        }
382        if ((modelTypeSelection & ModelTypeSelection.FAVICON_IMAGE) != 0) {
383          syncTypes.add(ModelType.FAVICON_IMAGE);
384        }
385        if ((modelTypeSelection & ModelTypeSelection.FAVICON_TRACKING) != 0) {
386          syncTypes.add(ModelType.FAVICON_TRACKING);
387        }
388        return syncTypes;
389    }
390
391    public boolean hasKeepEverythingSynced() {
392        return nativeHasKeepEverythingSynced(mNativeProfileSyncServiceAndroid);
393    }
394
395    /**
396     * Enables syncing for the passed data types.
397     *
398     * @param syncEverything Set to true if the user wants to sync all data types
399     *                       (including new data types we add in the future).
400     * @param enabledTypes   The set of types to enable. Ignored (can be null) if
401     *                       syncEverything is true.
402     */
403    public void setPreferredDataTypes(boolean syncEverything, Set<ModelType> enabledTypes) {
404        long modelTypeSelection = 0;
405        if (syncEverything || enabledTypes.contains(ModelType.AUTOFILL)) {
406            modelTypeSelection |= ModelTypeSelection.AUTOFILL;
407        }
408        if (syncEverything || enabledTypes.contains(ModelType.BOOKMARK)) {
409            modelTypeSelection |= ModelTypeSelection.BOOKMARK;
410        }
411        if (syncEverything || enabledTypes.contains(ModelType.PASSWORD)) {
412            modelTypeSelection |= ModelTypeSelection.PASSWORD;
413        }
414        if (syncEverything || enabledTypes.contains(ModelType.PROXY_TABS)) {
415            modelTypeSelection |= ModelTypeSelection.PROXY_TABS;
416        }
417        if (syncEverything || enabledTypes.contains(ModelType.TYPED_URL)) {
418            modelTypeSelection |= ModelTypeSelection.TYPED_URL;
419        }
420        nativeSetPreferredDataTypes(
421                mNativeProfileSyncServiceAndroid, syncEverything, modelTypeSelection);
422    }
423
424    public void setSyncSetupCompleted() {
425        nativeSetSyncSetupCompleted(mNativeProfileSyncServiceAndroid);
426    }
427
428    public boolean hasSyncSetupCompleted() {
429        return nativeHasSyncSetupCompleted(mNativeProfileSyncServiceAndroid);
430    }
431
432    public boolean isStartSuppressed() {
433        return nativeIsStartSuppressed(mNativeProfileSyncServiceAndroid);
434    }
435
436    /**
437     * Notifies sync whether sync setup is in progress - this tells sync whether it should start
438     * syncing data types when it starts up, or if it should just stay in "configuration mode".
439     *
440     * @param inProgress True to put sync in configuration mode, false to turn off configuration
441     *                   and allow syncing.
442     */
443    public void setSetupInProgress(boolean inProgress) {
444        nativeSetSetupInProgress(mNativeProfileSyncServiceAndroid, inProgress);
445    }
446
447    public void addSyncStateChangedListener(SyncStateChangedListener listener) {
448        ThreadUtils.assertOnUiThread();
449        mListeners.add(listener);
450    }
451
452    public void removeSyncStateChangedListener(SyncStateChangedListener listener) {
453        ThreadUtils.assertOnUiThread();
454        mListeners.remove(listener);
455    }
456
457    public boolean hasUnrecoverableError() {
458        return nativeHasUnrecoverableError(mNativeProfileSyncServiceAndroid);
459    }
460
461    /**
462     * Called when the state of the native sync engine has changed, so various
463     * UI elements can update themselves.
464     */
465    @CalledByNative
466    public void syncStateChanged() {
467        if (!mListeners.isEmpty()) {
468            for (SyncStateChangedListener listener : mListeners) {
469                listener.syncStateChanged();
470            }
471        }
472    }
473
474    @VisibleForTesting
475    public String getSyncInternalsInfoForTest() {
476        ThreadUtils.assertOnUiThread();
477        return nativeGetAboutInfoForTest(mNativeProfileSyncServiceAndroid);
478    }
479
480    /**
481     * Starts the sync engine.
482     */
483    public void enableSync() {
484        nativeEnableSync(mNativeProfileSyncServiceAndroid);
485    }
486
487    /**
488     * Stops the sync engine.
489     */
490    public void disableSync() {
491        nativeDisableSync(mNativeProfileSyncServiceAndroid);
492    }
493
494    /**
495     * Returns the time when the last sync cycle was completed.
496     *
497     * @return The difference measured in microseconds, between last sync cycle completion time
498     * and 1 January 1970 00:00:00 UTC.
499     */
500    public long getLastSyncedTimeForTest() {
501        return nativeGetLastSyncedTimeForTest(mNativeProfileSyncServiceAndroid);
502    }
503
504    // Native methods
505    private native void nativeNudgeSyncer(
506            int nativeProfileSyncServiceAndroid, int objectSource, String objectId, long version,
507            String payload);
508    private native void nativeNudgeSyncerForAllTypes(int nativeProfileSyncServiceAndroid);
509    private native int nativeInit();
510    private native void nativeEnableSync(int nativeProfileSyncServiceAndroid);
511    private native void nativeDisableSync(int nativeProfileSyncServiceAndroid);
512    private native void nativeSignInSync(int nativeProfileSyncServiceAndroid);
513    private native void nativeSignOutSync(int nativeProfileSyncServiceAndroid);
514    private native void nativeTokenAvailable(
515            int nativeProfileSyncServiceAndroid, String username, String authToken);
516    private native boolean nativeSetSyncSessionsId(int nativeProfileSyncServiceAndroid, String tag);
517    private native String nativeQuerySyncStatusSummary(int nativeProfileSyncServiceAndroid);
518    private native int nativeGetAuthError(int nativeProfileSyncServiceAndroid);
519    private native boolean nativeIsSyncInitialized(int nativeProfileSyncServiceAndroid);
520    private native boolean nativeIsFirstSetupInProgress(int nativeProfileSyncServiceAndroid);
521    private native boolean nativeIsEncryptEverythingEnabled(int nativeProfileSyncServiceAndroid);
522    private native void nativeEnableEncryptEverything(int nativeProfileSyncServiceAndroid);
523    private native boolean nativeIsPassphraseRequiredForDecryption(
524            int nativeProfileSyncServiceAndroid);
525    private native boolean nativeIsPassphraseRequiredForExternalType(
526            int nativeProfileSyncServiceAndroid);
527    private native boolean nativeIsUsingSecondaryPassphrase(int nativeProfileSyncServiceAndroid);
528    private native boolean nativeSetDecryptionPassphrase(
529            int nativeProfileSyncServiceAndroid, String passphrase);
530    private native void nativeSetEncryptionPassphrase(
531            int nativeProfileSyncServiceAndroid, String passphrase, boolean isGaia);
532    private native boolean nativeIsCryptographerReady(int nativeProfileSyncServiceAndroid);
533    private native int nativeGetPassphraseType(int nativeProfileSyncServiceAndroid);
534    private native boolean nativeHasExplicitPassphraseTime(int nativeProfileSyncServiceAndroid);
535    private native String nativeGetSyncEnterGooglePassphraseBodyWithDateText(
536            int nativeProfileSyncServiceAndroid);
537    private native String nativeGetSyncEnterCustomPassphraseBodyWithDateText(
538            int nativeProfileSyncServiceAndroid);
539    private native String nativeGetCurrentSignedInAccountText(int nativeProfileSyncServiceAndroid);
540    private native String nativeGetSyncEnterCustomPassphraseBodyText(
541            int nativeProfileSyncServiceAndroid);
542    private native boolean nativeIsSyncKeystoreMigrationDone(int nativeProfileSyncServiceAndroid);
543    private native long nativeGetEnabledDataTypes(
544        int nativeProfileSyncServiceAndroid);
545    private native void nativeSetPreferredDataTypes(
546            int nativeProfileSyncServiceAndroid, boolean syncEverything, long modelTypeSelection);
547    private native void nativeSetSetupInProgress(
548            int nativeProfileSyncServiceAndroid, boolean inProgress);
549    private native void nativeSetSyncSetupCompleted(int nativeProfileSyncServiceAndroid);
550    private native boolean nativeHasSyncSetupCompleted(int nativeProfileSyncServiceAndroid);
551    private native boolean nativeIsStartSuppressed(int nativeProfileSyncServiceAndroid);
552    private native boolean nativeHasKeepEverythingSynced(int nativeProfileSyncServiceAndroid);
553    private native boolean nativeHasUnrecoverableError(int nativeProfileSyncServiceAndroid);
554    private native String nativeGetAboutInfoForTest(int nativeProfileSyncServiceAndroid);
555    private native long nativeGetLastSyncedTimeForTest(int nativeProfileSyncServiceAndroid);
556}
557