root/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManager.java

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. onSignInAllowedChanged
  2. onSigninComplete
  3. onSigninCancelled
  4. get
  5. onFirstRunCheckDone
  6. isSignInAllowed
  7. addSignInAllowedObserver
  8. removeSignInAllowedObserver
  9. notifySignInAllowedChanged
  10. startSignIn
  11. onPolicyCheckedBeforeSignIn
  12. onPolicyFetchedBeforeSignIn
  13. doSignIn
  14. signOut
  15. getManagementDomain
  16. logInSignedInUser
  17. cancelSignIn
  18. wipeProfileData
  19. onProfileDataWiped
  20. onSignOutDone
  21. isNewProfileManagementEnabled
  22. nativeInit
  23. nativeShouldLoadPolicyForUser
  24. nativeCheckPolicyBeforeSignIn
  25. nativeFetchPolicyBeforeSignIn
  26. nativeOnSignInCompleted
  27. nativeSignOut
  28. nativeGetManagementDomain
  29. nativeWipeProfileData
  30. nativeLogInSignedInUser
  31. nativeIsNewProfileManagementEnabled

// 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.chrome.browser.signin;

import android.accounts.Account;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Handler;
import android.util.Log;

import org.chromium.base.CalledByNative;
import org.chromium.base.ObserverList;
import org.chromium.base.ThreadUtils;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.invalidation.InvalidationController;
import org.chromium.chrome.browser.sync.ProfileSyncService;
import org.chromium.sync.internal_api.pub.base.ModelType;
import org.chromium.sync.notifier.SyncStatusHelper;
import org.chromium.sync.signin.ChromeSigninController;

import java.util.HashSet;

/**
 * Android wrapper of the SigninManager which provides access from the Java layer.
 * <p/>
 * This class handles common paths during the sign-in and sign-out flows.
 * <p/>
 * Only usable from the UI thread as the native SigninManager requires its access to be in the
 * UI thread.
 * <p/>
 * See chrome/browser/signin/signin_manager_android.h for more details.
 */
public class SigninManager {

    private static final String TAG = "SigninManager";

    private static SigninManager sSigninManager;

    private final Context mContext;
    private final long mNativeSigninManagerAndroid;

    /** Tracks whether the First Run check has been completed.
     *
     * A new sign-in can not be started while this is pending, to prevent the
     * pending check from eventually starting a 2nd sign-in.
     */
    private boolean mFirstRunCheckIsPending = true;
    private final ObserverList<SignInAllowedObserver> mSignInAllowedObservers =
            new ObserverList<SignInAllowedObserver>();

    private Activity mSignInActivity;
    private Account mSignInAccount;
    private Observer mSignInObserver;
    private boolean mPassive = false;

    private ProgressDialog mSignOutProgressDialog;
    private Runnable mSignOutCallback;

    private AlertDialog mPolicyConfirmationDialog;

    /**
     * SignInAllowedObservers will be notified once signing-in becomes allowed or disallowed.
     */
    public static interface SignInAllowedObserver {
        /**
         * Invoked once all startup checks are done and signing-in becomes allowed, or disallowed.
         */
        public void onSignInAllowedChanged();
    }

    /**
     * The Observer of startSignIn() will be notified when sign-in completes.
     */
    public static interface Observer {
        /**
         * Invoked after sign-in completed successfully.
         */
        public void onSigninComplete();

        /**
         * Invoked when the sign-in process was cancelled by the user.
         *
         * The user should have the option of going back and starting the process again,
         * if possible.
         */
        public void onSigninCancelled();
    }

    /**
     * A helper method for retrieving the application-wide SigninManager.
     * <p/>
     * Can only be accessed on the main thread.
     *
     * @param context the ApplicationContext is retrieved from the context used as an argument.
     * @return a singleton instance of the SigninManager.
     */
    public static SigninManager get(Context context) {
        ThreadUtils.assertOnUiThread();
        if (sSigninManager == null) {
            sSigninManager = new SigninManager(context);
        }
        return sSigninManager;
    }

    private SigninManager(Context context) {
        ThreadUtils.assertOnUiThread();
        mContext = context.getApplicationContext();
        mNativeSigninManagerAndroid = nativeInit();
    }

    /**
     * Notifies the SigninManager that the First Run check has completed.
     *
     * The user will be allowed to sign-in once this is signaled.
     */
    public void onFirstRunCheckDone() {
        mFirstRunCheckIsPending = false;

        if (isSignInAllowed()) {
            notifySignInAllowedChanged();
        }
    }

    /**
     * Returns true if signin can be started now.
     */
    public boolean isSignInAllowed() {
        return !mFirstRunCheckIsPending &&
                mSignInAccount == null &&
                ChromeSigninController.get(mContext).getSignedInUser() == null;
    }

    public void addSignInAllowedObserver(SignInAllowedObserver observer) {
        mSignInAllowedObservers.addObserver(observer);
    }

    public void removeSignInAllowedObserver(SignInAllowedObserver observer) {
        mSignInAllowedObservers.removeObserver(observer);
    }

    private void notifySignInAllowedChanged() {
        new Handler().post(new Runnable() {
            @Override
            public void run() {
                for (SignInAllowedObserver observer : mSignInAllowedObservers) {
                    observer.onSignInAllowedChanged();
                }
            }
        });
    }

    /**
     * Starts the sign-in flow, and executes the callback when ready to proceed.
     * <p/>
     * This method checks with the native side whether the account has management enabled, and may
     * present a dialog to the user to confirm sign-in. The callback is invoked once these processes
     * and the common sign-in initialization complete.
     *
     * @param activity The context to use for the operation.
     * @param account The account to sign in to.
     * @param passive If passive is true then this operation should not interact with the user.
     * @param observer The Observer to notify when the sign-in process is finished.
     */
    public void startSignIn(
            Activity activity, final Account account, boolean passive, final Observer observer) {
        assert mSignInActivity == null;
        assert mSignInAccount == null;
        assert mSignInObserver == null;

        if (mFirstRunCheckIsPending) {
            Log.w(TAG, "Ignoring sign-in request until the First Run check completes.");
            return;
        }

        mSignInActivity = activity;
        mSignInAccount = account;
        mSignInObserver = observer;
        mPassive = passive;

        notifySignInAllowedChanged();

        if (!nativeShouldLoadPolicyForUser(account.name)) {
            // Proceed with the sign-in flow without checking for policy if it can be determined
            // that this account can't have management enabled based on the username.
            doSignIn();
            return;
        }

        Log.d(TAG, "Checking if account has policy management enabled");
        // This will call back to onPolicyCheckedBeforeSignIn.
        nativeCheckPolicyBeforeSignIn(mNativeSigninManagerAndroid, account.name);
    }

    @CalledByNative
    private void onPolicyCheckedBeforeSignIn(String managementDomain) {
        if (managementDomain == null) {
            Log.d(TAG, "Account doesn't have policy");
            doSignIn();
            return;
        }

        if (mSignInActivity.isDestroyed()) {
            // The activity is no longer running, cancel sign in.
            cancelSignIn();
            return;
        }

        if (mPassive) {
            // If this is a passive interaction (e.g. auto signin) then don't show the confirmation
            // dialog.
            nativeFetchPolicyBeforeSignIn(mNativeSigninManagerAndroid);
            return;
        }

        Log.d(TAG, "Account has policy management");
        AlertDialog.Builder builder = new AlertDialog.Builder(mSignInActivity);
        builder.setTitle(R.string.policy_dialog_title);
        builder.setMessage(mContext.getResources().getString(R.string.policy_dialog_message,
                                                             managementDomain));
        builder.setPositiveButton(
                R.string.policy_dialog_proceed,
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int id) {
                        Log.d(TAG, "Accepted policy management, proceeding with sign-in");
                        // This will call back to onPolicyFetchedBeforeSignIn.
                        nativeFetchPolicyBeforeSignIn(mNativeSigninManagerAndroid);
                        mPolicyConfirmationDialog = null;
                    }
                });
        builder.setNegativeButton(
                R.string.policy_dialog_cancel,
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int id) {
                        Log.d(TAG, "Cancelled sign-in");
                        cancelSignIn();
                        mPolicyConfirmationDialog = null;
                    }
                });
        builder.setOnDismissListener(
                new DialogInterface.OnDismissListener() {
                    @Override
                    public void onDismiss(DialogInterface dialog) {
                        if (mPolicyConfirmationDialog != null) {
                            Log.d(TAG, "Policy dialog dismissed, cancelling sign-in.");
                            cancelSignIn();
                            mPolicyConfirmationDialog = null;
                        }
                    }
                });
        mPolicyConfirmationDialog = builder.create();
        mPolicyConfirmationDialog.show();
    }

    @CalledByNative
    private void onPolicyFetchedBeforeSignIn() {
        // Policy has been fetched for the user and is being enforced; features like sync may now
        // be disabled by policy, and the rest of the sign-in flow can be resumed.
        doSignIn();
    }

    private void doSignIn() {
        Log.d(TAG, "Committing the sign-in process now");
        assert mSignInAccount != null;

        // Cache the signed-in account name.
        ChromeSigninController.get(mContext).setSignedInAccountName(mSignInAccount.name);

        // Tell the native side that sign-in has completed.
        nativeOnSignInCompleted(mNativeSigninManagerAndroid, mSignInAccount.name);

        // Register for invalidations.
        InvalidationController invalidationController = InvalidationController.get(mContext);
        invalidationController.setRegisteredTypes(mSignInAccount, true, new HashSet<ModelType>());

        // Sign-in to sync.
        ProfileSyncService profileSyncService = ProfileSyncService.get(mContext);
        if (SyncStatusHelper.get(mContext).isSyncEnabled(mSignInAccount) &&
                !profileSyncService.hasSyncSetupCompleted()) {
            profileSyncService.setSetupInProgress(true);
            profileSyncService.syncSignIn();
        }

        if (mSignInObserver != null)
            mSignInObserver.onSigninComplete();

        // All done, cleanup.
        Log.d(TAG, "Signin done");
        mSignInActivity = null;
        mSignInAccount = null;
        mSignInObserver = null;
        notifySignInAllowedChanged();
    }

    /**
     * Signs out of Chrome.
     * <p/>
     * This method clears the signed-in username, stops sync and sends out a
     * sign-out notification on the native side.
     *
     * @param activity If not null then a progress dialog is shown over the activity until signout
     * completes, in case the account had management enabled. The activity must be valid until the
     * callback is invoked.
     * @param callback Will be invoked after signout completes, if not null.
     */
    public void signOut(Activity activity, Runnable callback) {
        mSignOutCallback = callback;

        boolean wipeData = getManagementDomain() != null;
        Log.d(TAG, "Signing out, wipe data? " + wipeData);

        ChromeSigninController.get(mContext).clearSignedInUser();
        ProfileSyncService.get(mContext).signOut();
        nativeSignOut(mNativeSigninManagerAndroid);

        if (wipeData) {
            wipeProfileData(activity);
        } else {
            onSignOutDone();
        }
    }

    /**
     * Returns the management domain if the signed in account is managed, otherwise returns null.
     */
    public String getManagementDomain() {
        return nativeGetManagementDomain(mNativeSigninManagerAndroid);
    }

    public void logInSignedInUser() {
        nativeLogInSignedInUser(mNativeSigninManagerAndroid);
    }

    private void cancelSignIn() {
        if (mSignInObserver != null)
            mSignInObserver.onSigninCancelled();
        mSignInActivity = null;
        mSignInObserver = null;
        mSignInAccount = null;
        notifySignInAllowedChanged();
    }

    private void wipeProfileData(Activity activity) {
        if (activity != null) {
            // We don't have progress update, so this takes an indeterminate amount of time.
            boolean indeterminate = true;
            // This dialog is not cancelable by the user.
            boolean cancelable = false;
            mSignOutProgressDialog = ProgressDialog.show(
                activity,
                activity.getString(R.string.wiping_profile_data_title),
                activity.getString(R.string.wiping_profile_data_message),
                indeterminate, cancelable);
        }
        // This will call back to onProfileDataWiped().
        nativeWipeProfileData(mNativeSigninManagerAndroid);
    }

    @CalledByNative
    private void onProfileDataWiped() {
        if (mSignOutProgressDialog != null && mSignOutProgressDialog.isShowing())
            mSignOutProgressDialog.dismiss();
        onSignOutDone();
    }

    private void onSignOutDone() {
        if (mSignOutCallback != null) {
            new Handler().post(mSignOutCallback);
            mSignOutCallback = null;
        }
    }

    /**
     * @return True if the new profile management is enabled.
     */
    public static boolean isNewProfileManagementEnabled() {
        return nativeIsNewProfileManagementEnabled();
    }

    // Native methods.
    private native long nativeInit();
    private native boolean nativeShouldLoadPolicyForUser(String username);
    private native void nativeCheckPolicyBeforeSignIn(
            long nativeSigninManagerAndroid, String username);
    private native void nativeFetchPolicyBeforeSignIn(long nativeSigninManagerAndroid);
    private native void nativeOnSignInCompleted(long nativeSigninManagerAndroid, String username);
    private native void nativeSignOut(long nativeSigninManagerAndroid);
    private native String nativeGetManagementDomain(long nativeSigninManagerAndroid);
    private native void nativeWipeProfileData(long nativeSigninManagerAndroid);
    private native void nativeLogInSignedInUser(long nativeSigninManagerAndroid);
    private static native boolean nativeIsNewProfileManagementEnabled();
}

/* [<][>][^][v][top][bottom][index][help] */