| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ash/login/quickstart_controller.h" |
| |
| #include <memory> |
| |
| #include "ash/constants/ash_features.h" |
| #include "ash/constants/ash_switches.h" |
| #include "ash/public/cpp/bluetooth_config_service.h" |
| #include "base/check.h" |
| #include "base/logging.h" |
| #include "chrome/browser/ash/login/demo_mode/demo_setup_controller.h" |
| #include "chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker.h" |
| #include "chrome/browser/ash/login/oobe_quick_start/oobe_quick_start_pref_names.h" |
| #include "chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller.h" |
| #include "chrome/browser/ash/login/oobe_screen.h" |
| #include "chrome/browser/ash/login/ui/login_display_host.h" |
| #include "chrome/browser/ash/login/wizard_context.h" |
| #include "chrome/browser/ash/login/wizard_controller.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/ui/webui/ash/login/consumer_update_screen_handler.h" |
| #include "chrome/browser/ui/webui/ash/login/gaia_info_screen_handler.h" |
| #include "chrome/browser/ui/webui/ash/login/gaia_screen_handler.h" |
| #include "chrome/browser/ui/webui/ash/login/network_screen_handler.h" |
| #include "chrome/browser/ui/webui/ash/login/online_login_utils.h" |
| #include "chrome/browser/ui/webui/ash/login/quick_start_screen_handler.h" |
| #include "chrome/browser/ui/webui/ash/login/user_creation_screen_handler.h" |
| #include "chrome/browser/ui/webui/ash/login/welcome_screen_handler.h" |
| #include "chromeos/ash/components/login/auth/public/user_context.h" |
| #include "chromeos/ash/components/network/network_state_handler.h" |
| #include "chromeos/ash/components/network/network_type_pattern.h" |
| #include "chromeos/ash/components/quick_start/logging.h" |
| #include "chromeos/ash/components/quick_start/quick_start_metrics.h" |
| #include "chromeos/ash/components/quick_start/types.h" |
| #include "components/account_id/account_id.h" |
| #include "components/session_manager/core/session_manager.h" |
| #include "components/session_manager/session_manager_types.h" |
| #include "components/user_manager/user_type.h" |
| |
| namespace ash::quick_start { |
| |
| namespace { |
| |
| using bluetooth_config::mojom::BluetoothDevicePropertiesPtr; |
| using bluetooth_config::mojom::BluetoothSystemState; |
| |
| std::string GetBluetoothStateString(BluetoothSystemState system_state) { |
| switch (system_state) { |
| case BluetoothSystemState::kDisabled: |
| return "Bluetooth is turned off."; |
| case BluetoothSystemState::kDisabling: |
| return "Bluetooth is in the process of turning off."; |
| case BluetoothSystemState::kEnabled: |
| return "Bluetooth is turned on."; |
| case BluetoothSystemState::kEnabling: |
| return "Bluetooth is in the process of turning on."; |
| case BluetoothSystemState::kUnavailable: |
| return "Device does not have access to Bluetooth."; |
| default: |
| return "Unknown bluetooth state!"; |
| } |
| } |
| |
| std::optional<QuickStartController::EntryPoint> EntryPointFromScreen( |
| OobeScreenId screen) { |
| if (screen.name == WelcomeScreenHandler::kScreenId.name) { |
| return QuickStartController::EntryPoint::WELCOME_SCREEN; |
| } else if (screen.name == NetworkScreenHandler::kScreenId.name) { |
| return QuickStartController::EntryPoint::NETWORK_SCREEN; |
| } else if (screen.name == GaiaInfoScreenHandler::kScreenId.name) { |
| return QuickStartController::EntryPoint::GAIA_INFO_SCREEN; |
| } else if (screen.name == GaiaScreenHandler::kScreenId.name) { |
| return QuickStartController::EntryPoint::GAIA_SCREEN; |
| } |
| return std::nullopt; |
| } |
| |
| QuickStartMetrics::ScreenName ScreenNameFromOobeScreenId( |
| OobeScreenId screen_id) { |
| // TODO(b/298042953): Check Screen IDs for Unicorn account setup flow. |
| if (screen_id == ConsumerUpdateScreenView::kScreenId) { |
| // TODO(b/298042953): Update Screen ID when the new OOBE Checking for |
| // update and determining device configuration screen is added. |
| return QuickStartMetrics::ScreenName:: |
| kCheckingForUpdateAndDeterminingDeviceConfiguration; |
| } else if (screen_id == UserCreationView::kScreenId) { |
| return QuickStartMetrics::ScreenName::kChooseChromebookSetup; |
| } |
| return QuickStartMetrics::ScreenName::kOther; |
| } |
| |
| bool IsConnectedToWiFi() { |
| NetworkStateHandler* nsh = NetworkHandler::Get()->network_state_handler(); |
| return nsh->ConnectedNetworkByType(NetworkTypePattern::WiFi()) != nullptr; |
| } |
| |
| TargetDeviceBootstrapController::ConnectionClosedReason |
| ConnectionClosedReasonFromAbortFlowReason( |
| QuickStartController::AbortFlowReason reason) { |
| switch (reason) { |
| case QuickStartController::AbortFlowReason::USER_CLICKED_CANCEL: |
| [[fallthrough]]; |
| case QuickStartController::AbortFlowReason::USER_CLICKED_BACK: |
| [[fallthrough]]; |
| case QuickStartController::AbortFlowReason::SIGNIN_SCHOOL: |
| [[fallthrough]]; |
| case QuickStartController::AbortFlowReason::ENTERPRISE_ENROLLMENT: |
| return TargetDeviceBootstrapController::ConnectionClosedReason:: |
| kUserAborted; |
| case QuickStartController::AbortFlowReason::QUICK_START_FLOW_COMPLETE: |
| return TargetDeviceBootstrapController::ConnectionClosedReason::kComplete; |
| case QuickStartController::AbortFlowReason::ERROR: |
| return TargetDeviceBootstrapController::ConnectionClosedReason:: |
| kUnknownError; |
| } |
| } |
| |
| } // namespace |
| |
| QuickStartController::QuickStartController() { |
| // Main feature flag |
| if (!features::IsOobeQuickStartEnabled()) { |
| return; |
| } |
| |
| // QuickStart may not be available on the login screen. |
| if (session_manager::SessionManager::Get()->session_state() != |
| session_manager::SessionState::OOBE && |
| !features::IsOobeQuickStartOnLoginScreenEnabled()) { |
| return; |
| } |
| |
| InitTargetDeviceBootstrapController(); |
| StartObservingBluetoothState(); |
| } |
| |
| QuickStartController::~QuickStartController() { |
| if (bootstrap_controller_) { |
| bootstrap_controller_->RemoveObserver(this); |
| } |
| } |
| |
| void QuickStartController::AttachFrontend( |
| QuickStartController::UiDelegate* delegate) { |
| CHECK(ui_delegates_.empty()) << "Only one UI delegate shall be attached!"; |
| ui_delegates_.AddObserver(delegate); |
| } |
| |
| void QuickStartController::DetachFrontend( |
| QuickStartController::UiDelegate* delegate) { |
| ui_delegates_.RemoveObserver(delegate); |
| } |
| |
| void QuickStartController::UpdateUiState(UiState ui_state) { |
| QS_LOG(INFO) << "Updating UI state to " << ui_state; |
| ui_state_ = ui_state; |
| CHECK(!ui_delegates_.empty()); |
| for (auto& delegate : ui_delegates_) { |
| delegate.OnUiUpdateRequested(ui_state_.value()); |
| } |
| } |
| |
| void QuickStartController::ForceEnableQuickStart() { |
| if (bootstrap_controller_) { |
| return; |
| } |
| |
| InitTargetDeviceBootstrapController(); |
| StartObservingBluetoothState(); |
| |
| QS_LOG(INFO) << "Force enabling LocalPasswordsForConsumers!"; |
| ash::features::ForceEnableLocalPasswordsForConsumers(); |
| } |
| |
| void QuickStartController::DetermineEntryPointVisibility( |
| EntryPointButtonVisibilityCallback callback) { |
| // Bootstrap controller is only instantiated when the feature is enabled (also |
| // via the keyboard shortcut. See |ForceEnableQuickStart|.) |
| if (!bootstrap_controller_) { |
| std::move(callback).Run(/*visible=*/false); |
| return; |
| } |
| |
| // QuickStart should not be enabled for Demo mode or OS Install flows |
| if (DemoSetupController::IsOobeDemoSetupFlowInProgress() || |
| ash::switches::IsOsInstallAllowed()) { |
| std::move(callback).Run(/*visible=*/false); |
| return; |
| } |
| |
| // If the flow is ongoing, entry points are hidden. |
| if (IsSetupOngoing()) { |
| std::move(callback).Run(/*visible=*/false); |
| return; |
| } |
| |
| bootstrap_controller_->GetFeatureSupportStatusAsync( |
| base::BindOnce(&QuickStartController::OnGetQuickStartFeatureSupportStatus, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void QuickStartController::AbortFlow(AbortFlowReason reason) { |
| CHECK(bootstrap_controller_); |
| QS_LOG(INFO) << "Aborting flow: " << reason; |
| |
| bootstrap_controller_->CloseOpenConnections( |
| ConnectionClosedReasonFromAbortFlowReason(reason)); |
| bootstrap_controller_->StopAdvertising(); |
| bootstrap_controller_->Cleanup(); |
| ResetState(); |
| |
| // Triggers a screen exit if there is a UiDelegate driving the UI. |
| if (!ui_delegates_.empty()) { |
| CHECK(current_screen_ == QuickStartScreenHandler::kScreenId || |
| current_screen_ == NetworkScreenHandler::kScreenId); |
| ui_delegates_.begin()->OnUiUpdateRequested(UiState::EXIT_SCREEN); |
| } |
| } |
| |
| QuickStartController::EntryPoint QuickStartController::GetExitPoint() { |
| return exit_point_.value(); |
| } |
| |
| void QuickStartController::PrepareForUpdate() { |
| bootstrap_controller_->PrepareForUpdate(); |
| } |
| |
| void QuickStartController::ResumeSessionAfterCancelledUpdate() { |
| LoginDisplayHost::default_host() |
| ->GetWizardContext() |
| ->quick_start_setup_ongoing = true; |
| controller_state_ = ControllerState::WAITING_TO_RESUME_AFTER_UPDATE; |
| } |
| |
| void QuickStartController::InitTargetDeviceBootstrapController() { |
| CHECK(LoginDisplayHost::default_host()); |
| CHECK(!bootstrap_controller_); |
| |
| if (g_browser_process->local_state()->GetBoolean( |
| prefs::kShouldResumeQuickStartAfterReboot)) { |
| g_browser_process->local_state()->ClearPref( |
| prefs::kShouldResumeQuickStartAfterReboot); |
| LoginDisplayHost::default_host() |
| ->GetWizardContext() |
| ->quick_start_setup_ongoing = true; |
| controller_state_ = ControllerState::WAITING_TO_RESUME_AFTER_UPDATE; |
| } |
| |
| StartObservingScreenTransitions(); |
| LoginDisplayHost::default_host()->GetWizardContext()->quick_start_enabled = |
| true; |
| bootstrap_controller_ = |
| LoginDisplayHost::default_host()->GetQuickStartBootstrapController(); |
| |
| // Start observing and determine the discoverable name. |
| bootstrap_controller_->AddObserver(this); |
| discoverable_name_ = bootstrap_controller_->GetDiscoverableName(); |
| } |
| |
| void QuickStartController::OnGetQuickStartFeatureSupportStatus( |
| EntryPointButtonVisibilityCallback set_button_visibility_callback, |
| TargetDeviceConnectionBroker::FeatureSupportStatus status) { |
| const bool visible = |
| status == TargetDeviceConnectionBroker::FeatureSupportStatus::kSupported; |
| |
| // Make the entry point button visible when supported, otherwise keep hidden. |
| std::move(set_button_visibility_callback).Run(visible); |
| } |
| |
| void QuickStartController::OnStatusChanged( |
| const TargetDeviceBootstrapController::Status& status) { |
| using Step = TargetDeviceBootstrapController::Step; |
| using ErrorCode = TargetDeviceBootstrapController::ErrorCode; |
| |
| // TODO(b/298042953): Emit ScreenOpened metrics when automatically |
| // resuming after an update. |
| switch (status.step) { |
| case Step::ADVERTISING_WITH_QR_CODE: |
| controller_state_ = ControllerState::ADVERTISING; |
| CHECK(absl::holds_alternative<QRCode::PixelData>(status.payload)); |
| qr_code_data_ = absl::get<QRCode::PixelData>(status.payload); |
| UpdateUiState(UiState::SHOWING_QR); |
| QuickStartMetrics::RecordScreenOpened( |
| QuickStartMetrics::ScreenName::kSetUpWithAndroidPhone); |
| return; |
| case Step::ADVERTISING_WITHOUT_QR_CODE: |
| UpdateUiState(UiState::CONNECTING_TO_PHONE); |
| return; |
| case Step::PIN_VERIFICATION: |
| CHECK(absl::holds_alternative<PinString>(status.payload)); |
| pin_ = *absl::get<PinString>(status.payload); |
| CHECK_EQ(pin_.value().length(), 4UL); |
| UpdateUiState(UiState::SHOWING_PIN); |
| QuickStartMetrics::RecordScreenOpened( |
| QuickStartMetrics::ScreenName::kSetUpWithAndroidPhone); |
| return; |
| case Step::CONNECTED: |
| controller_state_ = ControllerState::CONNECTED; |
| OnPhoneConnectionEstablished(); |
| return; |
| case Step::REQUESTING_WIFI_CREDENTIALS: |
| UpdateUiState(UiState::CONNECTING_TO_WIFI); |
| QuickStartMetrics::RecordScreenOpened( |
| QuickStartMetrics::ScreenName::kConnectingToWifi); |
| return; |
| case Step::WIFI_CREDENTIALS_RECEIVED: |
| CHECK(absl::holds_alternative<mojom::WifiCredentials>(status.payload)); |
| |
| LoginDisplayHost::default_host() |
| ->GetWizardContext() |
| ->quick_start_wifi_credentials = |
| absl::get<mojom::WifiCredentials>(status.payload); |
| ABSL_FALLTHROUGH_INTENDED; |
| case Step::EMPTY_WIFI_CREDENTIALS_RECEIVED: |
| UpdateUiState(UiState::WIFI_CREDENTIALS_RECEIVED); |
| return; |
| case Step::REQUESTING_GOOGLE_ACCOUNT_INFO: |
| return; |
| case Step::GOOGLE_ACCOUNT_INFO_RECEIVED: |
| CHECK(absl::holds_alternative<EmailString>(status.payload)); |
| // If there aren't any accounts on the phone, the flow is aborted. |
| if (absl::get<EmailString>(status.payload)->empty()) { |
| QS_LOG(ERROR) << "No account on Android phone. No email received."; |
| AbortFlow(AbortFlowReason::ERROR); |
| return; |
| } |
| |
| // Populate the 'UserInfo' that is shown on the UI and start the transfer. |
| user_info_.email = *absl::get<EmailString>(status.payload); |
| UpdateUiState(UiState::SIGNING_IN); |
| bootstrap_controller_->AttemptGoogleAccountTransfer(); |
| return; |
| case Step::TRANSFERRING_GOOGLE_ACCOUNT_DETAILS: |
| // Intermediate state. Nothing to do. |
| if (controller_state_ != ControllerState::CONNECTED) { |
| QS_LOG(ERROR) << "Expected controller_state_ to be CONNECTED. Actual " |
| "controller_state_: " |
| << controller_state_; |
| AbortFlow(AbortFlowReason::ERROR); |
| } |
| // TODO(b/298042953): Record Gaia Transfer screen shown once UI is |
| // implemented. |
| return; |
| case Step::TRANSFERRED_GOOGLE_ACCOUNT_DETAILS: |
| if (controller_state_ != ControllerState::CONNECTED) { |
| QS_LOG(ERROR) << "Expected controller_state_ to be CONNECTED. Actual " |
| "controller_state_: " |
| << controller_state_; |
| AbortFlow(AbortFlowReason::ERROR); |
| return; |
| } |
| |
| if (absl::holds_alternative< |
| TargetDeviceBootstrapController::GaiaCredentials>( |
| status.payload)) { |
| QS_LOG(INFO) << "Successfully received an OAuth authorization code."; |
| OnOAuthTokenReceived( |
| absl::get<TargetDeviceBootstrapController::GaiaCredentials>( |
| status.payload)); |
| } else { |
| CHECK(absl::holds_alternative<ErrorCode>(status.payload)); |
| QS_LOG(ERROR) << "Error receiving FIDO assertion. Error Code = " |
| << static_cast<int>(absl::get<ErrorCode>(status.payload)); |
| |
| // TODO(b:286873060) - Implement retry mechanism/graceful exit. |
| NOTIMPLEMENTED(); |
| } |
| return; |
| case Step::NONE: |
| // Indicates we've stopped advertising and are not connected to the source |
| // device. No action required. |
| return; |
| case Step::ERROR: |
| if (absl::holds_alternative<ErrorCode>(status.payload)) { |
| QS_LOG(ERROR) << absl::get<ErrorCode>(status.payload); |
| } else { |
| QS_LOG(ERROR) << "Missing ErrorCode."; |
| } |
| AbortFlow(AbortFlowReason::ERROR); |
| return; |
| case Step::FLOW_ABORTED: |
| return; |
| case Step::SETUP_COMPLETE: |
| ResetState(); |
| return; |
| } |
| } |
| |
| void QuickStartController::OnCurrentScreenChanged(OobeScreenId previous_screen, |
| OobeScreenId current_screen) { |
| current_screen_ = current_screen; |
| previous_screen_ = previous_screen; |
| |
| if (current_screen_ == QuickStartScreenHandler::kScreenId) { |
| // Just switched into the quick start screen. The ScreenOpened metrics on |
| // the Quick Start screen are recorded from OnStatusChanged(). |
| HandleTransitionToQuickStartScreen(); |
| } else if (IsSetupOngoing()) { |
| QuickStartMetrics::RecordScreenOpened( |
| ScreenNameFromOobeScreenId(current_screen)); |
| } |
| } |
| |
| void QuickStartController::OnDestroyingOobeUI() { |
| observation_.Reset(); |
| } |
| |
| void QuickStartController::OnOAuthTokenReceived( |
| TargetDeviceBootstrapController::GaiaCredentials gaia_creds) { |
| gaia_creds_ = gaia_creds; |
| |
| if (gaia_creds_.gaia_id.empty()) { |
| QS_LOG(ERROR) << "Obfuscated Gaia ID missing!"; |
| AbortFlow(AbortFlowReason::ERROR); |
| return; |
| } |
| |
| FinishAccountCreation(); |
| } |
| |
| void QuickStartController::StartObservingScreenTransitions() { |
| CHECK(LoginDisplayHost::default_host()); |
| CHECK(LoginDisplayHost::default_host()->GetOobeUI()); |
| observation_.Observe(LoginDisplayHost::default_host()->GetOobeUI()); |
| } |
| |
| void QuickStartController::HandleTransitionToQuickStartScreen() { |
| CHECK(current_screen_ == QuickStartScreenHandler::kScreenId); |
| |
| // No ongoing setup. Entering the screen via entry point. |
| if (!IsSetupOngoing()) { |
| // Initially there is no UI step. TargetDeviceBootstrapController |
| // then determines whether a loading spinner (for the PIN case), |
| // or the QR code will be shown. If bluetooth is not turned on, a dialog |
| // is shown asking the user for their permission first. |
| CHECK(!ui_state_.has_value()) << "Found UI state without ongoing setup!"; |
| |
| // Keep track of where the flow originated. |
| CHECK(!entry_point_.has_value()) << "Entry point without ongoing setup"; |
| const auto entry_point = EntryPointFromScreen(previous_screen_.value()); |
| CHECK(entry_point.has_value()) << "Unknown entry point!"; |
| exit_point_ = entry_point_ = entry_point; |
| |
| // Set the QuickStart flow as ongoing for the rest of the system. |
| LoginDisplayHost::default_host() |
| ->GetWizardContext() |
| ->quick_start_setup_ongoing = true; |
| |
| if (IsBluetoothDisabled()) { |
| controller_state_ = ControllerState::WAITING_FOR_BLUETOOTH_PERMISSION; |
| UpdateUiState(UiState::SHOWING_BLUETOOTH_DIALOG); |
| return; |
| } |
| |
| StartAdvertising(); |
| } else if (controller_state_ == |
| ControllerState::WAITING_TO_RESUME_AFTER_UPDATE) { |
| exit_point_ = QuickStartController::EntryPoint::GAIA_INFO_SCREEN; |
| |
| // It's possible the local state still needs to be cleared if an update was |
| // initiated but cancelled. We can't check/clear the state immediately upon |
| // cancelling the update since it's possible it happens before the target |
| // device persists this pref to local state. |
| if (g_browser_process->local_state()->GetBoolean( |
| prefs::kShouldResumeQuickStartAfterReboot)) { |
| g_browser_process->local_state()->ClearPref( |
| prefs::kShouldResumeQuickStartAfterReboot); |
| } |
| |
| if (IsBluetoothDisabled()) { |
| controller_state_ = ControllerState::WAITING_FOR_BLUETOOTH_PERMISSION; |
| UpdateUiState(UiState::SHOWING_BLUETOOTH_DIALOG); |
| return; |
| } |
| |
| StartAdvertising(); |
| } else { |
| // If the setup has finished, transitioning to QuickStart should |
| // show the last step of the flow. |
| if (controller_state_ == ControllerState::SETUP_COMPLETE) { |
| UpdateUiState(UiState::SETUP_COMPLETE); |
| SavePhoneInstanceID(); |
| bootstrap_controller_->OnSetupComplete(); |
| return; |
| } |
| |
| // The flow must be resuming after reaching the GaiaInfoScreen or |
| // GaiaScreen. Note the the GaiaInfoScreen/GaiaScreen is technically never |
| // shown when it switches to QuickStart, so |previous_screen_| is one of the |
| // many screens that may have appeared up to this point. |
| // TODO(b:283965994) - Improve the resume logic. |
| |
| // OOBE flow cannot go back after enrollment checks, update exit point. |
| exit_point_ = QuickStartController::EntryPoint::GAIA_INFO_SCREEN; |
| |
| if (controller_state_ != ControllerState::CONNECTED) { |
| QS_LOG(ERROR) << "Expected controller_state_ to be CONNECTED. Actual " |
| "controller_state_: " |
| << controller_state_; |
| AbortFlow(AbortFlowReason::ERROR); |
| return; |
| } |
| |
| CHECK(LoginDisplayHost::default_host() |
| ->GetWizardContext() |
| ->quick_start_setup_ongoing); |
| StartAccountTransfer(); |
| } |
| } |
| |
| void QuickStartController::StartAccountTransfer() { |
| UpdateUiState(UiState::CONFIRM_GOOGLE_ACCOUNT); |
| bootstrap_controller_->RequestGoogleAccountInfo(); |
| } |
| |
| void QuickStartController::OnPhoneConnectionEstablished() { |
| bootstrap_controller_->StopAdvertising(); |
| |
| // If cancelling the flow would end on the welcome or network screen, |
| // we are still early in the OOBE flow. Transfer WiFi creds if not already |
| // connected. |
| if (exit_point_ == EntryPoint::WELCOME_SCREEN || |
| exit_point_ == EntryPoint::NETWORK_SCREEN) { |
| if (IsConnectedToWiFi()) { |
| // This will cause the QuickStartScreen to exit and the NetworkScreen |
| // will be shown next. |
| UpdateUiState(UiState::WIFI_CREDENTIALS_RECEIVED); |
| } else { |
| bootstrap_controller_->AttemptWifiCredentialTransfer(); |
| } |
| } else { |
| // We are after the 'Gaia Info' screen. Transfer credentials. |
| StartAccountTransfer(); |
| } |
| } |
| |
| void QuickStartController::SavePhoneInstanceID() { |
| CHECK(bootstrap_controller_); |
| std::string phone_instance_id = bootstrap_controller_->GetPhoneInstanceId(); |
| if (phone_instance_id.empty()) { |
| return; |
| } |
| |
| QS_LOG(INFO) << "Adding Phone Instance ID to Wizard Object for Unified " |
| "Setup UI enhancements. quick_start_phone_instance_id: " |
| << phone_instance_id; |
| LoginDisplayHost::default_host() |
| ->GetWizardContext() |
| ->quick_start_phone_instance_id = phone_instance_id; |
| } |
| |
| void QuickStartController::FinishAccountCreation() { |
| CHECK(!gaia_creds_.email.empty()); |
| CHECK(!gaia_creds_.gaia_id.empty()); |
| CHECK(!gaia_creds_.auth_code.empty()); |
| |
| UpdateUiState(UiState::CREATING_ACCOUNT); |
| controller_state_ = ControllerState::SETUP_COMPLETE; |
| |
| const AccountId account_id = AccountId::FromNonCanonicalEmail( |
| gaia_creds_.email, gaia_creds_.gaia_id, AccountType::GOOGLE); |
| // The user type is known to be regular. The unicorn flow transitions to the |
| // Gaia screen and uses its own mechanism for account creation. |
| std::unique_ptr<UserContext> user_context = |
| login::BuildUserContextForGaiaSignIn( |
| /*user_type=*/user_manager::UserType::kRegular, |
| /*account_id=*/account_id, |
| /*using_saml=*/false, |
| /*using_saml_api=*/false, |
| /*password=*/"", |
| /*password_attributes=*/SamlPasswordAttributes(), |
| /*sync_trusted_vault_keys=*/std::nullopt, |
| /*challenge_response_key=*/std::nullopt); |
| user_context->SetAuthCode(gaia_creds_.auth_code); |
| |
| if (LoginDisplayHost::default_host()) { |
| LoginDisplayHost::default_host()->CompleteLogin(*user_context); |
| } |
| } |
| |
| void QuickStartController::ResetState() { |
| entry_point_.reset(); |
| qr_code_data_.reset(); |
| pin_.reset(); |
| user_info_ = UserInfo(); |
| gaia_creds_ = TargetDeviceBootstrapController::GaiaCredentials(); |
| wifi_name_.reset(); |
| controller_state_ = ControllerState::NOT_ACTIVE; |
| ui_state_.reset(); |
| auto* wizard_context = LoginDisplayHost::default_host()->GetWizardContext(); |
| wizard_context->quick_start_setup_ongoing = false; |
| wizard_context->quick_start_wifi_credentials.reset(); |
| // Don't cleanup |bootstrap_controller_| state here, since it may be waiting |
| // for source device to gracefully drop connection. |
| } |
| |
| /******************* Bluetooth dialog related functions *******************/ |
| |
| void QuickStartController::StartObservingBluetoothState() { |
| GetBluetoothConfigService( |
| cros_bluetooth_config_remote_.BindNewPipeAndPassReceiver()); |
| cros_bluetooth_config_remote_->ObserveSystemProperties( |
| cros_system_properties_observer_receiver_.BindNewPipeAndPassRemote()); |
| } |
| |
| void QuickStartController::OnPropertiesUpdated( |
| bluetooth_config::mojom::BluetoothSystemPropertiesPtr properties) { |
| if (bluetooth_system_state_ == properties->system_state) { |
| return; |
| } |
| |
| bluetooth_system_state_ = properties->system_state; |
| |
| if (!IsSetupOngoing()) { |
| return; |
| } |
| |
| QS_LOG(INFO) << "New Bluetooth state: " |
| << GetBluetoothStateString(bluetooth_system_state_); |
| if (controller_state_ == ControllerState::WAITING_FOR_BLUETOOTH_PERMISSION || |
| controller_state_ == ControllerState::WAITING_FOR_BLUETOOTH_ACTIVATION) { |
| if (bluetooth_system_state_ == BluetoothSystemState::kEnabled) { |
| StartAdvertising(); |
| } |
| } |
| } |
| |
| bool QuickStartController::IsBluetoothDisabled() { |
| return bluetooth_system_state_ == BluetoothSystemState::kDisabled; |
| } |
| |
| void QuickStartController::OnBluetoothPermissionGranted() { |
| if (controller_state_ != ControllerState::WAITING_FOR_BLUETOOTH_PERMISSION) { |
| return; |
| } |
| |
| controller_state_ = ControllerState::WAITING_FOR_BLUETOOTH_ACTIVATION; |
| |
| if (IsBluetoothDisabled()) { |
| CHECK(cros_bluetooth_config_remote_); |
| cros_bluetooth_config_remote_->SetBluetoothEnabledWithoutPersistence(); |
| // Advertising will start once we are notified that bluetooth is enabled. |
| } |
| } |
| |
| void QuickStartController::StartAdvertising() { |
| QS_LOG(INFO) << "ControllerState::INITIALIZING requesting advertising."; |
| controller_state_ = ControllerState::INITIALIZING; |
| bootstrap_controller_->StartAdvertisingAndMaybeGetQRCode(); |
| } |
| |
| std::ostream& operator<<(std::ostream& stream, |
| const QuickStartController::UiState& ui_state) { |
| switch (ui_state) { |
| case QuickStartController::UiDelegate::UiState::SHOWING_BLUETOOTH_DIALOG: |
| stream << "[showing Bluetooth dialog]"; |
| break; |
| case QuickStartController::UiDelegate::UiState::CONNECTING_TO_PHONE: |
| stream << "[connecting to phone]"; |
| break; |
| case QuickStartController::UiDelegate::UiState::SHOWING_QR: |
| stream << "[showing QR]"; |
| break; |
| case QuickStartController::UiDelegate::UiState::SHOWING_PIN: |
| stream << "[showing PIN]"; |
| break; |
| case QuickStartController::UiDelegate::UiState::CONNECTING_TO_WIFI: |
| stream << "[connecting to WiFi]"; |
| break; |
| case QuickStartController::UiDelegate::UiState::WIFI_CREDENTIALS_RECEIVED: |
| stream << "[WiFi credentials received]"; |
| break; |
| case QuickStartController::UiDelegate::UiState::CONFIRM_GOOGLE_ACCOUNT: |
| stream << "[confirm Google account]"; |
| break; |
| case QuickStartController::UiDelegate::UiState::SIGNING_IN: |
| stream << "[signing in]"; |
| break; |
| case QuickStartController::UiDelegate::UiState::CREATING_ACCOUNT: |
| stream << "[creating account]"; |
| break; |
| case QuickStartController::UiDelegate::UiState::SETUP_COMPLETE: |
| stream << "[setup complete]"; |
| break; |
| case QuickStartController::UiDelegate::UiState::EXIT_SCREEN: |
| stream << "[exit screen]"; |
| break; |
| } |
| |
| return stream; |
| } |
| |
| std::ostream& operator<<( |
| std::ostream& stream, |
| const QuickStartController::AbortFlowReason& abort_flow_reason) { |
| switch (abort_flow_reason) { |
| case QuickStartController::AbortFlowReason::USER_CLICKED_BACK: |
| stream << "[user clicked back]"; |
| break; |
| case QuickStartController::AbortFlowReason::USER_CLICKED_CANCEL: |
| stream << "[user clicked cancel]"; |
| break; |
| case QuickStartController::AbortFlowReason::SIGNIN_SCHOOL: |
| stream << "[signin school]"; |
| break; |
| case QuickStartController::AbortFlowReason::ENTERPRISE_ENROLLMENT: |
| stream << "[enterprise enrollment]"; |
| break; |
| case QuickStartController::AbortFlowReason::QUICK_START_FLOW_COMPLETE: |
| stream << "[Quick Start flow complete]"; |
| break; |
| case QuickStartController::AbortFlowReason::ERROR: |
| stream << "[error]"; |
| break; |
| } |
| |
| return stream; |
| } |
| |
| std::ostream& operator<<( |
| std::ostream& stream, |
| const QuickStartController::ControllerState& controller_state) { |
| switch (controller_state) { |
| case QuickStartController::ControllerState::NOT_ACTIVE: |
| stream << "[not active]"; |
| break; |
| case QuickStartController::ControllerState:: |
| WAITING_FOR_BLUETOOTH_PERMISSION: |
| stream << "[waiting for bluetooth permission]"; |
| break; |
| case QuickStartController::ControllerState:: |
| WAITING_FOR_BLUETOOTH_ACTIVATION: |
| stream << "[waiting for bluetooth activation]"; |
| break; |
| case QuickStartController::ControllerState::WAITING_TO_RESUME_AFTER_UPDATE: |
| stream << "[waiting to resume after update]"; |
| break; |
| case QuickStartController::ControllerState::INITIALIZING: |
| stream << "[initializing]"; |
| break; |
| case QuickStartController::ControllerState::ADVERTISING: |
| stream << "[advertising]"; |
| break; |
| case QuickStartController::ControllerState::CONNECTED: |
| stream << "[connected]"; |
| break; |
| case QuickStartController::ControllerState:: |
| CONTINUING_AFTER_ENROLLMENT_CHECKS: |
| stream << "[continuing after enrollment checks]"; |
| break; |
| case QuickStartController::ControllerState::SETUP_COMPLETE: |
| stream << "[setup complete]"; |
| break; |
| } |
| |
| return stream; |
| } |
| |
| } // namespace ash::quick_start |