| // Copyright 2015 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_auth_service.h" |
| |
| #include <utility> |
| |
| #include "ash/common/shelf/shelf_delegate.h" |
| #include "ash/common/wm_shell.h" |
| #include "base/auto_reset.h" |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/logging.h" |
| #include "base/strings/string16.h" |
| #include "chrome/browser/chromeos/arc/arc_android_management_checker.h" |
| #include "chrome/browser/chromeos/arc/arc_auth_context.h" |
| #include "chrome/browser/chromeos/arc/arc_auth_notification.h" |
| #include "chrome/browser/chromeos/arc/arc_optin_uma.h" |
| #include "chrome/browser/chromeos/arc/arc_support_host.h" |
| #include "chrome/browser/chromeos/profiles/profile_helper.h" |
| #include "chrome/browser/extensions/extension_util.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/extensions/app_launch_params.h" |
| #include "chrome/browser/ui/extensions/application_launch.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/policy/core/browser/browser_policy_connector.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/syncable_prefs/pref_service_syncable.h" |
| #include "components/user_manager/user.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "extensions/browser/app_window/app_window_registry.h" |
| #include "extensions/browser/extension_prefs.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| namespace arc { |
| |
| namespace { |
| |
| // Weak pointer. This class is owned by ArcServiceManager. |
| ArcAuthService* g_arc_auth_service = 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; |
| |
| const char kStateNotInitialized[] = "NOT_INITIALIZED"; |
| const char kStateStopped[] = "STOPPED"; |
| const char kStateFetchingCode[] = "FETCHING_CODE"; |
| const char kStateActive[] = "ACTIVE"; |
| |
| bool IsAccountManaged(Profile* profile) { |
| return policy::ProfilePolicyConnectorFactory::GetForBrowserContext(profile) |
| ->IsManaged(); |
| } |
| |
| bool IsArcDisabledForEnterprise() { |
| return base::CommandLine::ForCurrentProcess()->HasSwitch( |
| chromeos::switches::kEnterpriseDisableArc); |
| } |
| |
| 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; |
| } |
| |
| ProvisioningResult ConvertArcSignInFailureReasonToProvisioningResult( |
| arc::mojom::ArcSignInFailureReason reason) { |
| using ArcSignInFailureReason = arc::mojom::ArcSignInFailureReason; |
| |
| #define MAP_PROVISIONING_RESULT(name) \ |
| case ArcSignInFailureReason::name: \ |
| return ProvisioningResult::name |
| |
| switch (reason) { |
| MAP_PROVISIONING_RESULT(UNKNOWN_ERROR); |
| MAP_PROVISIONING_RESULT(MOJO_VERSION_MISMATCH); |
| MAP_PROVISIONING_RESULT(MOJO_CALL_TIMEOUT); |
| MAP_PROVISIONING_RESULT(DEVICE_CHECK_IN_FAILED); |
| MAP_PROVISIONING_RESULT(DEVICE_CHECK_IN_TIMEOUT); |
| MAP_PROVISIONING_RESULT(DEVICE_CHECK_IN_INTERNAL_ERROR); |
| MAP_PROVISIONING_RESULT(GMS_NETWORK_ERROR); |
| MAP_PROVISIONING_RESULT(GMS_SERVICE_UNAVAILABLE); |
| MAP_PROVISIONING_RESULT(GMS_BAD_AUTHENTICATION); |
| MAP_PROVISIONING_RESULT(GMS_SIGN_IN_FAILED); |
| MAP_PROVISIONING_RESULT(GMS_SIGN_IN_TIMEOUT); |
| MAP_PROVISIONING_RESULT(GMS_SIGN_IN_INTERNAL_ERROR); |
| MAP_PROVISIONING_RESULT(CLOUD_PROVISION_FLOW_FAILED); |
| MAP_PROVISIONING_RESULT(CLOUD_PROVISION_FLOW_TIMEOUT); |
| MAP_PROVISIONING_RESULT(CLOUD_PROVISION_FLOW_INTERNAL_ERROR); |
| } |
| #undef MAP_PROVISIONING_RESULT |
| |
| NOTREACHED() << "unknown reason: " << static_cast<int>(reason); |
| return ProvisioningResult::UNKNOWN_ERROR; |
| } |
| |
| } // namespace |
| |
| ArcAuthService::ArcAuthService(ArcBridgeService* bridge_service) |
| : ArcService(bridge_service), binding_(this), weak_ptr_factory_(this) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK(!g_arc_auth_service); |
| |
| g_arc_auth_service = this; |
| |
| arc_bridge_service()->AddObserver(this); |
| arc_bridge_service()->auth()->AddObserver(this); |
| } |
| |
| ArcAuthService::~ArcAuthService() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK_EQ(this, g_arc_auth_service); |
| |
| Shutdown(); |
| arc_bridge_service()->auth()->RemoveObserver(this); |
| arc_bridge_service()->RemoveObserver(this); |
| |
| g_arc_auth_service = nullptr; |
| } |
| |
| // static |
| ArcAuthService* ArcAuthService::Get() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| return g_arc_auth_service; |
| } |
| |
| // static |
| void ArcAuthService::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::kArcBackupRestoreEnabled, true); |
| registry->RegisterBooleanPref(prefs::kArcLocationServiceEnabled, true); |
| } |
| |
| // static |
| void ArcAuthService::DisableUIForTesting() { |
| g_disable_ui_for_testing = true; |
| } |
| |
| // static |
| void ArcAuthService::SetShelfDelegateForTesting( |
| ash::ShelfDelegate* shelf_delegate) { |
| g_shelf_delegate_for_testing = shelf_delegate; |
| } |
| |
| // static |
| bool ArcAuthService::IsOptInVerificationDisabled() { |
| return base::CommandLine::ForCurrentProcess()->HasSwitch( |
| chromeos::switches::kDisableArcOptInVerification); |
| } |
| |
| // static |
| void ArcAuthService::EnableCheckAndroidManagementForTesting() { |
| g_enable_check_android_management_for_testing = true; |
| } |
| |
| // static |
| bool ArcAuthService::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()) { |
| 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; |
| } |
| |
| void ArcAuthService::OnInstanceReady() { |
| arc_bridge_service()->auth()->instance()->Init( |
| binding_.CreateInterfacePtrAndBind()); |
| } |
| |
| void ArcAuthService::OnBridgeStopped(ArcBridgeService::StopReason reason) { |
| // TODO(crbug.com/625923): Use |reason| to report more detailed errors. |
| if (waiting_for_reply_) { |
| // Using SERVICE_UNAVAILABLE instead of UNKNOWN_ERROR, since the latter |
| // causes this code to not try to stop ARC, so it would retry without the |
| // user noticing. |
| OnSignInFailedInternal(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. |
| OnArcDataRemoved(true); |
| } |
| } |
| |
| void ArcAuthService::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(&ArcAuthService::OnArcDataRemoved, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ArcAuthService::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(); |
| } |
| |
| std::string ArcAuthService::GetAndResetAuthCode() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| std::string auth_code; |
| auth_code_.swap(auth_code); |
| return auth_code; |
| } |
| |
| void ArcAuthService::GetAuthCodeDeprecated( |
| const GetAuthCodeDeprecatedCallback& callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK(!IsOptInVerificationDisabled()); |
| callback.Run(mojo::String(GetAndResetAuthCode())); |
| } |
| |
| void ArcAuthService::GetAuthCode(const GetAuthCodeCallback& callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| const std::string auth_code = GetAndResetAuthCode(); |
| const bool verification_disabled = IsOptInVerificationDisabled(); |
| if (!auth_code.empty() || verification_disabled) { |
| callback.Run(mojo::String(auth_code), !verification_disabled); |
| return; |
| } |
| |
| initial_opt_in_ = false; |
| auth_callback_ = callback; |
| StartUI(); |
| } |
| |
| void ArcAuthService::OnSignInComplete() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK_EQ(state_, State::ACTIVE); |
| DCHECK(!sign_in_time_.is_null()); |
| |
| waiting_for_reply_ = false; |
| |
| if (!IsOptInVerificationDisabled() && |
| !profile_->GetPrefs()->GetBoolean(prefs::kArcSignedIn)) { |
| playstore_launcher_.reset( |
| new ArcAppLauncher(profile_, kPlayStoreAppId, true)); |
| } |
| |
| profile_->GetPrefs()->SetBoolean(prefs::kArcSignedIn, true); |
| CloseUI(); |
| UpdateProvisioningTiming(base::Time::Now() - sign_in_time_, true, |
| IsAccountManaged(profile_)); |
| UpdateProvisioningResultUMA(ProvisioningResult::SUCCESS); |
| |
| FOR_EACH_OBSERVER(Observer, observer_list_, OnInitialStart()); |
| } |
| |
| void ArcAuthService::OnSignInFailed(arc::mojom::ArcSignInFailureReason reason) { |
| OnSignInFailedInternal( |
| ConvertArcSignInFailureReasonToProvisioningResult(reason)); |
| } |
| |
| void ArcAuthService::OnSignInFailedInternal(ProvisioningResult result) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK_EQ(state_, State::ACTIVE); |
| DCHECK(!sign_in_time_.is_null()); |
| |
| waiting_for_reply_ = false; |
| |
| UpdateProvisioningTiming(base::Time::Now() - sign_in_time_, false, |
| IsAccountManaged(profile_)); |
| UpdateOptInCancelUMA(OptInCancelReason::CLOUD_PROVISION_FLOW_FAIL); |
| UpdateProvisioningResultUMA(result); |
| |
| int error_message_id; |
| switch (result) { |
| case ProvisioningResult::GMS_NETWORK_ERROR: |
| error_message_id = IDS_ARC_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_message_id = IDS_ARC_SIGN_IN_SERVICE_UNAVAILABLE_ERROR; |
| break; |
| case ProvisioningResult::GMS_BAD_AUTHENTICATION: |
| error_message_id = IDS_ARC_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_message_id = IDS_ARC_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_message_id = IDS_ARC_SIGN_IN_CLOUD_PROVISION_FLOW_FAIL_ERROR; |
| break; |
| default: |
| error_message_id = IDS_ARC_SIGN_IN_UNKNOWN_ERROR; |
| break; |
| } |
| |
| if (result == ProvisioningResult::ARC_STOPPED) { |
| if (profile_->GetPrefs()->HasPrefPath(prefs::kArcSignedIn)) |
| profile_->GetPrefs()->SetBoolean(prefs::kArcSignedIn, false); |
| ShutdownBridgeAndShowUI(UIPage::ERROR, |
| l10n_util::GetStringUTF16(error_message_id)); |
| return; |
| } |
| |
| if (result == ProvisioningResult::CLOUD_PROVISION_FLOW_FAILED || |
| result == ProvisioningResult::CLOUD_PROVISION_FLOW_TIMEOUT || |
| result == ProvisioningResult::CLOUD_PROVISION_FLOW_INTERNAL_ERROR || |
| result == ProvisioningResult::UNKNOWN_ERROR) |
| RemoveArcData(); |
| |
| // We'll delay shutting down the bridge in this case to allow people to send |
| // feedback. |
| ShowUI(UIPage::ERROR_WITH_FEEDBACK, |
| l10n_util::GetStringUTF16(error_message_id)); |
| } |
| |
| void ArcAuthService::GetIsAccountManaged( |
| const GetIsAccountManagedCallback& callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| callback.Run(IsAccountManaged(profile_)); |
| } |
| |
| void ArcAuthService::SetState(State state) { |
| if (state_ == state) |
| return; |
| |
| state_ = state; |
| FOR_EACH_OBSERVER(Observer, observer_list_, OnOptInChanged(state_)); |
| } |
| |
| bool ArcAuthService::IsAllowed() const { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| return profile_ != nullptr; |
| } |
| |
| void ArcAuthService::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 (IsArcDisabledForEnterprise() && IsAccountManaged(profile)) { |
| VLOG(2) << "Enterprise users are not supported in ARC."; |
| return; |
| } |
| |
| profile_ = profile; |
| SetState(State::STOPPED); |
| |
| PrefServiceSyncableFromProfile(profile_)->AddSyncedPrefObserver( |
| prefs::kArcEnabled, this); |
| |
| context_.reset(new ArcAuthContext(this, profile_)); |
| |
| // In case UI is disabled we assume that ARC is opted-in. |
| if (IsOptInVerificationDisabled()) { |
| auth_code_.clear(); |
| StartArc(); |
| return; |
| } |
| |
| 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(&ArcAuthService::OnOptInPreferenceChanged, |
| weak_ptr_factory_.GetWeakPtr())); |
| if (profile_->GetPrefs()->GetBoolean(prefs::kArcEnabled)) { |
| OnOptInPreferenceChanged(); |
| } else { |
| RemoveArcData(); |
| UpdateEnabledStateUMA(false); |
| PrefServiceSyncableFromProfile(profile_)->AddObserver(this); |
| OnIsSyncingChanged(); |
| } |
| } |
| |
| void ArcAuthService::OnIsSyncingChanged() { |
| syncable_prefs::PrefServiceSyncable* const pref_service_syncable = |
| PrefServiceSyncableFromProfile(profile_); |
| if (!pref_service_syncable->IsSyncing()) |
| return; |
| |
| pref_service_syncable->RemoveObserver(this); |
| |
| if (IsArcEnabled()) |
| OnOptInPreferenceChanged(); |
| |
| if (!g_disable_ui_for_testing && profile_->IsNewProfile() && |
| !profile_->GetPrefs()->HasPrefPath(prefs::kArcEnabled)) { |
| ArcAuthNotification::Show(profile_); |
| } |
| } |
| |
| void ArcAuthService::Shutdown() { |
| ShutdownBridgeAndCloseUI(); |
| if (profile_) { |
| syncable_prefs::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 ArcAuthService::ShowUI(UIPage page, const base::string16& status) { |
| if (g_disable_ui_for_testing || IsOptInVerificationDisabled()) |
| return; |
| |
| SetUIPage(page, status); |
| const extensions::AppWindowRegistry* const app_window_registry = |
| extensions::AppWindowRegistry::Get(profile_); |
| DCHECK(app_window_registry); |
| if (app_window_registry->GetCurrentAppWindowForApp( |
| ArcSupportHost::kHostAppId)) { |
| return; |
| } |
| |
| const extensions::Extension* extension = |
| extensions::ExtensionRegistry::Get(profile_)->GetInstalledExtension( |
| ArcSupportHost::kHostAppId); |
| CHECK(extension && extensions::util::IsAppLaunchable( |
| ArcSupportHost::kHostAppId, profile_)); |
| |
| OpenApplication(CreateAppLaunchParamsUserContainer( |
| profile_, extension, NEW_WINDOW, extensions::SOURCE_CHROME_INTERNAL)); |
| } |
| |
| void ArcAuthService::OnContextReady() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| DCHECK(!initial_opt_in_); |
| CheckAndroidManagement(false); |
| } |
| |
| void ArcAuthService::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 (!disable_arc_from_ui_ && !arc_enabled && !IsArcManaged()) { |
| ash::ShelfDelegate* shelf_delegate = GetShelfDelegate(); |
| if (shelf_delegate) |
| shelf_delegate->UnpinAppWithID(ArcSupportHost::kHostAppId); |
| } |
| } |
| } |
| |
| void ArcAuthService::StopArc() { |
| if (state_ != State::STOPPED) { |
| UpdateEnabledStateUMA(false); |
| profile_->GetPrefs()->SetBoolean(prefs::kArcSignedIn, false); |
| } |
| ShutdownBridgeAndCloseUI(); |
| } |
| |
| void ArcAuthService::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_EACH_OBSERVER(Observer, observer_list_, OnOptInEnabled(arc_enabled)); |
| |
| if (!arc_enabled) { |
| StopArc(); |
| RemoveArcData(); |
| return; |
| } |
| |
| if (state_ == State::ACTIVE) |
| return; |
| CloseUI(); |
| auth_code_.clear(); |
| |
| if (!profile_->GetPrefs()->GetBoolean(prefs::kArcSignedIn)) { |
| // Need pre-fetch auth code and show OptIn UI if needed. |
| initial_opt_in_ = true; |
| StartUI(); |
| } else { |
| // Ready to start Arc, but check Android management first. |
| if (!g_disable_ui_for_testing || |
| g_enable_check_android_management_for_testing) { |
| CheckAndroidManagement(true); |
| } else { |
| StartArc(); |
| } |
| } |
| |
| UpdateEnabledStateUMA(true); |
| } |
| |
| void ArcAuthService::ShutdownBridge() { |
| playstore_launcher_.reset(); |
| auth_callback_.Reset(); |
| android_management_checker_.reset(); |
| arc_bridge_service()->Shutdown(); |
| if (state_ != State::NOT_INITIALIZED) |
| SetState(State::STOPPED); |
| FOR_EACH_OBSERVER(Observer, observer_list_, OnShutdownBridge()); |
| } |
| |
| void ArcAuthService::ShutdownBridgeAndCloseUI() { |
| ShutdownBridge(); |
| CloseUI(); |
| } |
| |
| void ArcAuthService::ShutdownBridgeAndShowUI(UIPage page, |
| const base::string16& status) { |
| ShutdownBridge(); |
| ShowUI(page, status); |
| } |
| |
| void ArcAuthService::AddObserver(Observer* observer) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| observer_list_.AddObserver(observer); |
| } |
| |
| void ArcAuthService::RemoveObserver(Observer* observer) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| observer_list_.RemoveObserver(observer); |
| } |
| |
| void ArcAuthService::CloseUI() { |
| FOR_EACH_OBSERVER(Observer, observer_list_, OnOptInUIClose()); |
| SetUIPage(UIPage::NO_PAGE, base::string16()); |
| if (!g_disable_ui_for_testing) |
| ArcAuthNotification::Hide(); |
| } |
| |
| void ArcAuthService::SetUIPage(UIPage page, const base::string16& status) { |
| ui_page_ = page; |
| ui_page_status_ = status; |
| FOR_EACH_OBSERVER(Observer, observer_list_, |
| OnOptInUIShowPage(ui_page_, ui_page_status_)); |
| } |
| |
| // This is the special method to support enterprise mojo API. |
| // TODO(hidehiko): Remove this. |
| void ArcAuthService::StopAndEnableArc() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK(!arc_bridge_service()->stopped()); |
| reenable_arc_ = true; |
| StopArc(); |
| } |
| |
| void ArcAuthService::StartArc() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| arc_bridge_service()->HandleStartup(); |
| SetState(State::ACTIVE); |
| } |
| |
| void ArcAuthService::SetAuthCodeAndStartArc(const std::string& auth_code) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK(!auth_code.empty()); |
| |
| if (!auth_callback_.is_null()) { |
| DCHECK_EQ(state_, State::FETCHING_CODE); |
| SetState(State::ACTIVE); |
| auth_callback_.Run(mojo::String(auth_code), !IsOptInVerificationDisabled()); |
| auth_callback_.Reset(); |
| return; |
| } |
| |
| State state = state_; |
| if (state != State::FETCHING_CODE) { |
| ShutdownBridgeAndCloseUI(); |
| return; |
| } |
| |
| sign_in_time_ = base::Time::Now(); |
| |
| SetUIPage(UIPage::START_PROGRESS, base::string16()); |
| ShutdownBridge(); |
| auth_code_ = auth_code; |
| waiting_for_reply_ = true; |
| StartArc(); |
| } |
| |
| void ArcAuthService::StartLso() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // Update UMA only if error (with or without feedback) is currently shown. |
| if (ui_page_ == UIPage::ERROR) { |
| UpdateOptInActionUMA(OptInActionType::RETRY); |
| } else if (ui_page_ == UIPage::ERROR_WITH_FEEDBACK) { |
| UpdateOptInActionUMA(OptInActionType::RETRY); |
| ShutdownBridge(); |
| } |
| |
| initial_opt_in_ = false; |
| StartUI(); |
| } |
| |
| void ArcAuthService::CancelAuthCode() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| if (state_ == State::NOT_INITIALIZED) { |
| NOTREACHED(); |
| return; |
| } |
| |
| // In case |state_| is ACTIVE, |ui_page_| can be START_PROGRESS (which means |
| // normal Arc booting) or ERROR or ERROR_WITH_FEEDBACK (in case Arc can not |
| // be started). If Arc is booting normally dont't stop it on progress close. |
| if (state_ != State::FETCHING_CODE && ui_page_ != UIPage::ERROR && |
| ui_page_ != UIPage::ERROR_WITH_FEEDBACK) { |
| return; |
| } |
| |
| // Update UMA with user cancel only if error is not currently shown. |
| if (ui_page_ != UIPage::ERROR && ui_page_ == UIPage::ERROR_WITH_FEEDBACK && |
| ui_page_ != UIPage::NO_PAGE) { |
| UpdateOptInCancelUMA(OptInCancelReason::USER_CANCEL); |
| } |
| |
| StopArc(); |
| |
| if (IsArcManaged()) |
| return; |
| |
| base::AutoReset<bool> auto_reset(&disable_arc_from_ui_, true); |
| DisableArc(); |
| } |
| |
| bool ArcAuthService::IsArcManaged() const { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK(profile_); |
| return profile_->GetPrefs()->IsManagedPreference(prefs::kArcEnabled); |
| } |
| |
| bool ArcAuthService::IsArcEnabled() const { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (!IsAllowed()) |
| return false; |
| |
| DCHECK(profile_); |
| return profile_->GetPrefs()->GetBoolean(prefs::kArcEnabled); |
| } |
| |
| void ArcAuthService::EnableArc() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK(profile_); |
| |
| if (IsArcEnabled()) { |
| OnOptInPreferenceChanged(); |
| return; |
| } |
| |
| if (!IsArcManaged()) |
| profile_->GetPrefs()->SetBoolean(prefs::kArcEnabled, true); |
| } |
| |
| void ArcAuthService::DisableArc() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK(profile_); |
| profile_->GetPrefs()->SetBoolean(prefs::kArcEnabled, false); |
| } |
| |
| void ArcAuthService::StartUI() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| 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. |
| ShowUI(UIPage::ERROR, l10n_util::GetStringUTF16( |
| IDS_ARC_SIGN_IN_SERVICE_UNAVAILABLE_ERROR)); |
| return; |
| } |
| |
| SetState(State::FETCHING_CODE); |
| |
| if (initial_opt_in_) { |
| initial_opt_in_ = false; |
| ShowUI(UIPage::TERMS_PROGRESS, base::string16()); |
| } else { |
| context_->PrepareContext(); |
| } |
| } |
| |
| void ArcAuthService::OnPrepareContextFailed() { |
| DCHECK_EQ(state_, State::FETCHING_CODE); |
| |
| ShutdownBridgeAndShowUI( |
| UIPage::ERROR, |
| l10n_util::GetStringUTF16(IDS_ARC_SERVER_COMMUNICATION_ERROR)); |
| UpdateOptInCancelUMA(OptInCancelReason::NETWORK_ERROR); |
| } |
| |
| void ArcAuthService::CheckAndroidManagement(bool background_mode) { |
| // Do not send requests for Chrome OS managed users. |
| if (IsAccountManaged(profile_)) { |
| StartArcIfSignedIn(); |
| return; |
| } |
| |
| // Do not send requests for well-known consumer domains. |
| if (policy::BrowserPolicyConnector::IsNonEnterpriseUser( |
| profile_->GetProfileUserName())) { |
| StartArcIfSignedIn(); |
| return; |
| } |
| |
| android_management_checker_.reset( |
| new ArcAndroidManagementChecker(this, context_->token_service(), |
| context_->account_id(), background_mode)); |
| if (background_mode) |
| StartArcIfSignedIn(); |
| } |
| |
| void ArcAuthService::OnAndroidManagementChecked( |
| policy::AndroidManagementClient::Result result) { |
| switch (result) { |
| case policy::AndroidManagementClient::Result::RESULT_UNMANAGED: |
| StartArcIfSignedIn(); |
| break; |
| case policy::AndroidManagementClient::Result::RESULT_MANAGED: |
| if (android_management_checker_->background_mode()) { |
| DisableArc(); |
| return; |
| } |
| ShutdownBridgeAndShowUI( |
| UIPage::ERROR, |
| l10n_util::GetStringUTF16(IDS_ARC_ANDROID_MANAGEMENT_REQUIRED_ERROR)); |
| UpdateOptInCancelUMA(OptInCancelReason::ANDROID_MANAGEMENT_REQUIRED); |
| break; |
| case policy::AndroidManagementClient::Result::RESULT_ERROR: |
| ShutdownBridgeAndShowUI( |
| UIPage::ERROR, |
| l10n_util::GetStringUTF16(IDS_ARC_SERVER_COMMUNICATION_ERROR)); |
| UpdateOptInCancelUMA(OptInCancelReason::NETWORK_ERROR); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| void ArcAuthService::StartArcIfSignedIn() { |
| if (state_ == State::ACTIVE) |
| return; |
| if (profile_->GetPrefs()->GetBoolean(prefs::kArcSignedIn) || |
| IsOptInVerificationDisabled()) { |
| StartArc(); |
| } else { |
| ShowUI(UIPage::LSO_PROGRESS, base::string16()); |
| } |
| } |
| |
| std::ostream& operator<<(std::ostream& os, const ArcAuthService::State& state) { |
| switch (state) { |
| case ArcAuthService::State::NOT_INITIALIZED: |
| return os << kStateNotInitialized; |
| case ArcAuthService::State::STOPPED: |
| return os << kStateStopped; |
| case ArcAuthService::State::FETCHING_CODE: |
| return os << kStateFetchingCode; |
| case ArcAuthService::State::ACTIVE: |
| return os << kStateActive; |
| default: |
| NOTREACHED(); |
| return os; |
| } |
| } |
| |
| } // namespace arc |