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.content.res.Resources;
14 import android.os.Handler;
15 import android.util.Log;
17 import org.chromium.base.CalledByNative;
18 import org.chromium.base.ObserverList;
19 import org.chromium.base.ThreadUtils;
20 import org.chromium.chrome.browser.invalidation.InvalidationController;
21 import org.chromium.chrome.browser.sync.ProfileSyncService;
22 import org.chromium.chrome.R;
23 import org.chromium.sync.internal_api.pub.base.ModelType;
24 import org.chromium.sync.notifier.SyncStatusHelper;
25 import org.chromium.sync.signin.ChromeSigninController;
27 import java.util.HashSet;
30 * Android wrapper of the SigninManager which provides access from the Java layer.
32 * This class handles common paths during the sign-in and sign-out flows.
34 * Only usable from the UI thread as the native SigninManager requires its access to be in the
37 * See chrome/browser/signin/signin_manager_android.h for more details.
39 public class SigninManager {
41 private static final String TAG = "SigninManager";
43 private static SigninManager sSigninManager;
45 private final Context mContext;
46 private final int mNativeSigninManagerAndroid;
48 /** Tracks whether the First Run check has been completed.
50 * A new sign-in can not be started while this is pending, to prevent the
51 * pending check from eventually starting a 2nd sign-in.
53 private boolean mFirstRunCheckIsPending = true;
54 private ObserverList<SignInAllowedObserver> mSignInAllowedObservers =
55 new ObserverList<SignInAllowedObserver>();
57 private Activity mSignInActivity;
58 private Account mSignInAccount;
59 private Observer mSignInObserver;
60 private boolean mPassive = false;
62 private ProgressDialog mSignOutProgressDialog;
63 private Runnable mSignOutCallback;
65 private AlertDialog mPolicyConfirmationDialog;
68 * SignInAllowedObservers will be notified once signing-in becomes allowed or disallowed.
70 public static interface SignInAllowedObserver {
72 * Invoked once all startup checks are done and signing-in becomes allowed, or disallowed.
74 public void onSignInAllowedChanged();
78 * The Observer of startSignIn() will be notified when sign-in completes.
80 public static interface Observer {
82 * Invoked after sign-in completed successfully.
84 public void onSigninComplete();
87 * Invoked when the sign-in process was cancelled by the user.
89 * The user should have the option of going back and starting the process again,
92 public void onSigninCancelled();
96 * A helper method for retrieving the application-wide SigninManager.
98 * Can only be accessed on the main thread.
100 * @param context the ApplicationContext is retrieved from the context used as an argument.
101 * @return a singleton instance of the SigninManager.
103 public static SigninManager get(Context context) {
104 ThreadUtils.assertOnUiThread();
105 if (sSigninManager == null) {
106 sSigninManager = new SigninManager(context);
108 return sSigninManager;
111 private SigninManager(Context context) {
112 ThreadUtils.assertOnUiThread();
113 mContext = context.getApplicationContext();
114 mNativeSigninManagerAndroid = nativeInit();
118 * Notifies the SigninManager that the First Run check has completed.
120 * The user will be allowed to sign-in once this is signaled.
122 public void onFirstRunCheckDone() {
123 mFirstRunCheckIsPending = false;
125 if (isSignInAllowed()) {
126 notifySignInAllowedChanged();
131 * Returns true if signin can be started now.
133 public boolean isSignInAllowed() {
134 return !mFirstRunCheckIsPending &&
135 mSignInAccount == null &&
136 ChromeSigninController.get(mContext).getSignedInUser() == null;
139 public void addSignInAllowedObserver(SignInAllowedObserver observer) {
140 mSignInAllowedObservers.addObserver(observer);
143 public void removeSignInAllowedObserver(SignInAllowedObserver observer) {
144 mSignInAllowedObservers.removeObserver(observer);
147 private void notifySignInAllowedChanged() {
148 new Handler().post(new Runnable() {
151 for (SignInAllowedObserver observer : mSignInAllowedObservers) {
152 observer.onSignInAllowedChanged();
159 * Starts the sign-in flow, and executes the callback when ready to proceed.
161 * This method checks with the native side whether the account has management enabled, and may
162 * present a dialog to the user to confirm sign-in. The callback is invoked once these processes
163 * and the common sign-in initialization complete.
165 * @param activity The context to use for the operation.
166 * @param account The account to sign in to.
167 * @param passive If passive is true then this operation should not interact with the user.
168 * @param callback The Observer to notify when the sign-in process is finished.
170 public void startSignIn(
171 Activity activity, final Account account, boolean passive, final Observer observer) {
172 assert mSignInActivity == null;
173 assert mSignInAccount == null;
174 assert mSignInObserver == null;
176 if (mFirstRunCheckIsPending) {
177 Log.w(TAG, "Ignoring sign-in request until the First Run check completes.");
181 mSignInActivity = activity;
182 mSignInAccount = account;
183 mSignInObserver = observer;
186 notifySignInAllowedChanged();
188 if (!nativeShouldLoadPolicyForUser(account.name)) {
189 // Proceed with the sign-in flow without checking for policy if it can be determined
190 // that this account can't have management enabled based on the username.
195 Log.d(TAG, "Checking if account has policy management enabled");
196 // This will call back to onPolicyCheckedBeforeSignIn.
197 nativeCheckPolicyBeforeSignIn(mNativeSigninManagerAndroid, account.name);
201 private void onPolicyCheckedBeforeSignIn(String managementDomain) {
202 if (managementDomain == null) {
203 Log.d(TAG, "Account doesn't have policy");
208 if (mSignInActivity.isDestroyed()) {
209 // The activity is no longer running, cancel sign in.
215 // If this is a passive interaction (e.g. auto signin) then don't show the confirmation
217 nativeFetchPolicyBeforeSignIn(mNativeSigninManagerAndroid);
221 Log.d(TAG, "Account has policy management");
222 AlertDialog.Builder builder = new AlertDialog.Builder(mSignInActivity);
223 builder.setTitle(R.string.policy_dialog_title);
224 builder.setMessage(mContext.getResources().getString(R.string.policy_dialog_message,
226 builder.setPositiveButton(
227 R.string.policy_dialog_proceed,
228 new DialogInterface.OnClickListener() {
230 public void onClick(DialogInterface dialog, int id) {
231 Log.d(TAG, "Accepted policy management, proceeding with sign-in");
232 // This will call back to onPolicyFetchedBeforeSignIn.
233 nativeFetchPolicyBeforeSignIn(mNativeSigninManagerAndroid);
234 mPolicyConfirmationDialog = null;
237 builder.setNegativeButton(
238 R.string.policy_dialog_cancel,
239 new DialogInterface.OnClickListener() {
241 public void onClick(DialogInterface dialog, int id) {
242 Log.d(TAG, "Cancelled sign-in");
244 mPolicyConfirmationDialog = null;
247 builder.setOnDismissListener(
248 new DialogInterface.OnDismissListener() {
250 public void onDismiss(DialogInterface dialog) {
251 if (mPolicyConfirmationDialog != null) {
252 Log.d(TAG, "Policy dialog dismissed, cancelling sign-in.");
254 mPolicyConfirmationDialog = null;
258 mPolicyConfirmationDialog = builder.create();
259 mPolicyConfirmationDialog.show();
263 private void onPolicyFetchedBeforeSignIn() {
264 // Policy has been fetched for the user and is being enforced; features like sync may now
265 // be disabled by policy, and the rest of the sign-in flow can be resumed.
269 private void doSignIn() {
270 Log.d(TAG, "Committing the sign-in process now");
271 assert mSignInAccount != null;
273 // Cache the signed-in account name.
274 ChromeSigninController.get(mContext).setSignedInAccountName(mSignInAccount.name);
276 // Tell the native side that sign-in has completed.
277 // This will trigger NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL.
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 private void cancelSignIn() {
343 if (mSignInObserver != null)
344 mSignInObserver.onSigninCancelled();
345 mSignInActivity = null;
346 mSignInObserver = null;
347 mSignInAccount = null;
348 notifySignInAllowedChanged();
351 private void wipeProfileData(Activity activity) {
352 if (activity != null) {
353 // We don't have progress update, so this takes an indeterminate amount of time.
354 boolean indeterminate = true;
355 // This dialog is not cancelable by the user.
356 boolean cancelable = false;
357 mSignOutProgressDialog = ProgressDialog.show(
359 activity.getString(R.string.wiping_profile_data_title),
360 activity.getString(R.string.wiping_profile_data_message),
361 indeterminate, cancelable);
363 // This will call back to onProfileDataWiped().
364 nativeWipeProfileData(mNativeSigninManagerAndroid);
368 private void onProfileDataWiped() {
369 if (mSignOutProgressDialog != null && mSignOutProgressDialog.isShowing())
370 mSignOutProgressDialog.dismiss();
374 private void onSignOutDone() {
375 if (mSignOutCallback != null) {
376 new Handler().post(mSignOutCallback);
377 mSignOutCallback = null;
382 private native int nativeInit();
383 private native boolean nativeShouldLoadPolicyForUser(String username);
384 private native void nativeCheckPolicyBeforeSignIn(
385 int nativeSigninManagerAndroid, String username);
386 private native void nativeFetchPolicyBeforeSignIn(int nativeSigninManagerAndroid);
387 private native void nativeOnSignInCompleted(int nativeSigninManagerAndroid, String username);
388 private native void nativeSignOut(int nativeSigninManagerAndroid);
389 private native String nativeGetManagementDomain(int nativeSigninManagerAndroid);
390 private native void nativeWipeProfileData(int nativeSigninManagerAndroid);
391 private native void nativeLogInSignedInUser(int nativeSigninManagerAndroid);