| Index: chrome/android/java/src/org/chromium/chrome/browser/childaccounts/ChildAccountService.java
|
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/childaccounts/ChildAccountService.java b/chrome/android/java/src/org/chromium/chrome/browser/childaccounts/ChildAccountService.java
|
| index 72bc73819ae8571becc1f77dd3bbaca6bd8a5482..ffbb7faf647a7bcfa9683fb48daa219cbfb7723b 100644
|
| --- a/chrome/android/java/src/org/chromium/chrome/browser/childaccounts/ChildAccountService.java
|
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/childaccounts/ChildAccountService.java
|
| @@ -10,90 +10,29 @@ import android.accounts.AccountManagerFuture;
|
| import android.accounts.AuthenticatorException;
|
| import android.accounts.OperationCanceledException;
|
| import android.content.Context;
|
| -import android.content.Intent;
|
|
|
| -import org.chromium.base.CommandLine;
|
| import org.chromium.base.Log;
|
| import org.chromium.base.ThreadUtils;
|
| -import org.chromium.base.TraceEvent;
|
| -import org.chromium.base.VisibleForTesting;
|
| -import org.chromium.base.annotations.CalledByNative;
|
| -import org.chromium.chrome.browser.ChromeSwitches;
|
| -import org.chromium.chrome.browser.services.AccountsChangedReceiver;
|
| -import org.chromium.chrome.browser.signin.SigninManager;
|
| import org.chromium.sync.signin.AccountManagerHelper;
|
| -import org.chromium.sync.signin.ChromeSigninController;
|
|
|
| import java.io.IOException;
|
| -import java.util.ArrayList;
|
| -import java.util.List;
|
| -import java.util.Timer;
|
| -import java.util.TimerTask;
|
| -
|
| -import javax.annotation.Nullable;
|
|
|
| /**
|
| - * This class detects child accounts and enables special treatment for them.
|
| + * This class serves as a simple interface for querying the child account information.
|
| + * It has two methods namely, checkHasChildAccount(...) which is asynchronous and queries the
|
| + * system directly for the information and the synchronous isChildAccount() which asks the native
|
| + * side assuming it has been set correctly already.
|
| + *
|
| + * The former method is used by ForcedSigninProcessor and FirstRunFlowSequencer to detect child
|
| + * accounts since the native side is only activated on signing in.
|
| + * Once signed in by the ForcedSigninProcessor, the ChildAccountInfoFetcher will notify the native
|
| + * side and also takes responsibility for monitoring changes and taking a suitable action.
|
| */
|
| public class ChildAccountService {
|
| + private static final String TAG = "ChildAccountService";
|
|
|
| - private static final String TAG = "cr.ChildAccountService";
|
| -
|
| - /**
|
| - * The maximum amount of time to wait for the initial child account check, in milliseconds.
|
| - */
|
| - private static final int CHILD_ACCOUNT_TIMEOUT_MS = 1000;
|
| -
|
| - private static ChildAccountService sChildAccountService;
|
| -
|
| - private final Context mContext;
|
| -
|
| - /**
|
| - * Non-null if the the child account status has been determined.
|
| - */
|
| - private Boolean mHasChildAccount;
|
| -
|
| - /**
|
| - * Non-null while a child account check is in progress. Note that if the child account status
|
| - * has been previously determined, the externally visible status only changes when the check
|
| - * finishes. This means that before that, calls to {@link #checkHasChildAccount} and
|
| - * {@link #hasChildAccount} will return the last value, even if it is now stale.
|
| - */
|
| - private AccountManagerFuture<Boolean> mAccountManagerFuture;
|
| -
|
| - /**
|
| - * Non-empty while the initial child account check is in progress.
|
| - */
|
| - private final List<HasChildAccountCallback> mCallbacks = new ArrayList<>();
|
| -
|
| - protected ChildAccountService(Context context) {
|
| - mContext = context;
|
| - AccountsChangedReceiver.addObserver(
|
| - new AccountsChangedReceiver.AccountsChangedObserver() {
|
| - @Override
|
| - public void onAccountsChanged(Context context, Intent intent) {
|
| - ThreadUtils.runOnUiThread(new Runnable() {
|
| - @Override
|
| - public void run() {
|
| - recheckChildAccountStatus();
|
| - }
|
| - });
|
| - }
|
| - });
|
| - }
|
| -
|
| - /**
|
| - * Returns the shared ChildAccountService instance, creating one if necessary.
|
| - *
|
| - * @param context The context to initialize the ChildAccountService with.
|
| - * @return The shared instance.
|
| - */
|
| - public static ChildAccountService getInstance(Context context) {
|
| - ThreadUtils.assertOnUiThread();
|
| - if (sChildAccountService == null) {
|
| - sChildAccountService = new ChildAccountService(context.getApplicationContext());
|
| - }
|
| - return sChildAccountService;
|
| + private ChildAccountService() {
|
| + // Only for static usage.
|
| }
|
|
|
| /**
|
| @@ -111,234 +50,53 @@ public class ChildAccountService {
|
| /**
|
| * Checks for the presence of child accounts on the device.
|
| *
|
| - * @param callback Will be called with the result (see
|
| - * {@link HasChildAccountCallback#onChildAccountChecked}). The callback is guaranteed
|
| - * to be called on a future turn of the event loop, even if the result can be
|
| - * determined immediately.
|
| + * @param callback A callback which will be called with the result.
|
| */
|
| - public void checkHasChildAccount(final HasChildAccountCallback callback) {
|
| - if (mHasChildAccount != null || maybeUpdatePredeterminedChildAccountStatus()) {
|
| - postCallback(callback);
|
| + public static void checkHasChildAccount(
|
| + Context context, final HasChildAccountCallback callback) {
|
| + ThreadUtils.assertOnUiThread();
|
| + if (!nativeIsChildAccountDetectionEnabled()) {
|
| + callback.onChildAccountChecked(false);
|
| return;
|
| }
|
| - mCallbacks.add(callback);
|
| - if (mAccountManagerFuture == null) requestChildAccountStatus();
|
| - }
|
| -
|
| - private void postCallback(final HasChildAccountCallback callback) {
|
| - final boolean hasChildAccount = mHasChildAccount;
|
| - ThreadUtils.postOnUiThread(new Runnable() {
|
| + AccountManagerHelper helper = AccountManagerHelper.get(context);
|
| + Account[] accounts = helper.getGoogleAccounts();
|
| + if (accounts.length != 1) {
|
| + callback.onChildAccountChecked(false);
|
| + return;
|
| + }
|
| + helper.checkChildAccount(accounts[0], new AccountManagerCallback<Boolean>() {
|
| @Override
|
| - public void run() {
|
| - callback.onChildAccountChecked(hasChildAccount);
|
| + public void run(AccountManagerFuture<Boolean> future) {
|
| + assert future.isDone();
|
| + boolean hasFeatures = false;
|
| + try {
|
| + hasFeatures = future.getResult();
|
| + } catch (AuthenticatorException | IOException e) {
|
| + Log.e(TAG, "Error while checking features: ", e);
|
| + } catch (OperationCanceledException e) {
|
| + Log.e(TAG, "Checking features was cancelled. This should not happen.");
|
| + }
|
| + callback.onChildAccountChecked(hasFeatures);
|
| }
|
| });
|
| }
|
|
|
| /**
|
| - * Updates the child account status if it can be determined immediately.
|
| - *
|
| - * @return Whether the child account status was updated.
|
| - */
|
| - private boolean maybeUpdatePredeterminedChildAccountStatus() {
|
| - Boolean predeterminedChildAccountStatus = getPredeterminedChildStatus();
|
| - if (predeterminedChildAccountStatus == null) return false;
|
| - setHasChildAccount(predeterminedChildAccountStatus);
|
| - return true;
|
| - }
|
| -
|
| - /**
|
| - * @return The child account status if it can be determined immediately, or null otherwise.
|
| - */
|
| - @Nullable
|
| - private Boolean getPredeterminedChildStatus() {
|
| - if (!nativeIsChildAccountDetectionEnabled()) {
|
| - Log.v(TAG, "Child account detection disabled");
|
| - return false;
|
| - }
|
| - AccountManagerHelper accountManagerHelper = AccountManagerHelper.get(mContext);
|
| - // This isn't strictly necessary, as getGoogleAccounts() will return an empty list if the
|
| - // GET_ACCOUNTS permission is not granted, but it makes the behavior explicit.
|
| - if (!accountManagerHelper.hasGetAccountsPermission()) {
|
| - Log.v(TAG, "GET_ACCOUNTS permission not granted");
|
| - return false;
|
| - }
|
| - Account[] googleAccounts = accountManagerHelper.getGoogleAccounts();
|
| - if (googleAccounts.length != 1) {
|
| - if (CommandLine.getInstance().hasSwitch(ChromeSwitches.CHILD_ACCOUNT)) {
|
| - Log.w(TAG, "Ignoring --" + ChromeSwitches.CHILD_ACCOUNT + " command line flag "
|
| - + "because there are " + googleAccounts.length + " Google accounts on the "
|
| - + "device");
|
| - } else {
|
| - Log.v(TAG, googleAccounts.length + " Google accounts on the device");
|
| - }
|
| - return false;
|
| - }
|
| - String childAccountName =
|
| - CommandLine.getInstance().getSwitchValue(ChromeSwitches.CHILD_ACCOUNT);
|
| - String accountName = googleAccounts[0].name;
|
| - if (childAccountName != null && accountName.equals(childAccountName)) {
|
| - Log.v(TAG, "Child account forced via command line for " + childAccountName);
|
| - return true;
|
| - }
|
| - return null;
|
| - }
|
| -
|
| - private void requestChildAccountStatus() {
|
| - assert mAccountManagerFuture == null;
|
| -
|
| - final Timer timer = new Timer();
|
| - final int traceId = System.identityHashCode(this);
|
| - TraceEvent.startAsync("ChildAccountService.checkFeatures", traceId);
|
| - AccountManagerHelper accountManagerHelper = AccountManagerHelper.get(mContext);
|
| - final AccountManagerFuture<Boolean> future = accountManagerHelper.checkChildAccount(
|
| - accountManagerHelper.getSingleGoogleAccount(),
|
| - new AccountManagerCallback<Boolean>() {
|
| - @Override
|
| - public void run(AccountManagerFuture<Boolean> future) {
|
| - TraceEvent.finishAsync("ChildAccountService.checkFeatures", traceId);
|
| -
|
| - timer.cancel();
|
| -
|
| - assert future.isDone();
|
| -
|
| - // Ignore any future that is not the current one.
|
| - if (future == mAccountManagerFuture) {
|
| - setHasChildAccount(getFutureResult());
|
| - }
|
| - }
|
| - });
|
| -
|
| - // Add a timeout during the initial check, to avoid blocking startup for too long.
|
| - if (mHasChildAccount == null) {
|
| - timer.schedule(new TimerTask() {
|
| - @Override
|
| - public void run() {
|
| - if (!future.isDone()) {
|
| - Log.v(TAG, "AM request timed out");
|
| - future.cancel(true);
|
| - }
|
| - }
|
| - }, CHILD_ACCOUNT_TIMEOUT_MS);
|
| - }
|
| - mAccountManagerFuture = future;
|
| - }
|
| -
|
| - private boolean getFutureResult() {
|
| - boolean result = false;
|
| - try {
|
| - result = mAccountManagerFuture.getResult();
|
| - Log.v(TAG, "AM future result:" + result);
|
| - } catch (OperationCanceledException e) {
|
| - Log.e(TAG, "Timed out fetching child account flag: ", e);
|
| - } catch (AuthenticatorException | IOException e) {
|
| - Log.e(TAG, "Error while fetching child account flag: ", e);
|
| - } finally {
|
| - mAccountManagerFuture = null;
|
| - }
|
| - return result;
|
| - }
|
| -
|
| - /**
|
| - * Returns whether there is a child account on the device. If the initial check has not
|
| - * completed yet, this will return a cached value from the last run (which might be stale now).
|
| - * Because this method might call into native code, it may only be called after the native
|
| - * library and the profile have been loaded.
|
| - *
|
| - * @return Whether there is a child account on the device.
|
| - */
|
| - public boolean hasChildAccount() {
|
| - ThreadUtils.assertOnUiThread();
|
| -
|
| - if (mHasChildAccount == null) return nativeGetIsChildAccount();
|
| -
|
| - return mHasChildAccount;
|
| - }
|
| -
|
| - private void setHasChildAccount(boolean hasChildAccount) {
|
| - Boolean oldHasChildAccount = mHasChildAccount;
|
| - mHasChildAccount = hasChildAccount;
|
| - for (HasChildAccountCallback callback : mCallbacks) {
|
| - postCallback(callback);
|
| - }
|
| - mCallbacks.clear();
|
| -
|
| - onChildAccountStatusUpdated(oldHasChildAccount);
|
| - }
|
| -
|
| - /**
|
| - * Called when the child account status has been determined or updated.
|
| - * Can be overridden by subclasses to avoid native calls and calls into dependencies in testing.
|
| + * Returns the previously determined value of whether there is a child account on the device.
|
| + * Should only be called after the native library and profile have been loaded.
|
| *
|
| - * @param oldValue The old child account status. This is null when the child account status
|
| - * has been determined for the first time after the browser has started.
|
| - */
|
| - protected void onChildAccountStatusUpdated(Boolean oldValue) {
|
| - Log.v(TAG, "hasChildAccount: " + mHasChildAccount + " oldHasChildAccount: " + oldValue);
|
| - if (mHasChildAccount) {
|
| - if (oldValue == null) {
|
| - // This is the first time we have determined the child account status, which means
|
| - // the browser is starting up. If we are not signed in yet, the startup code will
|
| - // sign in and call us back in onChildAccountSigninComplete().
|
| - if (ChromeSigninController.get(mContext).getSignedInUser() == null) return;
|
| - } else if (!oldValue.booleanValue()) {
|
| - // We have switched from no child account to child account while the browser
|
| - // is running. Sign in (which will call us back in onChildAccountSigninComplete()).
|
| - SigninManager signinManager = SigninManager.get(mContext);
|
| - Account account = AccountManagerHelper.get(mContext).getSingleGoogleAccount();
|
| - signinManager.signInToSelectedAccount(null, account,
|
| - SigninManager.SIGNIN_TYPE_FORCED_CHILD_ACCOUNT,
|
| - SigninManager.SIGNIN_SYNC_IMMEDIATELY, false, null);
|
| - return;
|
| - }
|
| - }
|
| - // Fallthrough for all other cases: Propagate child account status to native code.
|
| - // This is a no-op if the child account status does not change.
|
| - nativeSetIsChildAccount(mHasChildAccount);
|
| - }
|
| -
|
| - /**
|
| - * Called when the browser has been signed in to the child account.
|
| + * @return The previously determined value of whether there is a child account on the device.
|
| */
|
| - public void onChildAccountSigninComplete() {
|
| - nativeSetIsChildAccount(true);
|
| - }
|
| -
|
| - @VisibleForTesting
|
| - void recheckChildAccountStatus() {
|
| - // Cancel the AccountManagerFuture if it is running.
|
| - if (mAccountManagerFuture != null) {
|
| - mAccountManagerFuture.cancel(true);
|
| - mAccountManagerFuture = null;
|
| - }
|
| - if (!maybeUpdatePredeterminedChildAccountStatus()) {
|
| - requestChildAccountStatus();
|
| - }
|
| + public static boolean isChildAccount() {
|
| + return nativeIsChildAccount();
|
| }
|
|
|
| - @CalledByNative
|
| - private static void onInvalidationReceived() {
|
| - assert ThreadUtils.runningOnUiThread();
|
| - if (sChildAccountService == null) return;
|
| - sChildAccountService.recheckChildAccountStatus();
|
| - }
|
| + private static native boolean nativeIsChildAccount();
|
|
|
| /**
|
| * If this returns false, Chrome will assume there are no child accounts on the device,
|
| * and no further checks will be made, which has the effect of a kill switch.
|
| - * Can be overridden by subclasses to avoid native calls in testing.
|
| - *
|
| - * @return Whether child account detection is enabled.
|
| */
|
| - protected native boolean nativeIsChildAccountDetectionEnabled();
|
| -
|
| - /**
|
| - * Returns the previously determined value of whether there is a child account on the device.
|
| - * Can be overridden by subclasses to avoid native calls in testing.
|
| - *
|
| - * @return The previously determined value of whether there is a child account on the device.
|
| - */
|
| - protected native boolean nativeGetIsChildAccount();
|
| -
|
| - private native void nativeSetIsChildAccount(boolean isChild);
|
| + private static native boolean nativeIsChildAccountDetectionEnabled();
|
| }
|
|
|