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.
5 package org.chromium.chrome.browser.signin;
7 import android.accounts.Account;
8 import android.app.Activity;
9 import android.app.AlertDialog;
10 import android.app.ProgressDialog;
11 import android.content.Context;
12 import android.content.DialogInterface;
13 import android.os.Handler;
14 import android.util.Log;
16 import org.chromium.base.ActivityState;
17 import org.chromium.base.ApplicationStatus;
18 import org.chromium.base.CalledByNative;
19 import org.chromium.base.ObserverList;
20 import org.chromium.base.ThreadUtils;
21 import org.chromium.chrome.R;
22 import org.chromium.chrome.browser.invalidation.InvalidationController;
23 import org.chromium.chrome.browser.sync.ProfileSyncService;
24 import org.chromium.sync.internal_api.pub.base.ModelType;
25 import org.chromium.sync.notifier.SyncStatusHelper;
26 import org.chromium.sync.signin.ChromeSigninController;
28 import java.util.HashSet;
31 * Android wrapper of the SigninManager which provides access from the Java layer.
33 * This class handles common paths during the sign-in and sign-out flows.
35 * Only usable from the UI thread as the native SigninManager requires its access to be in the
38 * See chrome/browser/signin/signin_manager_android.h for more details.
40 public class SigninManager {
42 private static final String TAG = "SigninManager";
44 private static SigninManager sSigninManager;
46 private final Context mContext;
47 private final long mNativeSigninManagerAndroid;
49 /** Tracks whether the First Run check has been completed.
51 * A new sign-in can not be started while this is pending, to prevent the
52 * pending check from eventually starting a 2nd sign-in.
54 private boolean mFirstRunCheckIsPending = true;
55 private final ObserverList<SignInAllowedObserver> mSignInAllowedObservers =
56 new ObserverList<SignInAllowedObserver>();
58 private Activity mSignInActivity;
59 private Account mSignInAccount;
60 private Observer mSignInObserver;
61 private boolean mPassive = false;
63 private ProgressDialog mSignOutProgressDialog;
64 private Runnable mSignOutCallback;
66 private AlertDialog mPolicyConfirmationDialog;
69 * SignInAllowedObservers will be notified once signing-in becomes allowed or disallowed.
71 public static interface SignInAllowedObserver {
73 * Invoked once all startup checks are done and signing-in becomes allowed, or disallowed.
75 public void onSignInAllowedChanged();
79 * The Observer of startSignIn() will be notified when sign-in completes.
81 public static interface Observer {
83 * Invoked after sign-in completed successfully.
85 public void onSigninComplete();
88 * Invoked when the sign-in process was cancelled by the user.
90 * The user should have the option of going back and starting the process again,
93 public void onSigninCancelled();
97 * A helper method for retrieving the application-wide SigninManager.
99 * Can only be accessed on the main thread.
101 * @param context the ApplicationContext is retrieved from the context used as an argument.
102 * @return a singleton instance of the SigninManager.
104 public static SigninManager get(Context context) {
105 ThreadUtils.assertOnUiThread();
106 if (sSigninManager == null) {
107 sSigninManager = new SigninManager(context);
109 return sSigninManager;
112 private SigninManager(Context context) {
113 ThreadUtils.assertOnUiThread();
114 mContext = context.getApplicationContext();
115 mNativeSigninManagerAndroid = nativeInit();
119 * Notifies the SigninManager that the First Run check has completed.
121 * The user will be allowed to sign-in once this is signaled.
123 public void onFirstRunCheckDone() {
124 mFirstRunCheckIsPending = false;
126 if (isSignInAllowed()) {
127 notifySignInAllowedChanged();
132 * Returns true if signin can be started now.
134 public boolean isSignInAllowed() {
135 return !mFirstRunCheckIsPending &&
136 mSignInAccount == null &&
137 ChromeSigninController.get(mContext).getSignedInUser() == null;
140 public void addSignInAllowedObserver(SignInAllowedObserver observer) {
141 mSignInAllowedObservers.addObserver(observer);
144 public void removeSignInAllowedObserver(SignInAllowedObserver observer) {
145 mSignInAllowedObservers.removeObserver(observer);
148 private void notifySignInAllowedChanged() {
149 new Handler().post(new Runnable() {
152 for (SignInAllowedObserver observer : mSignInAllowedObservers) {
153 observer.onSignInAllowedChanged();
160 * Starts the sign-in flow, and executes the callback when ready to proceed.
162 * This method checks with the native side whether the account has management enabled, and may
163 * present a dialog to the user to confirm sign-in. The callback is invoked once these processes
164 * and the common sign-in initialization complete.
166 * @param activity The context to use for the operation.
167 * @param account The account to sign in to.
168 * @param passive If passive is true then this operation should not interact with the user.
169 * @param observer The Observer to notify when the sign-in process is finished.
171 public void startSignIn(
172 Activity activity, final Account account, boolean passive, final Observer observer) {
173 assert mSignInActivity == null;
174 assert mSignInAccount == null;
175 assert mSignInObserver == null;
177 if (mFirstRunCheckIsPending) {
178 Log.w(TAG, "Ignoring sign-in request until the First Run check completes.");
182 mSignInActivity = activity;
183 mSignInAccount = account;
184 mSignInObserver = observer;
187 notifySignInAllowedChanged();
189 if (!nativeShouldLoadPolicyForUser(account.name)) {
190 // Proceed with the sign-in flow without checking for policy if it can be determined
191 // that this account can't have management enabled based on the username.
196 Log.d(TAG, "Checking if account has policy management enabled");
197 // This will call back to onPolicyCheckedBeforeSignIn.
198 nativeCheckPolicyBeforeSignIn(mNativeSigninManagerAndroid, account.name);
202 private void onPolicyCheckedBeforeSignIn(String managementDomain) {
203 if (managementDomain == null) {
204 Log.d(TAG, "Account doesn't have policy");
209 if (ApplicationStatus.getStateForActivity(mSignInActivity) == ActivityState.DESTROYED) {
210 // The activity is no longer running, cancel sign in.
216 // If this is a passive interaction (e.g. auto signin) then don't show the confirmation
218 nativeFetchPolicyBeforeSignIn(mNativeSigninManagerAndroid);
222 Log.d(TAG, "Account has policy management");
223 AlertDialog.Builder builder = new AlertDialog.Builder(mSignInActivity);
224 builder.setTitle(R.string.policy_dialog_title);
225 builder.setMessage(mContext.getResources().getString(R.string.policy_dialog_message,
227 builder.setPositiveButton(
228 R.string.policy_dialog_proceed,
229 new DialogInterface.OnClickListener() {
231 public void onClick(DialogInterface dialog, int id) {
232 Log.d(TAG, "Accepted policy management, proceeding with sign-in");
233 // This will call back to onPolicyFetchedBeforeSignIn.
234 nativeFetchPolicyBeforeSignIn(mNativeSigninManagerAndroid);
235 mPolicyConfirmationDialog = null;
238 builder.setNegativeButton(
239 R.string.policy_dialog_cancel,
240 new DialogInterface.OnClickListener() {
242 public void onClick(DialogInterface dialog, int id) {
243 Log.d(TAG, "Cancelled sign-in");
245 mPolicyConfirmationDialog = null;
248 mPolicyConfirmationDialog = builder.create();
249 mPolicyConfirmationDialog.setOnDismissListener(
250 new DialogInterface.OnDismissListener() {
252 public void onDismiss(DialogInterface dialog) {
253 if (mPolicyConfirmationDialog != null) {
254 Log.d(TAG, "Policy dialog dismissed, cancelling sign-in.");
256 mPolicyConfirmationDialog = null;
260 mPolicyConfirmationDialog.show();
264 private void onPolicyFetchedBeforeSignIn() {
265 // Policy has been fetched for the user and is being enforced; features like sync may now
266 // be disabled by policy, and the rest of the sign-in flow can be resumed.
270 private void doSignIn() {
271 Log.d(TAG, "Committing the sign-in process now");
272 assert mSignInAccount != null;
274 // Cache the signed-in account name.
275 ChromeSigninController.get(mContext).setSignedInAccountName(mSignInAccount.name);
277 // Tell the native side that sign-in has completed.
278 nativeOnSignInCompleted(mNativeSigninManagerAndroid, mSignInAccount.name);
280 // Register for invalidations.
281 InvalidationController invalidationController = InvalidationController.get(mContext);
282 invalidationController.setRegisteredTypes(mSignInAccount, true, new HashSet<ModelType>());
285 ProfileSyncService profileSyncService = ProfileSyncService.get(mContext);
286 if (SyncStatusHelper.get(mContext).isSyncEnabled(mSignInAccount) &&
287 !profileSyncService.hasSyncSetupCompleted()) {
288 profileSyncService.setSetupInProgress(true);
289 profileSyncService.syncSignIn();
292 if (mSignInObserver != null)
293 mSignInObserver.onSigninComplete();
295 // All done, cleanup.
296 Log.d(TAG, "Signin done");
297 mSignInActivity = null;
298 mSignInAccount = null;
299 mSignInObserver = null;
300 notifySignInAllowedChanged();
304 * Signs out of Chrome.
306 * This method clears the signed-in username, stops sync and sends out a
307 * sign-out notification on the native side.
309 * @param activity If not null then a progress dialog is shown over the activity until signout
310 * completes, in case the account had management enabled. The activity must be valid until the
311 * callback is invoked.
312 * @param callback Will be invoked after signout completes, if not null.
314 public void signOut(Activity activity, Runnable callback) {
315 mSignOutCallback = callback;
317 boolean wipeData = getManagementDomain() != null;
318 Log.d(TAG, "Signing out, wipe data? " + wipeData);
320 ChromeSigninController.get(mContext).clearSignedInUser();
321 ProfileSyncService.get(mContext).signOut();
322 nativeSignOut(mNativeSigninManagerAndroid);
325 wipeProfileData(activity);
332 * Returns the management domain if the signed in account is managed, otherwise returns null.
334 public String getManagementDomain() {
335 return nativeGetManagementDomain(mNativeSigninManagerAndroid);
338 public void logInSignedInUser() {
339 nativeLogInSignedInUser(mNativeSigninManagerAndroid);
342 public void clearLastSignedInUser() {
343 nativeClearLastSignedInUser(mNativeSigninManagerAndroid);
346 private void cancelSignIn() {
347 if (mSignInObserver != null)
348 mSignInObserver.onSigninCancelled();
349 mSignInActivity = null;
350 mSignInObserver = null;
351 mSignInAccount = null;
352 notifySignInAllowedChanged();
355 private void wipeProfileData(Activity activity) {
356 if (activity != null) {
357 // We don't have progress update, so this takes an indeterminate amount of time.
358 boolean indeterminate = true;
359 // This dialog is not cancelable by the user.
360 boolean cancelable = false;
361 mSignOutProgressDialog = ProgressDialog.show(
363 activity.getString(R.string.wiping_profile_data_title),
364 activity.getString(R.string.wiping_profile_data_message),
365 indeterminate, cancelable);
367 // This will call back to onProfileDataWiped().
368 nativeWipeProfileData(mNativeSigninManagerAndroid);
372 private void onProfileDataWiped() {
373 if (mSignOutProgressDialog != null && mSignOutProgressDialog.isShowing())
374 mSignOutProgressDialog.dismiss();
378 private void onSignOutDone() {
379 if (mSignOutCallback != null) {
380 new Handler().post(mSignOutCallback);
381 mSignOutCallback = null;
386 * @return True if the new profile management is enabled.
388 public static boolean isNewProfileManagementEnabled() {
389 return nativeIsNewProfileManagementEnabled();
393 private native long nativeInit();
394 private native boolean nativeShouldLoadPolicyForUser(String username);
395 private native void nativeCheckPolicyBeforeSignIn(
396 long nativeSigninManagerAndroid, String username);
397 private native void nativeFetchPolicyBeforeSignIn(long nativeSigninManagerAndroid);
398 private native void nativeOnSignInCompleted(long nativeSigninManagerAndroid, String username);
399 private native void nativeSignOut(long nativeSigninManagerAndroid);
400 private native String nativeGetManagementDomain(long nativeSigninManagerAndroid);
401 private native void nativeWipeProfileData(long nativeSigninManagerAndroid);
402 private native void nativeClearLastSignedInUser(long nativeSigninManagerAndroid);
403 private native void nativeLogInSignedInUser(long nativeSigninManagerAndroid);
404 private static native boolean nativeIsNewProfileManagementEnabled();