blob: 583eeb38b6dd5bd94e1b023a1db4ea174c9cf5e7 [file] [log] [blame]
// 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.ActivityState;
import org.chromium.base.ApplicationStatus;
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;
private boolean mSigninAllowedByPolicy;
/**
* 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();
mSigninAllowedByPolicy = nativeIsSigninAllowedByPolicy(mNativeSigninManagerAndroid);
}
/**
* 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 mSigninAllowedByPolicy &&
!mFirstRunCheckIsPending &&
mSignInAccount == null &&
ChromeSigninController.get(mContext).getSignedInUser() == null;
}
/**
* Returns true if signin is disabled by policy.
*/
public boolean isSigninDisabledByPolicy() {
return !mSigninAllowedByPolicy;
}
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 (ApplicationStatus.getStateForActivity(mSignInActivity) == ActivityState.DESTROYED) {
// 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;
}
});
mPolicyConfirmationDialog = builder.create();
mPolicyConfirmationDialog.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.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);
}
public void clearLastSignedInUser() {
nativeClearLastSignedInUser(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();
}
@CalledByNative
private void onSigninAllowedByPolicyChanged(boolean newSigninAllowedByPolicy) {
mSigninAllowedByPolicy = newSigninAllowedByPolicy;
notifySignInAllowedChanged();
}
// Native methods.
private native long nativeInit();
private native boolean nativeIsSigninAllowedByPolicy(long nativeSigninManagerAndroid);
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 nativeClearLastSignedInUser(long nativeSigninManagerAndroid);
private native void nativeLogInSignedInUser(long nativeSigninManagerAndroid);
private static native boolean nativeIsNewProfileManagementEnabled();
}