Index: chrome/android/java/src/org/chromium/chrome/browser/child_accounts/ChildAccountService.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/child_accounts/ChildAccountService.java b/chrome/android/java/src/org/chromium/chrome/browser/child_accounts/ChildAccountService.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..15066cc7da8e9c86c9a72aaad4928558d760aca9 |
--- /dev/null |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/child_accounts/ChildAccountService.java |
@@ -0,0 +1,214 @@ |
+// Copyright 2014 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.child_accounts; |
+ |
+import android.accounts.Account; |
+import android.accounts.AccountManager; |
+import android.accounts.AccountManagerCallback; |
+import android.accounts.AccountManagerFuture; |
+import android.accounts.AuthenticatorException; |
+import android.accounts.OperationCanceledException; |
+import android.content.Context; |
+import android.os.AsyncTask; |
+import android.util.Log; |
+ |
+import org.chromium.base.CommandLine; |
+import org.chromium.chrome.ChromeSwitches; |
+import org.chromium.sync.signin.AccountManagerHelper; |
+ |
+import java.io.IOException; |
+import java.util.ArrayList; |
+import java.util.List; |
+import java.util.Timer; |
+import java.util.TimerTask; |
+import java.util.concurrent.ExecutionException; |
+ |
+/** |
+ * This class detects child accounts and enables special treatment for them. |
+ */ |
+public class ChildAccountService { |
+ |
+ private static final String TAG = "ChildAccountService"; |
+ |
+ /** |
+ * An account feature (corresponding to a Gaia service flag) that specifies whether the account |
+ * is a child account. |
+ */ |
+ private static final String FEATURE_IS_CHILD_ACCOUNT_KEY = "service_uca"; |
+ |
+ /** |
+ * The maximum amount of time to wait for the child account check, in milliseconds. |
+ */ |
+ private static final int CHILD_ACCOUNT_TIMEOUT_MS = 1000; |
+ |
+ private static final Object sLock = new Object(); |
+ |
+ private static ChildAccountService sChildAccountService; |
+ |
+ private final Context mContext; |
+ |
+ // This is null if we haven't determined the child account status yet. |
+ private Boolean mHasChildAccount; |
+ |
+ private AccountManagerFuture<Boolean> mAccountManagerFuture; |
+ |
+ private final List<HasChildAccountCallback> mCallbacks = new ArrayList<>(); |
+ |
+ private ChildAccountService(Context context) { |
+ mContext = context; |
+ } |
+ |
+ /** |
+ * 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) { |
+ synchronized (sLock) { |
+ if (sChildAccountService == null) { |
+ sChildAccountService = new ChildAccountService(context.getApplicationContext()); |
+ } |
+ } |
+ return sChildAccountService; |
+ } |
+ |
+ /** |
+ * A callback to return the result of {@link #checkHasChildAccount}. |
+ */ |
+ public static interface HasChildAccountCallback { |
+ |
+ /** |
+ * @param hasChildAccount Whether there is exactly one child account on the device. |
+ */ |
+ public void onChildAccountChecked(boolean hasChildAccount); |
+ |
+ } |
+ |
+ /** |
+ * Checks for the presence of child accounts on the device. |
+ * @param callback Will be called with the result (see |
+ * {@link HasChildAccountCallback#onChildAccountChecked}). |
+ */ |
+ public void checkHasChildAccount(final HasChildAccountCallback callback) { |
+ if (mHasChildAccount != null) { |
+ callback.onChildAccountChecked(mHasChildAccount); |
+ return; |
+ } |
+ |
+ Account[] googleAccounts = |
+ AccountManagerHelper.get(mContext).getGoogleAccounts(); |
+ if (googleAccounts.length != 1) { |
+ mHasChildAccount = false; |
+ callback.onChildAccountChecked(false); |
+ 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"); |
+ } |
+ return; |
+ } |
+ Account account = googleAccounts[0]; |
+ |
+ if (shouldForceChildAccount(account)) { |
+ mHasChildAccount = true; |
+ callback.onChildAccountChecked(true); |
+ return; |
+ } |
+ |
+ mCallbacks.add(callback); |
+ |
+ if (mAccountManagerFuture != null) return; |
+ |
+ final Timer timer = new Timer(); |
+ |
+ String[] features = {FEATURE_IS_CHILD_ACCOUNT_KEY}; |
+ mAccountManagerFuture = AccountManager.get(mContext).hasFeatures(account, features, |
+ new AccountManagerCallback<Boolean>() { |
+ @Override |
+ public void run(AccountManagerFuture<Boolean> future) { |
+ assert future == mAccountManagerFuture; |
+ assert future.isDone(); |
+ |
+ timer.cancel(); |
+ |
+ boolean hasChildAccount = hasChildAccount(); |
+ for (HasChildAccountCallback callback : mCallbacks) { |
+ callback.onChildAccountChecked(hasChildAccount); |
+ } |
+ } |
+ }, null /* handler */); |
+ |
+ timer.schedule(new TimerTask() { |
+ @Override |
+ public void run() { |
+ if (!mAccountManagerFuture.isDone()) mAccountManagerFuture.cancel(true); |
+ }}, CHILD_ACCOUNT_TIMEOUT_MS); |
+ } |
+ |
+ private boolean shouldForceChildAccount(Account account) { |
+ String childAccountName = CommandLine.getInstance().getSwitchValue( |
+ ChromeSwitches.CHILD_ACCOUNT); |
+ return childAccountName != null && account.name.equals(childAccountName); |
+ } |
+ |
+ private boolean getFutureResult() { |
+ try { |
+ return mAccountManagerFuture.getResult(); |
+ } catch (OperationCanceledException e) { |
+ Log.e(TAG, "Timed out fetching child account flag: ", e); |
+ } catch (AuthenticatorException e) { |
+ Log.e(TAG, "Error while fetching child account flag: ", e); |
+ } catch (IOException e) { |
+ Log.e(TAG, "Error while fetching child account flag: ", e); |
+ } |
+ return false; |
+ } |
+ |
+ /** |
+ * Synchronously checks for the presence of child accounts on the device. This method should |
+ * only be called after the result has been determined (usually using |
+ * {@link #checkHasChildAccount} or {@link #waitUntilFinished} to block). |
+ * @return Whether there is a child account on the device. |
+ */ |
+ public boolean hasChildAccount() { |
+ // Lazily get the result from the future, so this can be called both from the |
+ // AccountManagerCallback and after waiting for the future to be resolved. |
+ if (mHasChildAccount == null) { |
+ // If the future is not resolved yet, this will assert. |
+ mHasChildAccount = getFutureResult(); |
+ } |
+ return mHasChildAccount; |
+ } |
+ |
+ /** |
+ * Waits until we have determined the child account status. Usually you should use callbacks |
+ * instead of this method, see {@link #checkHasChildAccount}. |
+ */ |
+ public void waitUntilFinished() { |
+ if (mAccountManagerFuture == null) return; |
+ if (mAccountManagerFuture.isDone()) return; |
+ |
+ // This will block in getFutureResult(), but that may only happen on a background thread. |
+ try { |
+ new AsyncTask<Void, Void, Void>() { |
+ @Override |
+ protected Void doInBackground(Void... params) { |
+ getFutureResult(); |
+ return null; |
+ } |
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR).get(); |
+ } catch (ExecutionException e) { |
+ Log.w(TAG, "Error while fetching child account flag: ", e); |
+ } catch (InterruptedException e) { |
+ Log.w(TAG, "Interrupted while fetching child account flag: ", e); |
+ } |
+ } |
+ |
+ public void onChildAccountSigninComplete() { |
+ nativeOnChildAccountSigninComplete(); |
+ } |
+ |
+ private native void nativeOnChildAccountSigninComplete(); |
+} |