blob: 2be4bdc26e5af6b124e6df5010c8ba1920421e2a [file] [log] [blame]
// Copyright 2016 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/chromeos/arc/arc_session_manager.h"
#include <utility>
#include "ash/common/shelf/shelf_delegate.h"
#include "ash/common/wm_shell.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/strings/string16.h"
#include "base/time/time.h"
#include "chrome/browser/chromeos/arc/arc_auth_code_fetcher.h"
#include "chrome/browser/chromeos/arc/arc_auth_context.h"
#include "chrome/browser/chromeos/arc/arc_optin_uma.h"
#include "chrome/browser/chromeos/arc/arc_support_host.h"
#include "chrome/browser/chromeos/arc/auth/arc_robot_auth.h"
#include "chrome/browser/chromeos/arc/optin/arc_terms_of_service_negotiator.h"
#include "chrome/browser/chromeos/arc/policy/arc_android_management_checker.h"
#include "chrome/browser/chromeos/arc/policy/arc_policy_util.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/policy/profile_policy_connector_factory.h"
#include "chrome/browser/prefs/pref_service_syncable_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/app_list/arc/arc_app_launcher.h"
#include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
#include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "chromeos/chromeos_switches.h"
#include "chromeos/cryptohome/cryptohome_parameters.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/session_manager_client.h"
#include "components/arc/arc_bridge_service.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/sync_preferences/pref_service_syncable.h"
#include "components/user_manager/user.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/extension_prefs.h"
namespace arc {
namespace {
// Weak pointer. This class is owned by ArcServiceManager.
ArcSessionManager* g_arc_session_manager = nullptr;
// Skip creating UI in unit tests
bool g_disable_ui_for_testing = false;
// Use specified ash::ShelfDelegate for unit tests.
ash::ShelfDelegate* g_shelf_delegate_for_testing = nullptr;
// The Android management check is disabled by default, it's used only for
// testing.
bool g_enable_check_android_management_for_testing = false;
// Maximum amount of time we'll wait for ARC to finish booting up. Once this
// timeout expires, keep ARC running in case the user wants to file feedback,
// but present the UI to try again.
constexpr base::TimeDelta kArcSignInTimeout = base::TimeDelta::FromMinutes(5);
ash::ShelfDelegate* GetShelfDelegate() {
if (g_shelf_delegate_for_testing)
return g_shelf_delegate_for_testing;
if (ash::WmShell::HasInstance()) {
DCHECK(ash::WmShell::Get()->shelf_delegate());
return ash::WmShell::Get()->shelf_delegate();
}
return nullptr;
}
} // namespace
ArcSessionManager::ArcSessionManager(ArcBridgeService* bridge_service)
: ArcService(bridge_service), weak_ptr_factory_(this) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!g_arc_session_manager);
g_arc_session_manager = this;
arc_bridge_service()->AddObserver(this);
}
ArcSessionManager::~ArcSessionManager() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
Shutdown();
arc_bridge_service()->RemoveObserver(this);
DCHECK_EQ(this, g_arc_session_manager);
g_arc_session_manager = nullptr;
}
// static
ArcSessionManager* ArcSessionManager::Get() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
return g_arc_session_manager;
}
// static
void ArcSessionManager::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
// TODO(dspaid): Implement a mechanism to allow this to sync on first boot
// only.
registry->RegisterBooleanPref(prefs::kArcEnabled, false);
registry->RegisterBooleanPref(prefs::kArcSignedIn, false);
registry->RegisterBooleanPref(prefs::kArcTermsAccepted, false);
registry->RegisterBooleanPref(prefs::kArcBackupRestoreEnabled, true);
registry->RegisterBooleanPref(prefs::kArcLocationServiceEnabled, true);
}
// static
void ArcSessionManager::DisableUIForTesting() {
g_disable_ui_for_testing = true;
}
// static
void ArcSessionManager::SetShelfDelegateForTesting(
ash::ShelfDelegate* shelf_delegate) {
g_shelf_delegate_for_testing = shelf_delegate;
}
// static
bool ArcSessionManager::IsOptInVerificationDisabled() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
chromeos::switches::kDisableArcOptInVerification);
}
// static
void ArcSessionManager::EnableCheckAndroidManagementForTesting() {
g_enable_check_android_management_for_testing = true;
}
// static
bool ArcSessionManager::IsAllowedForProfile(const Profile* profile) {
if (!ArcBridgeService::GetEnabled(base::CommandLine::ForCurrentProcess())) {
VLOG(1) << "Arc is not enabled.";
return false;
}
if (!profile) {
VLOG(1) << "ARC is not supported for systems without profile.";
return false;
}
if (!chromeos::ProfileHelper::IsPrimaryProfile(profile)) {
VLOG(1) << "Non-primary users are not supported in ARC.";
return false;
}
if (profile->IsLegacySupervised()) {
VLOG(1) << "Supervised users are not supported in ARC.";
return false;
}
user_manager::User const* const user =
chromeos::ProfileHelper::Get()->GetUserByProfile(profile);
if ((!user || !user->HasGaiaAccount()) && !IsArcKioskMode()) {
VLOG(1) << "Users without GAIA accounts are not supported in ARC.";
return false;
}
if (user_manager::UserManager::Get()
->IsCurrentUserCryptohomeDataEphemeral()) {
VLOG(2) << "Users with ephemeral data are not supported in Arc.";
return false;
}
return true;
}
// static
bool ArcSessionManager::IsArcKioskMode() {
return user_manager::UserManager::Get()->IsLoggedInAsArcKioskApp();
}
void ArcSessionManager::OnBridgeStopped(ArcBridgeService::StopReason reason) {
// TODO(crbug.com/625923): Use |reason| to report more detailed errors.
if (arc_sign_in_timer_.IsRunning()) {
OnProvisioningFinished(ProvisioningResult::ARC_STOPPED);
}
if (clear_required_) {
// This should be always true, but just in case as this is looked at
// inside RemoveArcData() at first.
DCHECK(arc_bridge_service()->stopped());
RemoveArcData();
} else {
// To support special "Stop and enable ARC" procedure for enterprise,
// here call OnArcDataRemoved(true) as if the data removal is successfully
// done.
// TODO(hidehiko): Restructure the code. crbug.com/665316
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::Bind(&ArcSessionManager::OnArcDataRemoved,
weak_ptr_factory_.GetWeakPtr(), true));
}
}
void ArcSessionManager::RemoveArcData() {
if (!arc_bridge_service()->stopped()) {
// Just set a flag. On bridge stopped, this will be re-called,
// then session manager should remove the data.
clear_required_ = true;
return;
}
clear_required_ = false;
chromeos::DBusThreadManager::Get()->GetSessionManagerClient()->RemoveArcData(
cryptohome::Identification(
multi_user_util::GetAccountIdFromProfile(profile_)),
base::Bind(&ArcSessionManager::OnArcDataRemoved,
weak_ptr_factory_.GetWeakPtr()));
}
void ArcSessionManager::OnArcDataRemoved(bool success) {
LOG_IF(ERROR, !success) << "Required ARC user data wipe failed.";
// Here check if |reenable_arc_| is marked or not.
// The only case this happens should be in the special case for enterprise
// "on managed lost" case. In that case, OnBridgeStopped() should trigger
// the RemoveArcData(), then this.
// TODO(hidehiko): Restructure the code.
if (!reenable_arc_)
return;
// Restart ARC anyway. Let the enterprise reporting instance decide whether
// the ARC user data wipe is still required or not.
reenable_arc_ = false;
VLOG(1) << "Reenable ARC";
EnableArc();
}
void ArcSessionManager::OnProvisioningFinished(ProvisioningResult result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_EQ(state_, State::ACTIVE);
if (result == ProvisioningResult::CHROME_SERVER_COMMUNICATION_ERROR) {
// For backwards compatibility, use NETWORK_ERROR for
// CHROME_SERVER_COMMUNICATION_ERROR case.
UpdateOptInCancelUMA(OptInCancelReason::NETWORK_ERROR);
} else if (!sign_in_time_.is_null()) {
arc_sign_in_timer_.Stop();
UpdateProvisioningTiming(base::Time::Now() - sign_in_time_,
result == ProvisioningResult::SUCCESS,
policy_util::IsAccountManaged(profile_));
UpdateProvisioningResultUMA(result,
policy_util::IsAccountManaged(profile_));
if (result != ProvisioningResult::SUCCESS)
UpdateOptInCancelUMA(OptInCancelReason::CLOUD_PROVISION_FLOW_FAIL);
}
if (result == ProvisioningResult::SUCCESS) {
if (support_host_)
support_host_->Close();
if (profile_->GetPrefs()->GetBoolean(prefs::kArcSignedIn))
return;
profile_->GetPrefs()->SetBoolean(prefs::kArcSignedIn, true);
// Don't show Play Store app for ARC Kiosk because the only one UI in kiosk
// mode must be the kiosk app and device is not needed for opt-in.
if (!IsOptInVerificationDisabled() && !IsArcKioskMode()) {
playstore_launcher_.reset(
new ArcAppLauncher(profile_, kPlayStoreAppId, true));
}
for (auto& observer : observer_list_)
observer.OnInitialStart();
return;
}
ArcSupportHost::Error error;
switch (result) {
case ProvisioningResult::GMS_NETWORK_ERROR:
error = ArcSupportHost::Error::SIGN_IN_NETWORK_ERROR;
break;
case ProvisioningResult::GMS_SERVICE_UNAVAILABLE:
case ProvisioningResult::GMS_SIGN_IN_FAILED:
case ProvisioningResult::GMS_SIGN_IN_TIMEOUT:
case ProvisioningResult::GMS_SIGN_IN_INTERNAL_ERROR:
error = ArcSupportHost::Error::SIGN_IN_SERVICE_UNAVAILABLE_ERROR;
break;
case ProvisioningResult::GMS_BAD_AUTHENTICATION:
error = ArcSupportHost::Error::SIGN_IN_BAD_AUTHENTICATION_ERROR;
break;
case ProvisioningResult::DEVICE_CHECK_IN_FAILED:
case ProvisioningResult::DEVICE_CHECK_IN_TIMEOUT:
case ProvisioningResult::DEVICE_CHECK_IN_INTERNAL_ERROR:
error = ArcSupportHost::Error::SIGN_IN_GMS_NOT_AVAILABLE_ERROR;
break;
case ProvisioningResult::CLOUD_PROVISION_FLOW_FAILED:
case ProvisioningResult::CLOUD_PROVISION_FLOW_TIMEOUT:
case ProvisioningResult::CLOUD_PROVISION_FLOW_INTERNAL_ERROR:
error = ArcSupportHost::Error::SIGN_IN_CLOUD_PROVISION_FLOW_FAIL_ERROR;
break;
case ProvisioningResult::CHROME_SERVER_COMMUNICATION_ERROR:
error = ArcSupportHost::Error::SERVER_COMMUNICATION_ERROR;
break;
default:
error = ArcSupportHost::Error::SIGN_IN_UNKNOWN_ERROR;
break;
}
if (result == ProvisioningResult::ARC_STOPPED ||
result == ProvisioningResult::CHROME_SERVER_COMMUNICATION_ERROR) {
if (profile_->GetPrefs()->HasPrefPath(prefs::kArcSignedIn))
profile_->GetPrefs()->SetBoolean(prefs::kArcSignedIn, false);
ShutdownBridge();
if (support_host_)
support_host_->ShowError(error, false);
return;
}
if (result == ProvisioningResult::CLOUD_PROVISION_FLOW_FAILED ||
result == ProvisioningResult::CLOUD_PROVISION_FLOW_TIMEOUT ||
result == ProvisioningResult::CLOUD_PROVISION_FLOW_INTERNAL_ERROR ||
// OVERALL_SIGN_IN_TIMEOUT might be an indication that ARC believes it is
// fully setup, but Chrome does not.
result == ProvisioningResult::OVERALL_SIGN_IN_TIMEOUT ||
// Just to be safe, remove data if we don't know the cause.
result == ProvisioningResult::UNKNOWN_ERROR) {
RemoveArcData();
}
// We'll delay shutting down the bridge in this case to allow people to send
// feedback.
if (support_host_)
support_host_->ShowError(error, true /* = show send feedback button */);
}
void ArcSessionManager::SetState(State state) {
state_ = state;
}
bool ArcSessionManager::IsAllowed() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
return profile_ != nullptr;
}
void ArcSessionManager::OnPrimaryUserProfilePrepared(Profile* profile) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(profile && profile != profile_);
Shutdown();
if (!IsAllowedForProfile(profile))
return;
// TODO(khmel): Move this to IsAllowedForProfile.
if (policy_util::IsArcDisabledForEnterprise() &&
policy_util::IsAccountManaged(profile)) {
VLOG(2) << "Enterprise users are not supported in ARC.";
return;
}
profile_ = profile;
// Create the support host at initialization. Note that, practically,
// ARC support Chrome app is rarely used (only opt-in and re-auth flow).
// So, it may be better to initialize it lazily.
// TODO(hidehiko): Revisit to think about lazy initialization.
//
// Don't show UI for ARC Kiosk because the only one UI in kiosk mode must
// be the kiosk app. In case of error the UI will be useless as well, because
// in typical use case there will be no one nearby the kiosk device, who can
// do some action to solve the problem be means of UI.
if (!g_disable_ui_for_testing && !IsOptInVerificationDisabled() &&
!IsArcKioskMode()) {
DCHECK(!support_host_);
support_host_ = base::MakeUnique<ArcSupportHost>(profile_);
support_host_->AddObserver(this);
}
DCHECK_EQ(State::NOT_INITIALIZED, state_);
SetState(State::STOPPED);
PrefServiceSyncableFromProfile(profile_)->AddSyncedPrefObserver(
prefs::kArcEnabled, this);
context_.reset(new ArcAuthContext(profile_));
if (!g_disable_ui_for_testing ||
g_enable_check_android_management_for_testing) {
ArcAndroidManagementChecker::StartClient();
}
pref_change_registrar_.Init(profile_->GetPrefs());
pref_change_registrar_.Add(
prefs::kArcEnabled,
base::Bind(&ArcSessionManager::OnOptInPreferenceChanged,
weak_ptr_factory_.GetWeakPtr()));
if (profile_->GetPrefs()->GetBoolean(prefs::kArcEnabled)) {
OnOptInPreferenceChanged();
} else {
RemoveArcData();
PrefServiceSyncableFromProfile(profile_)->AddObserver(this);
OnIsSyncingChanged();
}
}
void ArcSessionManager::OnIsSyncingChanged() {
sync_preferences::PrefServiceSyncable* const pref_service_syncable =
PrefServiceSyncableFromProfile(profile_);
if (!pref_service_syncable->IsSyncing())
return;
pref_service_syncable->RemoveObserver(this);
if (IsArcEnabled())
OnOptInPreferenceChanged();
}
void ArcSessionManager::Shutdown() {
ShutdownBridge();
if (support_host_) {
support_host_->Close();
support_host_->RemoveObserver(this);
support_host_.reset();
}
if (profile_) {
sync_preferences::PrefServiceSyncable* pref_service_syncable =
PrefServiceSyncableFromProfile(profile_);
pref_service_syncable->RemoveObserver(this);
pref_service_syncable->RemoveSyncedPrefObserver(prefs::kArcEnabled, this);
}
pref_change_registrar_.RemoveAll();
context_.reset();
profile_ = nullptr;
SetState(State::NOT_INITIALIZED);
}
void ArcSessionManager::OnSyncedPrefChanged(const std::string& path,
bool from_sync) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Update UMA only for local changes
if (!from_sync) {
const bool arc_enabled =
profile_->GetPrefs()->GetBoolean(prefs::kArcEnabled);
UpdateOptInActionUMA(arc_enabled ? OptInActionType::OPTED_IN
: OptInActionType::OPTED_OUT);
if (!arc_enabled && !IsArcManaged()) {
ash::ShelfDelegate* shelf_delegate = GetShelfDelegate();
if (shelf_delegate)
shelf_delegate->UnpinAppWithID(ArcSupportHost::kHostAppId);
}
}
}
void ArcSessionManager::StopArc() {
if (state_ != State::STOPPED) {
profile_->GetPrefs()->SetBoolean(prefs::kArcSignedIn, false);
profile_->GetPrefs()->SetBoolean(prefs::kArcTermsAccepted, false);
}
ShutdownBridge();
if (support_host_)
support_host_->Close();
}
void ArcSessionManager::OnOptInPreferenceChanged() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(profile_);
// TODO(dspaid): Move code from OnSyncedPrefChanged into this method.
OnSyncedPrefChanged(prefs::kArcEnabled, IsArcManaged());
const bool arc_enabled = IsArcEnabled();
for (auto& observer : observer_list_)
observer.OnOptInEnabled(arc_enabled);
if (!arc_enabled) {
StopArc();
RemoveArcData();
return;
}
if (state_ == State::ACTIVE)
return;
if (support_host_)
support_host_->SetArcManaged(IsArcManaged());
// For ARC Kiosk we skip ToS because it is very likely that near the device
// there will be no one who is eligible to accept them.
// TODO(poromov): Move to more Kiosk dedicated set-up phase.
if (IsArcKioskMode())
profile_->GetPrefs()->SetBoolean(prefs::kArcTermsAccepted, true);
// If it is marked that sign in has been successfully done, then directly
// start ARC.
// For testing, and for Kisok mode, we also skip ToS negotiation procedure.
// For backward compatibility, this check needs to be prior to the
// kArcTermsAccepted check below.
if (profile_->GetPrefs()->GetBoolean(prefs::kArcSignedIn) ||
IsOptInVerificationDisabled() || IsArcKioskMode()) {
StartArc();
// Skip Android management check for testing.
// We also skip if Android management check for Kiosk mode,
// because there are no managed human users for Kiosk exist.
if (IsOptInVerificationDisabled() || IsArcKioskMode() ||
(g_disable_ui_for_testing &&
!g_enable_check_android_management_for_testing)) {
return;
}
// Check Android management in parallel.
// Note: Because the callback may be called in synchronous way (i.e. called
// on the same stack), StartCheck() needs to be called *after* StartArc().
// Otherwise, DisableArc() which may be called in
// OnBackgroundAndroidManagementChecked() could be ignored.
android_management_checker_ = base::MakeUnique<ArcAndroidManagementChecker>(
profile_, context_->token_service(), context_->account_id(),
true /* retry_on_error */);
android_management_checker_->StartCheck(
base::Bind(&ArcSessionManager::OnBackgroundAndroidManagementChecked,
weak_ptr_factory_.GetWeakPtr()));
return;
}
// If it is marked that the Terms of service is accepted already,
// just skip the negotiation with user, and start Android management
// check directly.
// This happens, e.g., when;
// 1) User accepted the Terms of service on OOBE flow.
// 2) User accepted the Terms of service on Opt-in flow, but logged out
// before ARC sign in procedure was done. Then, logs in again.
if (profile_->GetPrefs()->GetBoolean(prefs::kArcTermsAccepted)) {
support_host_->ShowArcLoading();
StartArcAndroidManagementCheck();
return;
}
// Need user's explicit Terms Of Service agreement.
StartTermsOfServiceNegotiation();
}
void ArcSessionManager::ShutdownBridge() {
arc_sign_in_timer_.Stop();
playstore_launcher_.reset();
terms_of_service_negotiator_.reset();
android_management_checker_.reset();
arc_bridge_service()->RequestStop();
if (state_ != State::NOT_INITIALIZED)
SetState(State::STOPPED);
for (auto& observer : observer_list_)
observer.OnShutdownBridge();
}
void ArcSessionManager::AddObserver(Observer* observer) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
observer_list_.AddObserver(observer);
}
void ArcSessionManager::RemoveObserver(Observer* observer) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
observer_list_.RemoveObserver(observer);
}
// This is the special method to support enterprise mojo API.
// TODO(hidehiko): Remove this.
void ArcSessionManager::StopAndEnableArc() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!arc_bridge_service()->stopped());
reenable_arc_ = true;
StopArc();
}
void ArcSessionManager::StartArc() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
arc_bridge_service()->RequestStart();
SetState(State::ACTIVE);
}
void ArcSessionManager::OnArcSignInTimeout() {
LOG(ERROR) << "Timed out waiting for first sign in.";
OnProvisioningFinished(ProvisioningResult::OVERALL_SIGN_IN_TIMEOUT);
}
void ArcSessionManager::CancelAuthCode() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (state_ == State::NOT_INITIALIZED) {
NOTREACHED();
return;
}
// In case |state_| is ACTIVE, UI page can be ARC_LOADING (which means normal
// ARC booting) or ERROR (in case ARC can not be started). If ARC is booting
// normally don't stop it on progress close.
if ((state_ != State::SHOWING_TERMS_OF_SERVICE &&
state_ != State::CHECKING_ANDROID_MANAGEMENT) &&
(!support_host_ ||
support_host_->ui_page() != ArcSupportHost::UIPage::ERROR)) {
return;
}
// Update UMA with user cancel only if error is not currently shown.
if (support_host_ &&
support_host_->ui_page() != ArcSupportHost::UIPage::NO_PAGE &&
support_host_->ui_page() != ArcSupportHost::UIPage::ERROR) {
UpdateOptInCancelUMA(OptInCancelReason::USER_CANCEL);
}
StopArc();
if (IsArcManaged())
return;
DisableArc();
}
bool ArcSessionManager::IsArcManaged() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(profile_);
return profile_->GetPrefs()->IsManagedPreference(prefs::kArcEnabled);
}
bool ArcSessionManager::IsArcEnabled() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!IsAllowed())
return false;
DCHECK(profile_);
return profile_->GetPrefs()->GetBoolean(prefs::kArcEnabled);
}
void ArcSessionManager::EnableArc() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(profile_);
if (IsArcEnabled()) {
OnOptInPreferenceChanged();
return;
}
if (!IsArcManaged())
profile_->GetPrefs()->SetBoolean(prefs::kArcEnabled, true);
}
void ArcSessionManager::DisableArc() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(profile_);
profile_->GetPrefs()->SetBoolean(prefs::kArcEnabled, false);
}
void ArcSessionManager::RecordArcState() {
// Only record Enabled state if ARC is allowed in the first place, so we do
// not split the ARC population by devices that cannot run ARC.
if (IsAllowed())
UpdateEnabledStateUMA(IsArcEnabled());
}
void ArcSessionManager::StartTermsOfServiceNegotiation() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!terms_of_service_negotiator_);
if (!arc_bridge_service()->stopped()) {
// If the user attempts to re-enable ARC while the bridge is still running
// the user should not be able to continue until the bridge has stopped.
if (support_host_) {
support_host_->ShowError(
ArcSupportHost::Error::SIGN_IN_SERVICE_UNAVAILABLE_ERROR, false);
}
return;
}
SetState(State::SHOWING_TERMS_OF_SERVICE);
if (support_host_) {
terms_of_service_negotiator_ =
base::MakeUnique<ArcTermsOfServiceNegotiator>(profile_->GetPrefs(),
support_host_.get());
terms_of_service_negotiator_->StartNegotiation(
base::Bind(&ArcSessionManager::OnTermsOfServiceNegotiated,
weak_ptr_factory_.GetWeakPtr()));
}
}
void ArcSessionManager::OnTermsOfServiceNegotiated(bool accepted) {
DCHECK(terms_of_service_negotiator_);
terms_of_service_negotiator_.reset();
if (!accepted) {
// To cancel, user needs to close the window. Note that clicking "Cancel"
// button effectively just closes the window.
CancelAuthCode();
return;
}
// Terms were accepted.
profile_->GetPrefs()->SetBoolean(prefs::kArcTermsAccepted, true);
support_host_->ShowArcLoading();
StartArcAndroidManagementCheck();
}
void ArcSessionManager::StartArcAndroidManagementCheck() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(arc_bridge_service()->stopped());
DCHECK(state_ == State::SHOWING_TERMS_OF_SERVICE ||
state_ == State::CHECKING_ANDROID_MANAGEMENT);
SetState(State::CHECKING_ANDROID_MANAGEMENT);
android_management_checker_.reset(new ArcAndroidManagementChecker(
profile_, context_->token_service(), context_->account_id(),
false /* retry_on_error */));
android_management_checker_->StartCheck(
base::Bind(&ArcSessionManager::OnAndroidManagementChecked,
weak_ptr_factory_.GetWeakPtr()));
}
void ArcSessionManager::OnAndroidManagementChecked(
policy::AndroidManagementClient::Result result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_EQ(state_, State::CHECKING_ANDROID_MANAGEMENT);
switch (result) {
case policy::AndroidManagementClient::Result::UNMANAGED:
VLOG(1) << "Starting ARC for first sign in.";
sign_in_time_ = base::Time::Now();
arc_sign_in_timer_.Start(
FROM_HERE, kArcSignInTimeout,
base::Bind(&ArcSessionManager::OnArcSignInTimeout,
weak_ptr_factory_.GetWeakPtr()));
StartArc();
break;
case policy::AndroidManagementClient::Result::MANAGED:
ShutdownBridge();
if (support_host_) {
support_host_->ShowError(
ArcSupportHost::Error::ANDROID_MANAGEMENT_REQUIRED_ERROR, false);
}
UpdateOptInCancelUMA(OptInCancelReason::ANDROID_MANAGEMENT_REQUIRED);
break;
case policy::AndroidManagementClient::Result::ERROR:
ShutdownBridge();
if (support_host_) {
support_host_->ShowError(
ArcSupportHost::Error::SERVER_COMMUNICATION_ERROR, false);
}
UpdateOptInCancelUMA(OptInCancelReason::NETWORK_ERROR);
break;
}
}
void ArcSessionManager::OnBackgroundAndroidManagementChecked(
policy::AndroidManagementClient::Result result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
switch (result) {
case policy::AndroidManagementClient::Result::UNMANAGED:
// Do nothing. ARC should be started already.
break;
case policy::AndroidManagementClient::Result::MANAGED:
DisableArc();
break;
case policy::AndroidManagementClient::Result::ERROR:
// This code should not be reached. For background check,
// retry_on_error should be set.
NOTREACHED();
}
}
void ArcSessionManager::OnWindowClosed() {
DCHECK(support_host_);
if (terms_of_service_negotiator_) {
// In this case, ArcTermsOfServiceNegotiator should handle the case.
// Do nothing.
return;
}
CancelAuthCode();
}
void ArcSessionManager::OnTermsAgreed(bool is_metrics_enabled,
bool is_backup_and_restore_enabled,
bool is_location_service_enabled) {
DCHECK(support_host_);
DCHECK(terms_of_service_negotiator_);
// This should be handled in ArcTermsOfServiceNegotiator. Do nothing here.
}
void ArcSessionManager::OnRetryClicked() {
DCHECK(support_host_);
UpdateOptInActionUMA(OptInActionType::RETRY);
// TODO(hidehiko): Simplify the retry logic.
if (terms_of_service_negotiator_) {
// Currently Terms of service is shown. ArcTermsOfServiceNegotiator should
// handle this.
} else if (!profile_->GetPrefs()->GetBoolean(prefs::kArcTermsAccepted)) {
StartTermsOfServiceNegotiation();
} else if (support_host_->ui_page() == ArcSupportHost::UIPage::ERROR &&
!arc_bridge_service()->stopped()) {
// ERROR_WITH_FEEDBACK is set in OnSignInFailed(). In the case, stopping
// ARC was postponed to contain its internal state into the report.
// Here, on retry, stop it, then restart.
DCHECK_EQ(State::ACTIVE, state_);
support_host_->ShowArcLoading();
ShutdownBridge();
reenable_arc_ = true;
} else if (state_ == State::ACTIVE) {
// This case is handled in ArcAuthService.
// Do nothing.
} else {
// Otherwise, we restart ARC. Note: this is the first boot case.
// For second or later boot, either ERROR_WITH_FEEDBACK case or ACTIVE
// case must hit.
support_host_->ShowArcLoading();
StartArcAndroidManagementCheck();
}
}
void ArcSessionManager::OnSendFeedbackClicked() {
DCHECK(support_host_);
chrome::OpenFeedbackDialog(nullptr);
}
std::ostream& operator<<(std::ostream& os,
const ArcSessionManager::State& state) {
switch (state) {
case ArcSessionManager::State::NOT_INITIALIZED:
return os << "NOT_INITIALIZED";
case ArcSessionManager::State::STOPPED:
return os << "STOPPED";
case ArcSessionManager::State::SHOWING_TERMS_OF_SERVICE:
return os << "SHOWING_TERMS_OF_SERVICE";
case ArcSessionManager::State::CHECKING_ANDROID_MANAGEMENT:
return os << "CHECKING_ANDROID_MANAGEMENT";
case ArcSessionManager::State::ACTIVE:
return os << "ACTIVE";
}
// Some compiler reports an error even if all values of an enum-class are
// covered indivisually in a switch statement.
NOTREACHED();
return os;
}
} // namespace arc