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