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 5 package org.chromium.chrome.browser.signin; 6 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; 15 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; 27 28 import java.util.HashSet; 29 30 /** 31 * Android wrapper of the SigninManager which provides access from the Java layer. 32 * <p/> 33 * This class handles common paths during the sign-in and sign-out flows. 34 * <p/> 35 * Only usable from the UI thread as the native SigninManager requires its access to be in the 36 * UI thread. 37 * <p/> 38 * See chrome/browser/signin/signin_manager_android.h for more details. 39 */ 40 public class SigninManager { 41 42 private static final String TAG = "SigninManager"; 43 44 private static SigninManager sSigninManager; 45 46 private final Context mContext; 47 private final long mNativeSigninManagerAndroid; 48 49 /** Tracks whether the First Run check has been completed. 50 * 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. 53 */ 54 private boolean mFirstRunCheckIsPending = true; 55 private final ObserverList<SignInAllowedObserver> mSignInAllowedObservers = 56 new ObserverList<SignInAllowedObserver>(); 57 58 private Activity mSignInActivity; 59 private Account mSignInAccount; 60 private Observer mSignInObserver; 61 private boolean mPassive = false; 62 63 private ProgressDialog mSignOutProgressDialog; 64 private Runnable mSignOutCallback; 65 66 private AlertDialog mPolicyConfirmationDialog; 67 68 /** 69 * SignInAllowedObservers will be notified once signing-in becomes allowed or disallowed. 70 */ 71 public static interface SignInAllowedObserver { 72 /** 73 * Invoked once all startup checks are done and signing-in becomes allowed, or disallowed. 74 */ onSignInAllowedChanged()75 public void onSignInAllowedChanged(); 76 } 77 78 /** 79 * The Observer of startSignIn() will be notified when sign-in completes. 80 */ 81 public static interface Observer { 82 /** 83 * Invoked after sign-in completed successfully. 84 */ onSigninComplete()85 public void onSigninComplete(); 86 87 /** 88 * Invoked when the sign-in process was cancelled by the user. 89 * 90 * The user should have the option of going back and starting the process again, 91 * if possible. 92 */ onSigninCancelled()93 public void onSigninCancelled(); 94 } 95 96 /** 97 * A helper method for retrieving the application-wide SigninManager. 98 * <p/> 99 * Can only be accessed on the main thread. 100 * 101 * @param context the ApplicationContext is retrieved from the context used as an argument. 102 * @return a singleton instance of the SigninManager. 103 */ get(Context context)104 public static SigninManager get(Context context) { 105 ThreadUtils.assertOnUiThread(); 106 if (sSigninManager == null) { 107 sSigninManager = new SigninManager(context); 108 } 109 return sSigninManager; 110 } 111 SigninManager(Context context)112 private SigninManager(Context context) { 113 ThreadUtils.assertOnUiThread(); 114 mContext = context.getApplicationContext(); 115 mNativeSigninManagerAndroid = nativeInit(); 116 } 117 118 /** 119 * Notifies the SigninManager that the First Run check has completed. 120 * 121 * The user will be allowed to sign-in once this is signaled. 122 */ onFirstRunCheckDone()123 public void onFirstRunCheckDone() { 124 mFirstRunCheckIsPending = false; 125 126 if (isSignInAllowed()) { 127 notifySignInAllowedChanged(); 128 } 129 } 130 131 /** 132 * Returns true if signin can be started now. 133 */ isSignInAllowed()134 public boolean isSignInAllowed() { 135 return !mFirstRunCheckIsPending && 136 mSignInAccount == null && 137 ChromeSigninController.get(mContext).getSignedInUser() == null; 138 } 139 addSignInAllowedObserver(SignInAllowedObserver observer)140 public void addSignInAllowedObserver(SignInAllowedObserver observer) { 141 mSignInAllowedObservers.addObserver(observer); 142 } 143 removeSignInAllowedObserver(SignInAllowedObserver observer)144 public void removeSignInAllowedObserver(SignInAllowedObserver observer) { 145 mSignInAllowedObservers.removeObserver(observer); 146 } 147 notifySignInAllowedChanged()148 private void notifySignInAllowedChanged() { 149 new Handler().post(new Runnable() { 150 @Override 151 public void run() { 152 for (SignInAllowedObserver observer : mSignInAllowedObservers) { 153 observer.onSignInAllowedChanged(); 154 } 155 } 156 }); 157 } 158 159 /** 160 * Starts the sign-in flow, and executes the callback when ready to proceed. 161 * <p/> 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. 165 * 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. 170 */ startSignIn( Activity activity, final Account account, boolean passive, final Observer observer)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; 176 177 if (mFirstRunCheckIsPending) { 178 Log.w(TAG, "Ignoring sign-in request until the First Run check completes."); 179 return; 180 } 181 182 mSignInActivity = activity; 183 mSignInAccount = account; 184 mSignInObserver = observer; 185 mPassive = passive; 186 187 notifySignInAllowedChanged(); 188 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. 192 doSignIn(); 193 return; 194 } 195 196 Log.d(TAG, "Checking if account has policy management enabled"); 197 // This will call back to onPolicyCheckedBeforeSignIn. 198 nativeCheckPolicyBeforeSignIn(mNativeSigninManagerAndroid, account.name); 199 } 200 201 @CalledByNative onPolicyCheckedBeforeSignIn(String managementDomain)202 private void onPolicyCheckedBeforeSignIn(String managementDomain) { 203 if (managementDomain == null) { 204 Log.d(TAG, "Account doesn't have policy"); 205 doSignIn(); 206 return; 207 } 208 209 if (ApplicationStatus.getStateForActivity(mSignInActivity) == ActivityState.DESTROYED) { 210 // The activity is no longer running, cancel sign in. 211 cancelSignIn(); 212 return; 213 } 214 215 if (mPassive) { 216 // If this is a passive interaction (e.g. auto signin) then don't show the confirmation 217 // dialog. 218 nativeFetchPolicyBeforeSignIn(mNativeSigninManagerAndroid); 219 return; 220 } 221 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, 226 managementDomain)); 227 builder.setPositiveButton( 228 R.string.policy_dialog_proceed, 229 new DialogInterface.OnClickListener() { 230 @Override 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; 236 } 237 }); 238 builder.setNegativeButton( 239 R.string.policy_dialog_cancel, 240 new DialogInterface.OnClickListener() { 241 @Override 242 public void onClick(DialogInterface dialog, int id) { 243 Log.d(TAG, "Cancelled sign-in"); 244 cancelSignIn(); 245 mPolicyConfirmationDialog = null; 246 } 247 }); 248 mPolicyConfirmationDialog = builder.create(); 249 mPolicyConfirmationDialog.setOnDismissListener( 250 new DialogInterface.OnDismissListener() { 251 @Override 252 public void onDismiss(DialogInterface dialog) { 253 if (mPolicyConfirmationDialog != null) { 254 Log.d(TAG, "Policy dialog dismissed, cancelling sign-in."); 255 cancelSignIn(); 256 mPolicyConfirmationDialog = null; 257 } 258 } 259 }); 260 mPolicyConfirmationDialog.show(); 261 } 262 263 @CalledByNative onPolicyFetchedBeforeSignIn()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. 267 doSignIn(); 268 } 269 doSignIn()270 private void doSignIn() { 271 Log.d(TAG, "Committing the sign-in process now"); 272 assert mSignInAccount != null; 273 274 // Cache the signed-in account name. 275 ChromeSigninController.get(mContext).setSignedInAccountName(mSignInAccount.name); 276 277 // Tell the native side that sign-in has completed. 278 nativeOnSignInCompleted(mNativeSigninManagerAndroid, mSignInAccount.name); 279 280 // Register for invalidations. 281 InvalidationController invalidationController = InvalidationController.get(mContext); 282 invalidationController.setRegisteredTypes(mSignInAccount, true, new HashSet<ModelType>()); 283 284 // Sign-in to sync. 285 ProfileSyncService profileSyncService = ProfileSyncService.get(mContext); 286 if (SyncStatusHelper.get(mContext).isSyncEnabled(mSignInAccount) && 287 !profileSyncService.hasSyncSetupCompleted()) { 288 profileSyncService.setSetupInProgress(true); 289 profileSyncService.syncSignIn(); 290 } 291 292 if (mSignInObserver != null) 293 mSignInObserver.onSigninComplete(); 294 295 // All done, cleanup. 296 Log.d(TAG, "Signin done"); 297 mSignInActivity = null; 298 mSignInAccount = null; 299 mSignInObserver = null; 300 notifySignInAllowedChanged(); 301 } 302 303 /** 304 * Signs out of Chrome. 305 * <p/> 306 * This method clears the signed-in username, stops sync and sends out a 307 * sign-out notification on the native side. 308 * 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. 313 */ signOut(Activity activity, Runnable callback)314 public void signOut(Activity activity, Runnable callback) { 315 mSignOutCallback = callback; 316 317 boolean wipeData = getManagementDomain() != null; 318 Log.d(TAG, "Signing out, wipe data? " + wipeData); 319 320 ChromeSigninController.get(mContext).clearSignedInUser(); 321 ProfileSyncService.get(mContext).signOut(); 322 nativeSignOut(mNativeSigninManagerAndroid); 323 324 if (wipeData) { 325 wipeProfileData(activity); 326 } else { 327 onSignOutDone(); 328 } 329 } 330 331 /** 332 * Returns the management domain if the signed in account is managed, otherwise returns null. 333 */ getManagementDomain()334 public String getManagementDomain() { 335 return nativeGetManagementDomain(mNativeSigninManagerAndroid); 336 } 337 logInSignedInUser()338 public void logInSignedInUser() { 339 nativeLogInSignedInUser(mNativeSigninManagerAndroid); 340 } 341 clearLastSignedInUser()342 public void clearLastSignedInUser() { 343 nativeClearLastSignedInUser(mNativeSigninManagerAndroid); 344 } 345 cancelSignIn()346 private void cancelSignIn() { 347 if (mSignInObserver != null) 348 mSignInObserver.onSigninCancelled(); 349 mSignInActivity = null; 350 mSignInObserver = null; 351 mSignInAccount = null; 352 notifySignInAllowedChanged(); 353 } 354 wipeProfileData(Activity activity)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( 362 activity, 363 activity.getString(R.string.wiping_profile_data_title), 364 activity.getString(R.string.wiping_profile_data_message), 365 indeterminate, cancelable); 366 } 367 // This will call back to onProfileDataWiped(). 368 nativeWipeProfileData(mNativeSigninManagerAndroid); 369 } 370 371 @CalledByNative onProfileDataWiped()372 private void onProfileDataWiped() { 373 if (mSignOutProgressDialog != null && mSignOutProgressDialog.isShowing()) 374 mSignOutProgressDialog.dismiss(); 375 onSignOutDone(); 376 } 377 onSignOutDone()378 private void onSignOutDone() { 379 if (mSignOutCallback != null) { 380 new Handler().post(mSignOutCallback); 381 mSignOutCallback = null; 382 } 383 } 384 385 /** 386 * @return True if the new profile management is enabled. 387 */ isNewProfileManagementEnabled()388 public static boolean isNewProfileManagementEnabled() { 389 return nativeIsNewProfileManagementEnabled(); 390 } 391 392 // Native methods. nativeInit()393 private native long nativeInit(); nativeShouldLoadPolicyForUser(String username)394 private native boolean nativeShouldLoadPolicyForUser(String username); nativeCheckPolicyBeforeSignIn( long nativeSigninManagerAndroid, String username)395 private native void nativeCheckPolicyBeforeSignIn( 396 long nativeSigninManagerAndroid, String username); nativeFetchPolicyBeforeSignIn(long nativeSigninManagerAndroid)397 private native void nativeFetchPolicyBeforeSignIn(long nativeSigninManagerAndroid); nativeOnSignInCompleted(long nativeSigninManagerAndroid, String username)398 private native void nativeOnSignInCompleted(long nativeSigninManagerAndroid, String username); nativeSignOut(long nativeSigninManagerAndroid)399 private native void nativeSignOut(long nativeSigninManagerAndroid); nativeGetManagementDomain(long nativeSigninManagerAndroid)400 private native String nativeGetManagementDomain(long nativeSigninManagerAndroid); nativeWipeProfileData(long nativeSigninManagerAndroid)401 private native void nativeWipeProfileData(long nativeSigninManagerAndroid); nativeClearLastSignedInUser(long nativeSigninManagerAndroid)402 private native void nativeClearLastSignedInUser(long nativeSigninManagerAndroid); nativeLogInSignedInUser(long nativeSigninManagerAndroid)403 private native void nativeLogInSignedInUser(long nativeSigninManagerAndroid); nativeIsNewProfileManagementEnabled()404 private static native boolean nativeIsNewProfileManagementEnabled(); 405 } 406