Commit 4b5f497d authored by bauerb's avatar bauerb Committed by Commit bot

Add support for child accounts on Android.

BUG=440405

Review URL: https://codereview.chromium.org/789853002

Cr-Commit-Position: refs/heads/master@{#308117}
parent 80483075
......@@ -12,6 +12,9 @@ public abstract class ChromeSwitches {
// Switches used from Java. Please continue switch style used Chrome where
// options-have-hypens and are_not_split_with_underscores.
/** Testing: pretend that the switch value is the name of a child account. */
public static final String CHILD_ACCOUNT = "child-account";
// Disables the new Website Settings dialog, which replaces the old one.
// TODO(sashab): Once the new WebsiteSettingsPopup is ready to be permanent,
// remove this flag and delete WebsiteSettingsLegacyPopup and all it's
......
// 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();
}
......@@ -56,6 +56,7 @@
#include "chrome/browser/search_engines/template_url_service_android.h"
#include "chrome/browser/signin/android_profile_oauth2_token_service.h"
#include "chrome/browser/speech/tts_android.h"
#include "chrome/browser/supervised_user/child_accounts/child_account_service_android.h"
#include "chrome/browser/sync/profile_sync_service_android.h"
#include "chrome/browser/ui/android/autofill/autofill_dialog_controller_android.h"
#include "chrome/browser/ui/android/autofill/autofill_dialog_result.h"
......@@ -128,6 +129,7 @@ static base::android::RegistrationMethod kChromeRegisteredMethods[] = {
{"BookmarksBridge", BookmarksBridge::RegisterBookmarksBridge},
{"CardUnmaskPrompt", autofill::CardUnmaskPromptViewAndroid::Register},
{"CertificateViewer", RegisterCertificateViewer},
{"ChildAccountService", RegisterChildAccountService},
{"ChromeBrowserProvider",
ChromeBrowserProvider::RegisterChromeBrowserProvider},
{"ChromeHttpAuthHandler",
......
......@@ -1018,7 +1018,7 @@ void ProfileManager::DoFinalInitForServices(Profile* profile,
}
#endif
#if defined(ENABLE_SUPERVISED_USERS) && !defined(OS_ANDROID)
#if defined(ENABLE_SUPERVISED_USERS)
// Initialization needs to happen after extension system initialization (for
// extension::ManagementPolicy) and InitProfileUserPrefs (for setting the
// initializing the supervised flag if necessary).
......
......@@ -42,6 +42,19 @@ ChildAccountService::ChildAccountService(Profile* profile)
ChildAccountService::~ChildAccountService() {}
void ChildAccountService::SetIsChildAccount(bool is_child_account) {
if (profile_->IsChild() == is_child_account)
return;
if (is_child_account) {
profile_->GetPrefs()->SetString(prefs::kSupervisedUserId,
supervised_users::kChildAccountSUID);
} else {
profile_->GetPrefs()->ClearPref(prefs::kSupervisedUserId);
}
PropagateChildStatusToUser(is_child_account);
}
void ChildAccountService::Init() {
SigninManagerFactory::GetForProfile(profile_)->AddObserver(this);
SupervisedUserServiceFactory::GetForProfile(profile_)->SetDelegate(this);
......@@ -236,19 +249,6 @@ void ChildAccountService::OnFlagsFetched(
SetIsChildAccount(is_child_account);
}
void ChildAccountService::SetIsChildAccount(bool is_child_account) {
if (profile_->IsChild() == is_child_account)
return;
if (is_child_account) {
profile_->GetPrefs()->SetString(prefs::kSupervisedUserId,
supervised_users::kChildAccountSUID);
} else {
profile_->GetPrefs()->ClearPref(prefs::kSupervisedUserId);
}
PropagateChildStatusToUser(is_child_account);
}
void ChildAccountService::PropagateChildStatusToUser(bool is_child) {
#if defined(OS_CHROMEOS)
user_manager::User* user =
......
......@@ -35,6 +35,11 @@ class ChildAccountService : public KeyedService,
void Init();
// Sets whether the signed-in account is a child account.
// Public so it can be called on platforms where child account detection
// happens outside of this class (like Android).
void SetIsChildAccount(bool is_child_account);
// KeyedService:
virtual void Shutdown() override;
......@@ -67,8 +72,6 @@ class ChildAccountService : public KeyedService,
void OnFlagsFetched(AccountServiceFlagFetcher::ResultCode,
const std::vector<std::string>& flags);
void SetIsChildAccount(bool is_child_account);
void PropagateChildStatusToUser(bool is_child);
void SetFirstCustodianPrefs(const FamilyInfoFetcher::FamilyMember& custodian);
......
// 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.
#include "chrome/browser/supervised_user/child_accounts/child_account_service_android.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/supervised_user/child_accounts/child_account_service.h"
#include "chrome/browser/supervised_user/child_accounts/child_account_service_factory.h"
#include "jni/ChildAccountService_jni.h"
void OnChildAccountSigninComplete(JNIEnv* env, jobject obj) {
VLOG(1) << "OnChildAccountSigninComplete";
Profile* profile = ProfileManager::GetLastUsedProfile();
ChildAccountServiceFactory::GetForProfile(profile)->SetIsChildAccount(true);
}
bool RegisterChildAccountService(JNIEnv* env) {
return RegisterNativesImpl(env);
}
// 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.
#ifndef CHROME_BROWSER_SUPERVISED_USER_CHILD_ACCOUNTS_CHILD_ACCOUNT_SERVICE_ANDROID_H_
#define CHROME_BROWSER_SUPERVISED_USER_CHILD_ACCOUNTS_CHILD_ACCOUNT_SERVICE_ANDROID_H_
#include <jni.h>
// Register native methods
bool RegisterChildAccountService(JNIEnv* env);
#endif // CHROME_BROWSER_SUPERVISED_USER_CHILD_ACCOUNTS_CHILD_ACCOUNT_SERVICE_ANDROID_H_
......@@ -1575,6 +1575,7 @@
'android/java/src/org/chromium/chrome/browser/BookmarksBridge.java',
'android/java/src/org/chromium/chrome/browser/banners/AppBannerManager.java',
'android/java/src/org/chromium/chrome/browser/CertificateViewer.java',
'android/java/src/org/chromium/chrome/browser/child_accounts/ChildAccountService.java',
'android/java/src/org/chromium/chrome/browser/ChromiumApplication.java',
'android/java/src/org/chromium/chrome/browser/ChromeBrowserProvider.java',
'android/java/src/org/chromium/chrome/browser/ChromeHttpAuthHandler.java',
......@@ -2577,6 +2578,8 @@
'chrome_browser_supervised_user_sources': [
'browser/supervised_user/child_accounts/child_account_service.cc',
'browser/supervised_user/child_accounts/child_account_service.h',
'browser/supervised_user/child_accounts/child_account_service_android.cc',
'browser/supervised_user/child_accounts/child_account_service_android.h',
'browser/supervised_user/child_accounts/child_account_service_factory.cc',
'browser/supervised_user/child_accounts/child_account_service_factory.h',
'browser/supervised_user/child_accounts/family_info_fetcher.cc',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment