| 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.content.res.Resources; |
| 14 | import android.os.Handler; |
| 15 | import android.util.Log; |
| 16 | |
| 17 | import org.chromium.base.CalledByNative; |
| 18 | import org.chromium.base.ThreadUtils; |
| 19 | import org.chromium.chrome.browser.sync.ProfileSyncService; |
| 20 | import org.chromium.chrome.R; |
| 21 | import org.chromium.sync.internal_api.pub.base.ModelType; |
| 22 | import org.chromium.sync.notifier.InvalidationController; |
| 23 | import org.chromium.sync.notifier.SyncStatusHelper; |
| 24 | import org.chromium.sync.signin.ChromeSigninController; |
| 25 | |
| 26 | import java.util.HashSet; |
| 27 | |
| 28 | /** |
| 29 | * Android wrapper of the SigninManager which provides access from the Java layer. |
| 30 | * <p/> |
| 31 | * This class handles common paths during the sign-in and sign-out flows. |
| 32 | * <p/> |
| 33 | * Only usable from the UI thread as the native SigninManager requires its access to be in the |
| 34 | * UI thread. |
| 35 | * <p/> |
| 36 | * See chrome/browser/signin/signin_manager_android.h for more details. |
| 37 | */ |
| 38 | public class SigninManager { |
| 39 | |
| 40 | private static final String TAG = "SigninManager"; |
| 41 | |
| 42 | private static SigninManager sSigninManager; |
| 43 | |
| 44 | private final Context mContext; |
| 45 | private final int mNativeSigninManagerAndroid; |
| 46 | |
| 47 | private Activity mSignInActivity; |
| 48 | private Account mSignInAccount; |
| 49 | private Observer mSignInObserver; |
| 50 | private boolean mPassive = false; |
| 51 | |
| 52 | private ProgressDialog mSignOutProgressDialog; |
| 53 | private Runnable mSignOutCallback; |
| 54 | |
| 55 | private AlertDialog mPolicyConfirmationDialog; |
| 56 | |
| 57 | /** |
| 58 | * The Observer of startSignIn() will be notified when sign-in completes. |
| 59 | */ |
| 60 | public static interface Observer { |
| 61 | /** |
| 62 | * Invoked after sign-in completed successfully. |
| 63 | */ |
| 64 | public void onSigninComplete(); |
| 65 | |
| 66 | /** |
| 67 | * Invoked when the sign-in process was cancelled by the user. |
| 68 | * |
| 69 | * The user should have the option of going back and starting the process again, |
| 70 | * if possible. |
| 71 | */ |
| 72 | public void onSigninCancelled(); |
| 73 | } |
| 74 | |
| 75 | /** |
| 76 | * A helper method for retrieving the application-wide SigninManager. |
| 77 | * <p/> |
| 78 | * Can only be accessed on the main thread. |
| 79 | * |
| 80 | * @param context the ApplicationContext is retrieved from the context used as an argument. |
| 81 | * @return a singleton instance of the SigninManager. |
| 82 | */ |
| 83 | public static SigninManager get(Context context) { |
| 84 | ThreadUtils.assertOnUiThread(); |
| 85 | if (sSigninManager == null) { |
| 86 | sSigninManager = new SigninManager(context); |
| 87 | } |
| 88 | return sSigninManager; |
| 89 | } |
| 90 | |
| 91 | private SigninManager(Context context) { |
| 92 | ThreadUtils.assertOnUiThread(); |
| 93 | mContext = context.getApplicationContext(); |
| 94 | mNativeSigninManagerAndroid = nativeInit(); |
| 95 | } |
| 96 | |
| 97 | /** |
| 98 | * Starts the sign-in flow, and executes the callback when ready to proceed. |
| 99 | * <p/> |
| 100 | * This method checks with the native side whether the account has management enabled, and may |
| 101 | * present a dialog to the user to confirm sign-in. The callback is invoked once these processes |
| 102 | * and the common sign-in initialization complete. |
| 103 | * |
| 104 | * @param activity The context to use for the operation. |
| 105 | * @param account The account to sign in to. |
| 106 | * @param passive If passive is true then this operation should not interact with the user. |
| 107 | * @param callback The Observer to notify when the sign-in process is finished. |
| 108 | */ |
| 109 | public void startSignIn( |
| 110 | Activity activity, final Account account, boolean passive, final Observer observer) { |
| 111 | assert mSignInActivity == null; |
| 112 | assert mSignInAccount == null; |
| 113 | assert mSignInObserver == null; |
| 114 | mSignInActivity = activity; |
| 115 | mSignInAccount = account; |
| 116 | mSignInObserver = observer; |
| 117 | mPassive = passive; |
| 118 | |
| 119 | if (!nativeShouldLoadPolicyForUser(account.name)) { |
| 120 | // Proceed with the sign-in flow without checking for policy if it can be determined |
| 121 | // that this account can't have management enabled based on the username. |
| 122 | doSignIn(); |
| 123 | return; |
| 124 | } |
| 125 | |
| 126 | Log.d(TAG, "Checking if account has policy management enabled"); |
| 127 | // This will call back to onPolicyCheckedBeforeSignIn. |
| 128 | nativeCheckPolicyBeforeSignIn(mNativeSigninManagerAndroid, account.name); |
| 129 | } |
| 130 | |
| 131 | @CalledByNative |
| 132 | private void onPolicyCheckedBeforeSignIn(String managementDomain) { |
| 133 | if (managementDomain == null) { |
| 134 | Log.d(TAG, "Account doesn't have policy"); |
| 135 | doSignIn(); |
| 136 | return; |
| 137 | } |
| 138 | |
| 139 | if (mPassive) { |
| 140 | // The account has policy management, but the user should be asked before signing-in |
| 141 | // to an account with management enabled. Don't show the policy dialog since this is a |
| 142 | // passive interaction (e.g. auto signing-in), and just don't auto-signin in this case. |
| 143 | cancelSignIn(); |
| 144 | return; |
| 145 | } |
| 146 | |
| 147 | if (mSignInActivity.isDestroyed()) { |
| 148 | // The activity is no longer running, cancel sign in. |
| 149 | cancelSignIn(); |
| 150 | return; |
| 151 | } |
| 152 | |
| 153 | Log.d(TAG, "Account has policy management"); |
| 154 | AlertDialog.Builder builder = new AlertDialog.Builder(mSignInActivity); |
| 155 | builder.setTitle(R.string.policy_dialog_title); |
| 156 | builder.setMessage(mContext.getResources().getString(R.string.policy_dialog_message, |
| 157 | managementDomain)); |
| 158 | builder.setPositiveButton( |
| 159 | R.string.policy_dialog_proceed, |
| 160 | new DialogInterface.OnClickListener() { |
| 161 | @Override |
| 162 | public void onClick(DialogInterface dialog, int id) { |
| 163 | Log.d(TAG, "Accepted policy management, proceeding with sign-in"); |
| 164 | // This will call back to onPolicyFetchedBeforeSignIn. |
| 165 | nativeFetchPolicyBeforeSignIn(mNativeSigninManagerAndroid); |
| 166 | mPolicyConfirmationDialog = null; |
| 167 | } |
| 168 | }); |
| 169 | builder.setNegativeButton( |
| 170 | R.string.policy_dialog_cancel, |
| 171 | new DialogInterface.OnClickListener() { |
| 172 | @Override |
| 173 | public void onClick(DialogInterface dialog, int id) { |
| 174 | Log.d(TAG, "Cancelled sign-in"); |
| 175 | cancelSignIn(); |
| 176 | mPolicyConfirmationDialog = null; |
| 177 | } |
| 178 | }); |
| 179 | builder.setOnDismissListener( |
| 180 | new DialogInterface.OnDismissListener() { |
| 181 | @Override |
| 182 | public void onDismiss(DialogInterface dialog) { |
| 183 | if (mPolicyConfirmationDialog != null) { |
| 184 | Log.d(TAG, "Policy dialog dismissed, cancelling sign-in."); |
| 185 | cancelSignIn(); |
| 186 | mPolicyConfirmationDialog = null; |
| 187 | } |
| 188 | } |
| 189 | }); |
| 190 | mPolicyConfirmationDialog = builder.create(); |
| 191 | mPolicyConfirmationDialog.show(); |
| 192 | } |
| 193 | |
| 194 | @CalledByNative |
| 195 | private void onPolicyFetchedBeforeSignIn() { |
| 196 | // Policy has been fetched for the user and is being enforced; features like sync may now |
| 197 | // be disabled by policy, and the rest of the sign-in flow can be resumed. |
| 198 | doSignIn(); |
| 199 | } |
| 200 | |
| 201 | private void doSignIn() { |
| 202 | Log.d(TAG, "Committing the sign-in process now"); |
| 203 | assert mSignInAccount != null; |
| 204 | |
| 205 | // Cache the signed-in account name. |
| 206 | ChromeSigninController.get(mContext).setSignedInAccountName(mSignInAccount.name); |
| 207 | |
| 208 | // Tell the native side that sign-in has completed. |
| 209 | // This will trigger NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL. |
| 210 | nativeOnSignInCompleted(mNativeSigninManagerAndroid, mSignInAccount.name); |
| 211 | |
| 212 | // Register for invalidations. |
| 213 | InvalidationController invalidationController = InvalidationController.get(mContext); |
| 214 | invalidationController.setRegisteredTypes(mSignInAccount, true, new HashSet<ModelType>()); |
| 215 | |
| 216 | // Sign-in to sync. |
| 217 | ProfileSyncService profileSyncService = ProfileSyncService.get(mContext); |
| 218 | if (SyncStatusHelper.get(mContext).isSyncEnabled(mSignInAccount) && |
| 219 | !profileSyncService.hasSyncSetupCompleted()) { |
| 220 | profileSyncService.setSetupInProgress(true); |
| 221 | profileSyncService.syncSignIn(); |
| 222 | } |
| 223 | |
| 224 | if (mSignInObserver != null) |
| 225 | mSignInObserver.onSigninComplete(); |
| 226 | |
| 227 | // All done, cleanup. |
| 228 | Log.d(TAG, "Signin done"); |
| 229 | mSignInActivity = null; |
| 230 | mSignInAccount = null; |
| 231 | mSignInObserver = null; |
| 232 | } |
| 233 | |
| 234 | /** |
| 235 | * Signs out of Chrome. |
| 236 | * <p/> |
| 237 | * This method clears the signed-in username, stops sync and sends out a |
| 238 | * sign-out notification on the native side. |
| 239 | * |
| 240 | * @param activity If not null then a progress dialog is shown over the activity until signout |
| 241 | * completes, in case the account had management enabled. The activity must be valid until the |
| 242 | * callback is invoked. |
| 243 | * @param callback Will be invoked after signout completes, if not null. |
| 244 | */ |
| 245 | public void signOut(Activity activity, Runnable callback) { |
| 246 | mSignOutCallback = callback; |
| 247 | |
| 248 | boolean wipeData = getManagementDomain() != null; |
| 249 | Log.d(TAG, "Signing out, wipe data? " + wipeData); |
| 250 | |
| 251 | ChromeSigninController.get(mContext).clearSignedInUser(); |
| 252 | ProfileSyncService.get(mContext).signOut(); |
| 253 | nativeSignOut(mNativeSigninManagerAndroid); |
| 254 | |
| 255 | if (wipeData) { |
| 256 | wipeProfileData(activity); |
| 257 | } else { |
| 258 | onSignOutDone(); |
| 259 | } |
| 260 | } |
| 261 | |
| 262 | /** |
| 263 | * Returns the management domain if the signed in account is managed, otherwise returns null. |
| 264 | */ |
| 265 | public String getManagementDomain() { |
| 266 | return nativeGetManagementDomain(mNativeSigninManagerAndroid); |
| 267 | } |
| 268 | |
| 269 | public void logInSignedInUser() { |
| 270 | nativeLogInSignedInUser(mNativeSigninManagerAndroid); |
| 271 | } |
| 272 | |
| 273 | private void cancelSignIn() { |
| 274 | if (mSignInObserver != null) |
| 275 | mSignInObserver.onSigninCancelled(); |
| 276 | mSignInActivity = null; |
| 277 | mSignInObserver = null; |
| 278 | mSignInAccount = null; |
| 279 | } |
| 280 | |
| 281 | private void wipeProfileData(Activity activity) { |
| 282 | if (activity != null) { |
| 283 | // We don't have progress update, so this takes an indeterminate amount of time. |
| 284 | boolean indeterminate = true; |
| 285 | // This dialog is not cancelable by the user. |
| 286 | boolean cancelable = false; |
| 287 | mSignOutProgressDialog = ProgressDialog.show( |
| 288 | activity, |
| 289 | activity.getString(R.string.wiping_profile_data_title), |
| 290 | activity.getString(R.string.wiping_profile_data_message), |
| 291 | indeterminate, cancelable); |
| 292 | } |
| 293 | // This will call back to onProfileDataWiped(). |
| 294 | nativeWipeProfileData(mNativeSigninManagerAndroid); |
| 295 | } |
| 296 | |
| 297 | @CalledByNative |
| 298 | private void onProfileDataWiped() { |
| 299 | if (mSignOutProgressDialog != null && mSignOutProgressDialog.isShowing()) |
| 300 | mSignOutProgressDialog.dismiss(); |
| 301 | onSignOutDone(); |
| 302 | } |
| 303 | |
| 304 | private void onSignOutDone() { |
| 305 | if (mSignOutCallback != null) { |
| 306 | new Handler().post(mSignOutCallback); |
| 307 | mSignOutCallback = null; |
| 308 | } |
| 309 | } |
| 310 | |
| 311 | // Native methods. |
| 312 | private native int nativeInit(); |
| 313 | private native boolean nativeShouldLoadPolicyForUser(String username); |
| 314 | private native void nativeCheckPolicyBeforeSignIn( |
| 315 | int nativeSigninManagerAndroid, String username); |
| 316 | private native void nativeFetchPolicyBeforeSignIn(int nativeSigninManagerAndroid); |
| 317 | private native void nativeOnSignInCompleted(int nativeSigninManagerAndroid, String username); |
| 318 | private native void nativeSignOut(int nativeSigninManagerAndroid); |
| 319 | private native String nativeGetManagementDomain(int nativeSigninManagerAndroid); |
| 320 | private native void nativeWipeProfileData(int nativeSigninManagerAndroid); |
| 321 | private native void nativeLogInSignedInUser(int nativeSigninManagerAndroid); |
| 322 | } |