blob: 3f09eff36867707edfe5cfb9a0b3d1d5214bfd6f [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.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.support.annotation.MainThread;
import android.support.annotation.Nullable;
import org.chromium.base.ActivityState;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.Callback;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.ObserverList;
import org.chromium.base.Promise;
import org.chromium.base.ThreadUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.base.task.PostTask;
import org.chromium.chrome.browser.externalauth.ExternalAuthUtils;
import org.chromium.chrome.browser.externalauth.UserRecoverableErrorHandler;
import org.chromium.chrome.browser.sync.SyncUserDataWiper;
import org.chromium.components.signin.AccountIdProvider;
import org.chromium.components.signin.AccountManagerFacade;
import org.chromium.components.signin.AccountTrackerService;
import org.chromium.components.signin.ChromeSigninController;
import org.chromium.components.sync.AndroidSyncSettings;
import org.chromium.content_public.browser.UiThreadTaskTraits;
import java.util.ArrayList;
import java.util.List;
/**
* 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 implements AccountTrackerService.OnSystemAccountsSeededListener {
private static final String TAG = "SigninManager";
/**
* A SignInStateObserver is notified when the user signs in to or out of Chrome.
*/
public interface SignInStateObserver {
/**
* Invoked when the user has signed in to Chrome.
*/
void onSignedIn();
/**
* Invoked when the user has signed out of Chrome.
*/
void onSignedOut();
}
/**
* SignInAllowedObservers will be notified once signing-in becomes allowed or disallowed.
*/
public interface SignInAllowedObserver {
/**
* Invoked once all startup checks are done and signing-in becomes allowed, or disallowed.
*/
void onSignInAllowedChanged();
}
/**
* Callbacks for the sign-in flow.
*/
public interface SignInCallback {
/**
* Invoked after sign-in is completed successfully.
*/
void onSignInComplete();
/**
* Invoked if the sign-in processes does not complete for any reason.
*/
void onSignInAborted();
}
/**
* Hooks for wiping data during sign out.
*/
public interface WipeDataHooks {
/**
* Called before data is wiped.
*/
void preWipeData();
/**
* Called after data is wiped.
*/
void postWipeData();
}
/**
* Contains all the state needed for signin. This forces signin flow state to be
* cleared atomically, and all final fields to be set upon initialization.
*/
private static class SignInState {
final Account mAccount;
final Activity mActivity;
final SignInCallback mCallback;
/**
* If the system accounts need to be seeded, the sign in flow will block for that to occur.
* This boolean should be set to true during that time and then reset back to false
* afterwards. This allows the manager to know if it should progress the flow when the
* account tracker broadcasts updates.
*/
boolean mBlockedOnAccountSeeding;
/**
* @param account The account to sign in to.
* @param activity Reference to the UI to use for dialogs. Null means forced signin.
* @param callback Called when the sign-in process finishes or is cancelled. Can be null.
*/
SignInState(
Account account, @Nullable Activity activity, @Nullable SignInCallback callback) {
this.mAccount = account;
this.mActivity = activity;
this.mCallback = callback;
}
/**
* Returns whether this is an interactive sign-in flow.
*/
boolean isInteractive() {
return mActivity != null;
}
/**
* Returns whether the sign-in flow activity was set but is no longer visible to the user.
*/
boolean isActivityInvisible() {
return mActivity != null
&& (ApplicationStatus.getStateForActivity(mActivity) == ActivityState.STOPPED
|| ApplicationStatus.getStateForActivity(mActivity)
== ActivityState.DESTROYED);
}
}
/**
* Contains all the state needed for sign out. Like SignInState, this forces flow state to be
* cleared atomically, and all final fields to be set upon initialization.
*/
private static class SignOutState {
final Runnable mCallback;
final WipeDataHooks mWipeDataHooks;
final String mManagementDomain;
/**
* @param callback Called after sign-out finishes and all data has been cleared.
* @param wipeDataHooks Hooks to call before/after data wiping phase of sign-out.
* @param managementDomain Domain when account is managed.
*/
SignOutState(@Nullable Runnable callback, @Nullable WipeDataHooks wipeDataHooks,
@Nullable String managementDomain) {
this.mCallback = callback;
this.mWipeDataHooks = wipeDataHooks;
this.mManagementDomain = managementDomain;
}
}
@SuppressLint("StaticFieldLeak")
private static SigninManager sSigninManager;
private static int sSignInAccessPoint = SigninAccessPoint.UNKNOWN;
private final long mNativeSigninManagerAndroid;
private final Context mContext;
private final AccountTrackerService mAccountTrackerService;
private final AndroidSyncSettings mAndroidSyncSettings;
private final ObserverList<SignInStateObserver> mSignInStateObservers = new ObserverList<>();
private final ObserverList<SignInAllowedObserver> mSignInAllowedObservers =
new ObserverList<>();
private List<Runnable> mCallbacksWaitingForPendingOperation = new ArrayList<>();
private boolean mSigninAllowedByPolicy;
/**
* 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;
/**
* Will be set during the sign in process, and nulled out when there is not a pending sign in.
* Needs to be null checked after ever async entry point because it can be nulled out at any
* time by system accounts changing.
*/
private @Nullable SignInState mSignInState;
/**
* Set during sign-out process and nulled out once complete. Helps to atomically gather/clear
* various sign-out state.
*/
private @Nullable SignOutState mSignOutState;
/**
* A helper method for retrieving the application-wide SigninManager.
* <p/>
* Can only be accessed on the main thread.
*
* @return a singleton instance of the SigninManager.
*/
public static SigninManager get() {
ThreadUtils.assertOnUiThread();
if (sSigninManager == null) {
sSigninManager = new SigninManager();
}
return sSigninManager;
}
private SigninManager() {
this(ContextUtils.getApplicationContext(),
IdentityServicesProvider.getAccountTrackerService(), AndroidSyncSettings.get());
}
@VisibleForTesting
SigninManager(Context context, AccountTrackerService accountTrackerService,
AndroidSyncSettings androidSyncSettings) {
ThreadUtils.assertOnUiThread();
assert context != null;
assert accountTrackerService != null;
assert androidSyncSettings != null;
mContext = context;
mAccountTrackerService = accountTrackerService;
mAndroidSyncSettings = androidSyncSettings;
mNativeSigninManagerAndroid = nativeInit();
mSigninAllowedByPolicy = nativeIsSigninAllowedByPolicy(mNativeSigninManagerAndroid);
mAccountTrackerService.addSystemAccountsSeededListener(this);
}
/**
* Log the access point when the user see the view of choosing account to sign in.
* @param accessPoint the enum value of AccessPoint defined in signin_metrics.h.
*/
public static void logSigninStartAccessPoint(int accessPoint) {
RecordHistogram.recordEnumeratedHistogram(
"Signin.SigninStartedAccessPoint", accessPoint, SigninAccessPoint.MAX);
sSignInAccessPoint = accessPoint;
}
private void logSigninCompleteAccessPoint() {
RecordHistogram.recordEnumeratedHistogram(
"Signin.SigninCompletedAccessPoint", sSignInAccessPoint, SigninAccessPoint.MAX);
sSignInAccessPoint = SigninAccessPoint.UNKNOWN;
}
/**
* 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 !mFirstRunCheckIsPending && mSignInState == null && mSigninAllowedByPolicy
&& ChromeSigninController.get().getSignedInUser() == null && isSigninSupported();
}
/**
* Returns true if signin is disabled by policy.
*/
public boolean isSigninDisabledByPolicy() {
return !mSigninAllowedByPolicy;
}
/**
* @return Whether true if the current user is not demo user and the user has a reasonable
* Google Play Services installed.
*/
public boolean isSigninSupported() {
return !ApiCompatibilityUtils.isDemoUser(mContext)
&& !ExternalAuthUtils.getInstance().isGooglePlayServicesMissing(mContext);
}
/**
* @return Whether force sign-in is enabled by policy.
*/
public boolean isForceSigninEnabled() {
return nativeIsForceSigninEnabled(mNativeSigninManagerAndroid);
}
/**
* Registers a SignInStateObserver to be notified when the user signs in or out of Chrome.
*/
public void addSignInStateObserver(SignInStateObserver observer) {
mSignInStateObservers.addObserver(observer);
}
/**
* Unregisters a SignInStateObserver to be notified when the user signs in or out of Chrome.
*/
public void removeSignInStateObserver(SignInStateObserver observer) {
mSignInStateObservers.removeObserver(observer);
}
public void addSignInAllowedObserver(SignInAllowedObserver observer) {
mSignInAllowedObservers.addObserver(observer);
}
public void removeSignInAllowedObserver(SignInAllowedObserver observer) {
mSignInAllowedObservers.removeObserver(observer);
}
private void notifySignInAllowedChanged() {
PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
for (SignInAllowedObserver observer : mSignInAllowedObservers) {
observer.onSignInAllowedChanged();
}
});
}
/**
* Continue pending sign in after system accounts have been seeded into AccountTrackerService.
*/
@Override
public void onSystemAccountsSeedingComplete() {
if (mSignInState != null && mSignInState.mBlockedOnAccountSeeding) {
mSignInState.mBlockedOnAccountSeeding = false;
progressSignInFlowCheckPolicy();
}
}
/**
* Clear pending sign in when system accounts in AccountTrackerService were refreshed.
*/
@Override
public void onSystemAccountsChanged() {
if (mSignInState != null) {
abortSignIn();
}
}
/**
* Starts the sign-in flow, and executes the callback when finished.
*
* If an activity is provided, it is considered an "interactive" sign-in and the user can be
* prompted to confirm various aspects of sign-in using dialogs inside the activity.
* The sign-in flow goes through the following steps:
*
* - Wait for AccountTrackerService to be seeded.
* - If interactive, confirm the account change with the user.
* - Wait for policy to be checked for the account.
* - If interactive and the account is managed, warn the user.
* - If managed, wait for the policy to be fetched.
* - Complete sign-in with the native SigninManager and kick off token requests.
* - Call the callback if provided.
*
* @param account The account to sign in to.
* @param activity The activity used to launch UI prompts, or null for a forced signin.
* @param callback Optional callback for when the sign-in process is finished.
*/
public void signIn(
Account account, @Nullable Activity activity, @Nullable SignInCallback callback) {
if (account == null) {
Log.w(TAG, "Ignoring sign-in request due to null account.");
if (callback != null) callback.onSignInAborted();
return;
}
if (mSignInState != null) {
Log.w(TAG, "Ignoring sign-in request as another sign-in request is pending.");
if (callback != null) callback.onSignInAborted();
return;
}
if (mFirstRunCheckIsPending) {
Log.w(TAG, "Ignoring sign-in request until the First Run check completes.");
if (callback != null) callback.onSignInAborted();
return;
}
mSignInState = new SignInState(account, activity, callback);
notifySignInAllowedChanged();
progressSignInFlowSeedSystemAccounts();
}
/**
* Same as above but retrieves the Account object for the given accountName.
*/
public void signIn(String accountName, @Nullable final Activity activity,
@Nullable final SignInCallback callback) {
AccountManagerFacade.get().getAccountFromName(
accountName, account -> signIn(account, activity, callback));
}
private void progressSignInFlowSeedSystemAccounts() {
if (mAccountTrackerService.checkAndSeedSystemAccounts()) {
progressSignInFlowCheckPolicy();
} else if (AccountIdProvider.getInstance().canBeUsed()) {
mSignInState.mBlockedOnAccountSeeding = true;
} else {
Activity activity = mSignInState.mActivity;
UserRecoverableErrorHandler errorHandler = activity != null
? new UserRecoverableErrorHandler.ModalDialog(activity, !isForceSigninEnabled())
: new UserRecoverableErrorHandler.SystemNotification();
ExternalAuthUtils.getInstance().canUseGooglePlayServices(errorHandler);
Log.w(TAG, "Cancelling the sign-in process as Google Play services is unavailable");
abortSignIn();
}
}
/**
* Continues the signin flow by checking if there is a policy that the account is subject to.
*/
private void progressSignInFlowCheckPolicy() {
if (mSignInState == null) {
Log.w(TAG, "Ignoring sign in progress request as no pending sign in.");
return;
}
if (mSignInState.isActivityInvisible()) {
abortSignIn();
return;
}
if (!nativeShouldLoadPolicyForUser(mSignInState.mAccount.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.
finishSignIn();
return;
}
Log.d(TAG, "Checking if account has policy management enabled");
// This will call back to onPolicyCheckedBeforeSignIn.
nativeCheckPolicyBeforeSignIn(mNativeSigninManagerAndroid, mSignInState.mAccount.name);
}
@CalledByNative
@VisibleForTesting
void onPolicyCheckedBeforeSignIn(String managementDomain) {
assert mSignInState != null;
if (managementDomain == null) {
Log.d(TAG, "Account doesn't have policy");
finishSignIn();
return;
}
if (mSignInState.isActivityInvisible()) {
abortSignIn();
return;
}
// The user has already been notified that they are signing into a managed account.
// This will call back to onPolicyFetchedBeforeSignIn.
nativeFetchPolicyBeforeSignIn(mNativeSigninManagerAndroid);
}
@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.
finishSignIn();
}
private void finishSignIn() {
// This method should be called at most once per sign-in flow.
assert mSignInState != null;
// Tell the native side that sign-in has completed.
nativeOnSignInCompleted(mNativeSigninManagerAndroid, mSignInState.mAccount.name);
// Cache the signed-in account name. This must be done after the native call, otherwise
// sync tries to start without being signed in natively and crashes.
ChromeSigninController.get().setSignedInAccountName(mSignInState.mAccount.name);
mAndroidSyncSettings.updateAccount(mSignInState.mAccount);
mAndroidSyncSettings.enableChromeSync();
if (mSignInState.mCallback != null) {
mSignInState.mCallback.onSignInComplete();
}
// Trigger token requests via native.
logInSignedInUser();
if (mSignInState.isInteractive()) {
// If signin was a user action, record that it succeeded.
RecordUserAction.record("Signin_Signin_Succeed");
logSigninCompleteAccessPoint();
// Log signin in reason as defined in signin_metrics.h. Right now only
// SIGNIN_PRIMARY_ACCOUNT available on Android.
RecordHistogram.recordEnumeratedHistogram("Signin.SigninReason",
SigninReason.SIGNIN_PRIMARY_ACCOUNT, SigninReason.MAX);
}
Log.d(TAG, "Signin completed.");
mSignInState = null;
notifyCallbacksWaitingForOperation();
notifySignInAllowedChanged();
for (SignInStateObserver observer : mSignInStateObservers) {
observer.onSignedIn();
}
}
/**
* Returns true if a sign-in or sign-out operation is in progress. See also
* {@link #runAfterOperationInProgress}.
*/
@MainThread
public boolean isOperationInProgress() {
ThreadUtils.assertOnUiThread();
return mSignInState != null || mSignOutState != null;
}
/**
* Schedules the runnable to be invoked after currently ongoing a sign-in or sign-out operation
* is finished. If there's no operation is progress, posts the callback to the UI thread right
* away. See also {@link #isOperationInProgress}.
*/
@MainThread
public void runAfterOperationInProgress(Runnable runnable) {
ThreadUtils.assertOnUiThread();
if (isOperationInProgress()) {
mCallbacksWaitingForPendingOperation.add(runnable);
return;
}
PostTask.postTask(UiThreadTaskTraits.DEFAULT, runnable);
}
private void notifyCallbacksWaitingForOperation() {
ThreadUtils.assertOnUiThread();
for (Runnable callback : mCallbacksWaitingForPendingOperation) {
PostTask.postTask(UiThreadTaskTraits.DEFAULT, callback);
}
mCallbacksWaitingForPendingOperation.clear();
}
/**
* Invokes signOut and returns a {@link Promise} that will be fulfilled on completion.
* This is equivalent to calling {@link #signOut(@SignoutReason int signoutSource, Runnable
* callback)} with a callback that fulfills the returned {@link Promise}.
*/
public Promise<Void> signOutPromise(@SignoutReason int signoutSource) {
final Promise<Void> promise = new Promise<>();
signOut(signoutSource, () -> promise.fulfill(null));
return promise;
}
/**
* Invokes signOut with no callback or wipeDataHooks.
*/
public void signOut(@SignoutReason int signoutSource) {
signOut(signoutSource, null, null);
}
/**
* Invokes signOut() with no wipeDataHooks.
*/
public void signOut(@SignoutReason int signoutSource, Runnable callback) {
signOut(signoutSource, callback, null);
}
/**
* 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 signoutSource describes the event driving the signout (e.g.
* {@link SignoutReason.USER_CLICKED_SIGNOUT_SETTINGS}).
* @param callback Will be invoked after sign-out completes, if not null.
* @param wipeDataHooks Hooks to call before/after data wiping phase of sign-out.
*/
public void signOut(
@SignoutReason int signoutSource, Runnable callback, WipeDataHooks wipeDataHooks) {
// Only one signOut at a time!
assert mSignOutState == null;
// Grab the management domain before nativeSignOut() potentially clears it.
mSignOutState = new SignOutState(callback, wipeDataHooks, getManagementDomain());
Log.d(TAG, "Signing out, management domain: " + mSignOutState.mManagementDomain);
// User data will be wiped in resetAccountData(), called from onNativeSignOut().
nativeSignOut(mNativeSigninManagerAndroid, signoutSource);
}
/**
* Returns the management domain if the signed in account is managed, otherwise returns null.
*/
public String getManagementDomain() {
return nativeGetManagementDomain(mNativeSigninManagerAndroid);
}
@VisibleForTesting
void logInSignedInUser() {
nativeLogInSignedInUser(mNativeSigninManagerAndroid);
}
public void clearLastSignedInUser() {
nativeClearLastSignedInUser(mNativeSigninManagerAndroid);
}
/**
* Aborts the current sign in.
*
* Package protected to allow dialog fragments to abort the signin flow.
*/
void abortSignIn() {
// Ensure this function can only run once per signin flow.
SignInState signInState = mSignInState;
assert signInState != null;
mSignInState = null;
notifyCallbacksWaitingForOperation();
if (signInState.mCallback != null) {
signInState.mCallback.onSignInAborted();
}
nativeAbortSignIn(mNativeSigninManagerAndroid);
Log.d(TAG, "Signin flow aborted.");
notifySignInAllowedChanged();
}
@VisibleForTesting
@CalledByNative
void onNativeSignOut() {
if (mSignOutState == null) {
// TODO(https://crbug.com/873671): Management domain is not captured in signOut() for
// sign-outs that are initiated from the native side. But grabbing it here may be too
// late! The management domain may be already cleared due to race condition with
// sign-out observers on the native side.
mSignOutState = new SignOutState(null, null, getManagementDomain());
}
Log.d(TAG, "Native signed out, management domain: " + mSignOutState.mManagementDomain);
// Native sign-out must happen before resetting the account so data is deleted correctly.
// http://crbug.com/589028
resetAccountData();
}
/**
* Called AFTER native sign-out is complete, this method clears various
* account and profile data associated with the previous signin.
*/
void resetAccountData() {
// Should be set at beginning of sign-out flow.
assert mSignOutState != null;
ChromeSigninController.get().setSignedInAccountName(null);
mAndroidSyncSettings.updateAccount(null);
if (mSignOutState.mManagementDomain != null) {
wipeProfileData();
} else {
wipeGoogleServiceWorkerCaches();
}
mAccountTrackerService.invalidateAccountSeedStatus(true);
}
private void wipeProfileData() {
// Should be set at start of sign-out flow.
assert mSignOutState != null;
if (mSignOutState.mWipeDataHooks != null) mSignOutState.mWipeDataHooks.preWipeData();
// This will call back to onProfileDataWiped().
nativeWipeProfileData(mNativeSigninManagerAndroid);
}
private void wipeGoogleServiceWorkerCaches() {
// Should be set at start of sign-out flow.
assert mSignOutState != null;
if (mSignOutState.mWipeDataHooks != null) mSignOutState.mWipeDataHooks.preWipeData();
// This will call back to onProfileDataWiped().
nativeWipeGoogleServiceWorkerCaches(mNativeSigninManagerAndroid);
}
/**
* Convenience method to return a Promise to be fulfilled when the user's sync data has been
* wiped if the parameter is true, or an already fulfilled Promise if the parameter is false.
*/
public static Promise<Void> wipeSyncUserDataIfRequired(boolean required) {
if (required) {
return SyncUserDataWiper.wipeSyncUserData();
} else {
return Promise.fulfilled(null);
}
}
@CalledByNative
@VisibleForTesting
protected void onProfileDataWiped() {
// Should be set at start of sign-out flow.
assert mSignOutState != null;
if (mSignOutState.mWipeDataHooks != null) mSignOutState.mWipeDataHooks.postWipeData();
finishSignOut();
}
private void finishSignOut() {
// Should be set at start of sign-out flow.
assert mSignOutState != null;
if (mSignOutState.mCallback != null) {
PostTask.postTask(UiThreadTaskTraits.DEFAULT, mSignOutState.mCallback);
}
mSignOutState = null;
notifyCallbacksWaitingForOperation();
for (SignInStateObserver observer : mSignInStateObservers) {
observer.onSignedOut();
}
}
/**
* @return Whether there is a signed in account on the native side.
*/
public boolean isSignedInOnNative() {
return nativeIsSignedInOnNative(mNativeSigninManagerAndroid);
}
@CalledByNative
private void onSigninAllowedByPolicyChanged(boolean newSigninAllowedByPolicy) {
mSigninAllowedByPolicy = newSigninAllowedByPolicy;
notifySignInAllowedChanged();
}
/**
* Performs an asynchronous check to see if the user is a managed user.
* @param callback A callback to be called with true if the user is a managed user and false
* otherwise. May be called synchronously from this function.
*/
public void isUserManaged(String email, final Callback<Boolean> callback) {
if (nativeShouldLoadPolicyForUser(email)) {
nativeIsUserManaged(email, callback);
} else {
callback.onResult(false);
}
}
public String extractDomainName(String email) {
return nativeExtractDomainName(email);
}
@VisibleForTesting
public static void setInstanceForTesting(SigninManager signinManager) {
sSigninManager = signinManager;
}
// Native methods.
@VisibleForTesting
native long nativeInit();
@VisibleForTesting
native boolean nativeIsSigninAllowedByPolicy(long nativeSigninManagerAndroid);
@VisibleForTesting
native boolean nativeIsForceSigninEnabled(long nativeSigninManagerAndroid);
@VisibleForTesting
native void nativeCheckPolicyBeforeSignIn(long nativeSigninManagerAndroid, String username);
@VisibleForTesting
native void nativeFetchPolicyBeforeSignIn(long nativeSigninManagerAndroid);
@VisibleForTesting
native void nativeAbortSignIn(long nativeSigninManagerAndroid);
@VisibleForTesting
native void nativeOnSignInCompleted(long nativeSigninManagerAndroid, String username);
@VisibleForTesting
native void nativeSignOut(long nativeSigninManagerAndroid, @SignoutReason int reason);
@VisibleForTesting
native String nativeGetManagementDomain(long nativeSigninManagerAndroid);
@VisibleForTesting
native void nativeWipeProfileData(long nativeSigninManagerAndroid);
@VisibleForTesting
native void nativeWipeGoogleServiceWorkerCaches(long nativeSigninManagerAndroid);
@VisibleForTesting
native void nativeClearLastSignedInUser(long nativeSigninManagerAndroid);
@VisibleForTesting
native void nativeLogInSignedInUser(long nativeSigninManagerAndroid);
@VisibleForTesting
native boolean nativeIsSignedInOnNative(long nativeSigninManagerAndroid);
@VisibleForTesting
native boolean nativeShouldLoadPolicyForUser(String username);
@VisibleForTesting
native void nativeIsUserManaged(String username, Callback<Boolean> callback);
@VisibleForTesting
native String nativeExtractDomainName(String email);
}