| // Copyright 2017 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/tether/tether_service.h" |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/macros.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "chrome/browser/chromeos/cryptauth/chrome_cryptauth_service_factory.h" |
| #include "chrome/browser/chromeos/profiles/profile_helper.h" |
| #include "chrome/browser/chromeos/tether/tether_service_factory.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/ash/network/tether_notification_presenter.h" |
| #include "chrome/common/pref_names.h" |
| #include "chromeos/chromeos_features.h" |
| #include "chromeos/chromeos_switches.h" |
| #include "chromeos/components/proximity_auth/logging/logging.h" |
| #include "chromeos/components/tether/gms_core_notifications_state_tracker_impl.h" |
| #include "chromeos/components/tether/tether_component.h" |
| #include "chromeos/components/tether/tether_component_impl.h" |
| #include "chromeos/components/tether/tether_host_fetcher_impl.h" |
| #include "chromeos/network/device_state.h" |
| #include "chromeos/network/network_connect.h" |
| #include "chromeos/network/network_type_pattern.h" |
| #include "chromeos/services/device_sync/cryptauth_enrollment_manager.h" |
| #include "chromeos/services/multidevice_setup/public/cpp/prefs.h" |
| #include "chromeos/services/secure_channel/public/cpp/client/secure_channel_client.h" |
| #include "components/cryptauth/cryptauth_service.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "components/prefs/pref_service.h" |
| #include "device/bluetooth/bluetooth_adapter_factory.h" |
| |
| namespace { |
| |
| constexpr int64_t kMinAdvertisingIntervalMilliseconds = 100; |
| constexpr int64_t kMaxAdvertisingIntervalMilliseconds = 100; |
| |
| constexpr int64_t kMetricFalsePositiveSeconds = 2; |
| |
| } // namespace |
| |
| // static |
| TetherService* TetherService::Get(Profile* profile) { |
| if (!IsFeatureFlagEnabled()) |
| return nullptr; |
| |
| // Tether networks are only available for the primary user; thus, no |
| // TetherService object should be created for secondary users. If multiple |
| // instances were created for each user, inconsistencies could lead to browser |
| // crashes. See https://crbug.com/809357. |
| if (!chromeos::ProfileHelper::Get()->IsPrimaryProfile(profile)) |
| return nullptr; |
| |
| return TetherServiceFactory::GetForBrowserContext(profile); |
| } |
| |
| // static |
| void TetherService::RegisterProfilePrefs( |
| user_prefs::PrefRegistrySyncable* registry) { |
| // If we initially assume that BLE advertising is not supported, it will |
| // result in Tether's Settings and Quick Settings sections not being visible |
| // when the user logs in with Bluetooth disabled (because the TechnologyState |
| // will be UNAVAILABLE, instead of the desired UNINITIALIZED). |
| // |
| // Initially assuming that BLE advertising *is* supported works well for most |
| // devices, but if a user first logs into a device without BLE advertising |
| // support and with Bluetooth disabled, Tether will be visible in Settings and |
| // Quick Settings, but disappear upon enabling Bluetooth. This is an |
| // acceptable edge case, and likely rare because Bluetooth is enabled by |
| // default on new logins. Additionally, through this pref, we will record if |
| // BLE advertising is not supported and remember that for future logins. |
| registry->RegisterBooleanPref(prefs::kInstantTetheringBleAdvertisingSupported, |
| true); |
| |
| chromeos::tether::TetherComponentImpl::RegisterProfilePrefs(registry); |
| } |
| |
| // static |
| bool TetherService::IsFeatureFlagEnabled() { |
| return base::FeatureList::IsEnabled(chromeos::features::kInstantTethering); |
| } |
| |
| // static. |
| std::string TetherService::TetherFeatureStateToString( |
| const TetherFeatureState& state) { |
| switch (state) { |
| case (TetherFeatureState::SHUT_DOWN): |
| return "[TetherService shut down]"; |
| case (TetherFeatureState::BLE_ADVERTISING_NOT_SUPPORTED): |
| return "[BLE advertising not supported]"; |
| case (TetherFeatureState::NO_AVAILABLE_HOSTS): |
| return "[no potential Tether hosts]"; |
| case (TetherFeatureState::CELLULAR_DISABLED): |
| return "[Cellular setting disabled]"; |
| case (TetherFeatureState::PROHIBITED): |
| return "[prohibited by device policy]"; |
| case (TetherFeatureState::BLUETOOTH_DISABLED): |
| return "[Bluetooth is disabled]"; |
| case (TetherFeatureState::USER_PREFERENCE_DISABLED): |
| return "[Instant Tethering preference is disabled]"; |
| case (TetherFeatureState::ENABLED): |
| return "[Enabled]"; |
| case (TetherFeatureState::BLE_NOT_PRESENT): |
| return "[BLE is not present on the device]"; |
| case (TetherFeatureState::WIFI_NOT_PRESENT): |
| return "[Wi-Fi is not present on the device]"; |
| case (TetherFeatureState::SUSPENDED): |
| return "[Suspended]"; |
| case (TetherFeatureState::BETTER_TOGETHER_SUITE_DISABLED): |
| return "[Better Together suite is disabled]"; |
| case (TetherFeatureState::TETHER_FEATURE_STATE_MAX): |
| // |previous_feature_state_| is initialized to TETHER_FEATURE_STATE_MAX, |
| // and this value is never actually used in practice. |
| return "[TetherService initializing]"; |
| default: |
| NOTREACHED(); |
| return "[Invalid state]"; |
| } |
| } |
| |
| TetherService::TetherService( |
| Profile* profile, |
| chromeos::PowerManagerClient* power_manager_client, |
| cryptauth::CryptAuthService* cryptauth_service, |
| chromeos::device_sync::DeviceSyncClient* device_sync_client, |
| chromeos::secure_channel::SecureChannelClient* secure_channel_client, |
| chromeos::multidevice_setup::MultiDeviceSetupClient* |
| multidevice_setup_client, |
| chromeos::NetworkStateHandler* network_state_handler, |
| session_manager::SessionManager* session_manager) |
| : profile_(profile), |
| power_manager_client_(power_manager_client), |
| cryptauth_service_(cryptauth_service), |
| device_sync_client_(device_sync_client), |
| secure_channel_client_(secure_channel_client), |
| multidevice_setup_client_(multidevice_setup_client), |
| network_state_handler_(network_state_handler), |
| session_manager_(session_manager), |
| notification_presenter_( |
| std::make_unique<chromeos::tether::TetherNotificationPresenter>( |
| profile_, |
| chromeos::NetworkConnect::Get())), |
| gms_core_notifications_state_tracker_( |
| std::make_unique< |
| chromeos::tether::GmsCoreNotificationsStateTrackerImpl>()), |
| tether_host_fetcher_( |
| chromeos::tether::TetherHostFetcherImpl::Factory::NewInstance( |
| device_sync_client_, |
| multidevice_setup_client_)), |
| timer_(std::make_unique<base::OneShotTimer>()), |
| weak_ptr_factory_(this) { |
| tether_host_fetcher_->AddObserver(this); |
| power_manager_client_->AddObserver(this); |
| network_state_handler_->AddObserver(this, FROM_HERE); |
| device_sync_client_->AddObserver(this); |
| multidevice_setup_client_->AddObserver(this); |
| |
| UMA_HISTOGRAM_BOOLEAN("InstantTethering.UserPreference.OnStartup", |
| IsEnabledByPreference()); |
| PA_LOG(VERBOSE) |
| << "TetherService has started. Initial user preference value: " |
| << IsEnabledByPreference(); |
| |
| if (device_sync_client_->is_ready()) |
| OnReady(); |
| |
| // Wait for OnReady() to be called. OnReady() will indirectly |
| // call OnHostStatusChanged(), which will call GetAdapter(). |
| } |
| |
| TetherService::~TetherService() { |
| if (tether_component_) |
| tether_component_->RemoveObserver(this); |
| } |
| |
| void TetherService::StartTetherIfPossible() { |
| if (GetTetherTechnologyState() != |
| chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED) { |
| return; |
| } |
| |
| // Do not initialize the TetherComponent if it already exists. |
| if (tether_component_) |
| return; |
| |
| PA_LOG(VERBOSE) << "Starting up TetherComponent."; |
| tether_component_ = |
| chromeos::tether::TetherComponentImpl::Factory::NewInstance( |
| device_sync_client_, secure_channel_client_, |
| tether_host_fetcher_.get(), notification_presenter_.get(), |
| gms_core_notifications_state_tracker_.get(), profile_->GetPrefs(), |
| network_state_handler_, |
| chromeos::NetworkHandler::Get() |
| ->managed_network_configuration_handler(), |
| chromeos::NetworkConnect::Get(), |
| chromeos::NetworkHandler::Get()->network_connection_handler(), |
| adapter_, session_manager_); |
| } |
| |
| chromeos::tether::GmsCoreNotificationsStateTracker* |
| TetherService::GetGmsCoreNotificationsStateTracker() { |
| return gms_core_notifications_state_tracker_.get(); |
| } |
| |
| void TetherService::StopTetherIfNecessary() { |
| if (!tether_component_ || |
| tether_component_->status() != |
| chromeos::tether::TetherComponent::Status::ACTIVE) { |
| return; |
| } |
| |
| PA_LOG(VERBOSE) << "Shutting down TetherComponent."; |
| |
| chromeos::tether::TetherComponent::ShutdownReason shutdown_reason; |
| switch (GetTetherFeatureState()) { |
| case SHUT_DOWN: |
| shutdown_reason = |
| chromeos::tether::TetherComponent::ShutdownReason::USER_LOGGED_OUT; |
| break; |
| case SUSPENDED: |
| shutdown_reason = |
| chromeos::tether::TetherComponent::ShutdownReason::USER_CLOSED_LID; |
| break; |
| case CELLULAR_DISABLED: |
| shutdown_reason = |
| chromeos::tether::TetherComponent::ShutdownReason::CELLULAR_DISABLED; |
| break; |
| case BLUETOOTH_DISABLED: |
| shutdown_reason = |
| chromeos::tether::TetherComponent::ShutdownReason::BLUETOOTH_DISABLED; |
| break; |
| case USER_PREFERENCE_DISABLED: |
| shutdown_reason = |
| chromeos::tether::TetherComponent::ShutdownReason::PREF_DISABLED; |
| break; |
| case BLE_NOT_PRESENT: |
| shutdown_reason = chromeos::tether::TetherComponent::ShutdownReason:: |
| BLUETOOTH_CONTROLLER_DISAPPEARED; |
| break; |
| case NO_AVAILABLE_HOSTS: |
| // If |tether_component_| was previously active but now has been shut down |
| // due to no longer having a host, this means that the host became |
| // unverified. |
| shutdown_reason = chromeos::tether::TetherComponent::ShutdownReason:: |
| MULTIDEVICE_HOST_UNVERIFIED; |
| break; |
| case BETTER_TOGETHER_SUITE_DISABLED: |
| shutdown_reason = chromeos::tether::TetherComponent::ShutdownReason:: |
| BETTER_TOGETHER_SUITE_DISABLED; |
| break; |
| default: |
| PA_LOG(ERROR) << "Unexpected shutdown reason. FeatureState is " |
| << GetTetherFeatureState() << "."; |
| shutdown_reason = |
| chromeos::tether::TetherComponent::ShutdownReason::OTHER; |
| break; |
| } |
| |
| tether_component_->AddObserver(this); |
| tether_component_->RequestShutdown(shutdown_reason); |
| } |
| |
| void TetherService::Shutdown() { |
| if (shut_down_) |
| return; |
| |
| shut_down_ = true; |
| |
| // Remove all observers. This ensures that once Shutdown() is called, no more |
| // calls to UpdateTetherTechnologyState() will be triggered. |
| tether_host_fetcher_->RemoveObserver(this); |
| power_manager_client_->RemoveObserver(this); |
| network_state_handler_->RemoveObserver(this, FROM_HERE); |
| device_sync_client_->RemoveObserver(this); |
| multidevice_setup_client_->RemoveObserver(this); |
| |
| if (adapter_) |
| adapter_->RemoveObserver(this); |
| |
| // Shut down the feature. Note that this does not change Tether's technology |
| // state in NetworkStateHandler because doing so could cause visual jank just |
| // as the user logs out. |
| StopTetherIfNecessary(); |
| tether_component_.reset(); |
| |
| tether_host_fetcher_.reset(); |
| notification_presenter_.reset(); |
| } |
| |
| void TetherService::SuspendImminent( |
| power_manager::SuspendImminent::Reason reason) { |
| suspended_ = true; |
| UpdateTetherTechnologyState(); |
| } |
| |
| void TetherService::SuspendDone(const base::TimeDelta& sleep_duration) { |
| suspended_ = false; |
| |
| // If there was a previous TetherComponent instance in the process of an |
| // asynchronous shutdown, that session is stale by this point. Kill it now, so |
| // that the next session can start up immediately. |
| if (tether_component_) { |
| tether_component_->RemoveObserver(this); |
| tether_component_.reset(); |
| } |
| |
| UpdateTetherTechnologyState(); |
| } |
| |
| void TetherService::OnTetherHostsUpdated() { |
| UpdateTetherTechnologyState(); |
| } |
| |
| void TetherService::AdapterPoweredChanged(device::BluetoothAdapter* adapter, |
| bool powered) { |
| // Once the BLE advertising interval has been set (regardless of if BLE |
| // advertising is supported), simply update the TechnologyState. |
| if (has_attempted_to_set_ble_advertising_interval_) { |
| UpdateTetherTechnologyState(); |
| return; |
| } |
| |
| // If the BluetoothAdapter was not powered when first fetched (see |
| // OnBluetoothAdapterFetched()), now attempt to set the BLE advertising |
| // interval. |
| if (powered) |
| SetBleAdvertisingInterval(); |
| } |
| |
| void TetherService::DeviceListChanged() { |
| UpdateEnabledState(); |
| } |
| |
| void TetherService::DevicePropertiesUpdated( |
| const chromeos::DeviceState* device) { |
| if (device->Matches(chromeos::NetworkTypePattern::Tether() | |
| chromeos::NetworkTypePattern::WiFi())) { |
| UpdateEnabledState(); |
| } |
| } |
| |
| void TetherService::UpdateEnabledState() { |
| bool was_pref_enabled = IsEnabledByPreference(); |
| chromeos::NetworkStateHandler::TechnologyState tether_technology_state = |
| network_state_handler_->GetTechnologyState( |
| chromeos::NetworkTypePattern::Tether()); |
| |
| // If |was_pref_enabled| differs from the new Tether TechnologyState, the |
| // settings toggle has been changed. Update the kInstantTetheringEnabled user |
| // pref accordingly. |
| bool is_enabled; |
| if (was_pref_enabled && tether_technology_state == |
| chromeos::NetworkStateHandler::TechnologyState:: |
| TECHNOLOGY_AVAILABLE) { |
| is_enabled = false; |
| } else if (!was_pref_enabled && tether_technology_state == |
| chromeos::NetworkStateHandler:: |
| TechnologyState::TECHNOLOGY_ENABLED) { |
| is_enabled = true; |
| } else { |
| is_enabled = was_pref_enabled; |
| } |
| |
| if (is_enabled != was_pref_enabled) { |
| if (base::FeatureList::IsEnabled( |
| chromeos::features::kEnableUnifiedMultiDeviceSetup)) { |
| multidevice_setup_client_->SetFeatureEnabledState( |
| chromeos::multidevice_setup::mojom::Feature::kInstantTethering, |
| is_enabled, base::nullopt /* auth_token */, base::DoNothing()); |
| } else { |
| profile_->GetPrefs()->SetBoolean( |
| chromeos::multidevice_setup::kInstantTetheringEnabledPrefName, |
| is_enabled); |
| LogUserPreferenceChanged(is_enabled); |
| UpdateTetherTechnologyState(); |
| } |
| } else { |
| UpdateTetherTechnologyState(); |
| } |
| } |
| |
| void TetherService::OnShutdownComplete() { |
| DCHECK(tether_component_->status() == |
| chromeos::tether::TetherComponent::Status::SHUT_DOWN); |
| tether_component_->RemoveObserver(this); |
| tether_component_.reset(); |
| PA_LOG(VERBOSE) << "TetherComponent was shut down."; |
| |
| // It is possible that the Tether TechnologyState was set to ENABLED while the |
| // previous TetherComponent instance was shutting down. If that was the case, |
| // restart TetherComponent. |
| if (!shut_down_) |
| StartTetherIfPossible(); |
| } |
| |
| void TetherService::OnReady() { |
| if (shut_down_) |
| return; |
| |
| if (base::FeatureList::IsEnabled( |
| chromeos::features::kEnableUnifiedMultiDeviceSetup)) { |
| OnFeatureStatesChanged(multidevice_setup_client_->GetFeatureStates()); |
| } else { |
| GetBluetoothAdapter(); |
| } |
| } |
| |
| void TetherService::OnFeatureStatesChanged( |
| const chromeos::multidevice_setup::MultiDeviceSetupClient::FeatureStatesMap& |
| feature_states_map) { |
| const chromeos::multidevice_setup::mojom::FeatureState new_state = |
| feature_states_map |
| .find(chromeos::multidevice_setup::mojom::Feature::kInstantTethering) |
| ->second; |
| |
| // If the feature changed from enabled to disabled or vice-versa, log the |
| // associated metric. |
| if (new_state == |
| chromeos::multidevice_setup::mojom::FeatureState::kEnabledByUser && |
| previous_feature_state_ == TetherFeatureState::USER_PREFERENCE_DISABLED) { |
| LogUserPreferenceChanged(true /* is_now_enabled */); |
| } else if (new_state == chromeos::multidevice_setup::mojom::FeatureState:: |
| kDisabledByUser && |
| previous_feature_state_ == TetherFeatureState::ENABLED) { |
| LogUserPreferenceChanged(false /* is_now_enabled */); |
| } |
| |
| if (adapter_) |
| UpdateTetherTechnologyState(); |
| else |
| GetBluetoothAdapter(); |
| } |
| |
| bool TetherService::HasSyncedTetherHosts() const { |
| return tether_host_fetcher_->HasSyncedTetherHosts(); |
| } |
| |
| void TetherService::UpdateTetherTechnologyState() { |
| if (!adapter_) |
| return; |
| |
| chromeos::NetworkStateHandler::TechnologyState new_tether_technology_state = |
| GetTetherTechnologyState(); |
| |
| if (new_tether_technology_state == |
| chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED) { |
| // If Tether should be enabled, notify NetworkStateHandler before starting |
| // up the component. This ensures that it is not possible to add Tether |
| // networks before the network stack is ready for them. |
| network_state_handler_->SetTetherTechnologyState( |
| new_tether_technology_state); |
| StartTetherIfPossible(); |
| } else { |
| // If Tether should not be enabled, shut down the component before notifying |
| // NetworkStateHandler. This ensures that nothing in TetherComponent |
| // attempts to edit Tether networks or properties when the network stack is |
| // not ready for them. |
| StopTetherIfNecessary(); |
| network_state_handler_->SetTetherTechnologyState( |
| new_tether_technology_state); |
| } |
| } |
| |
| chromeos::NetworkStateHandler::TechnologyState |
| TetherService::GetTetherTechnologyState() { |
| TetherFeatureState new_feature_state = GetTetherFeatureState(); |
| if (new_feature_state != previous_feature_state_) { |
| PA_LOG(INFO) << "Tether state has changed. New state: " |
| << TetherFeatureStateToString(new_feature_state) |
| << ", Old state: " |
| << TetherFeatureStateToString(previous_feature_state_); |
| previous_feature_state_ = new_feature_state; |
| |
| RecordTetherFeatureStateIfPossible(); |
| } |
| |
| switch (new_feature_state) { |
| case SHUT_DOWN: |
| case SUSPENDED: |
| case BLE_NOT_PRESENT: |
| case BLE_ADVERTISING_NOT_SUPPORTED: |
| case WIFI_NOT_PRESENT: |
| case NO_AVAILABLE_HOSTS: |
| case CELLULAR_DISABLED: |
| case BETTER_TOGETHER_SUITE_DISABLED: |
| return chromeos::NetworkStateHandler::TechnologyState:: |
| TECHNOLOGY_UNAVAILABLE; |
| |
| case PROHIBITED: |
| return chromeos::NetworkStateHandler::TechnologyState:: |
| TECHNOLOGY_PROHIBITED; |
| |
| case BLUETOOTH_DISABLED: |
| return chromeos::NetworkStateHandler::TechnologyState:: |
| TECHNOLOGY_UNINITIALIZED; |
| |
| case USER_PREFERENCE_DISABLED: |
| return chromeos::NetworkStateHandler::TechnologyState:: |
| TECHNOLOGY_AVAILABLE; |
| |
| case ENABLED: |
| return chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED; |
| |
| default: |
| return chromeos::NetworkStateHandler::TechnologyState:: |
| TECHNOLOGY_UNAVAILABLE; |
| } |
| } |
| |
| void TetherService::GetBluetoothAdapter() { |
| if (adapter_ || is_adapter_being_fetched_) |
| return; |
| |
| is_adapter_being_fetched_ = true; |
| |
| // In the case that this is indirectly called from the constructor, |
| // GetAdapter() may call OnBluetoothAdapterFetched immediately which can cause |
| // problems with the Fake implementation since the class is not fully |
| // constructed yet. Post the GetAdapter call to avoid this. |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(device::BluetoothAdapterFactory::GetAdapter, |
| base::BindRepeating( |
| &TetherService::OnBluetoothAdapterFetched, |
| weak_ptr_factory_.GetWeakPtr()))); |
| } |
| |
| void TetherService::OnBluetoothAdapterFetched( |
| scoped_refptr<device::BluetoothAdapter> adapter) { |
| is_adapter_being_fetched_ = false; |
| |
| if (shut_down_) |
| return; |
| |
| adapter_ = adapter; |
| adapter_->AddObserver(this); |
| |
| // Update TechnologyState in case Tether is otherwise available but Bluetooth |
| // is off. |
| UpdateTetherTechnologyState(); |
| |
| // If |adapter_| is not powered, wait until it is to call |
| // SetBleAdvertisingInterval(). See AdapterPoweredChanged(). |
| if (IsBluetoothPowered()) |
| SetBleAdvertisingInterval(); |
| } |
| |
| void TetherService::OnBluetoothAdapterAdvertisingIntervalSet() { |
| has_attempted_to_set_ble_advertising_interval_ = true; |
| SetIsBleAdvertisingSupportedPref(true); |
| |
| UpdateTetherTechnologyState(); |
| } |
| |
| void TetherService::OnBluetoothAdapterAdvertisingIntervalError( |
| device::BluetoothAdvertisement::ErrorCode status) { |
| has_attempted_to_set_ble_advertising_interval_ = true; |
| SetIsBleAdvertisingSupportedPref(false); |
| |
| UpdateTetherTechnologyState(); |
| } |
| |
| void TetherService::SetBleAdvertisingInterval() { |
| DCHECK(IsBluetoothPowered()); |
| adapter_->SetAdvertisingInterval( |
| base::TimeDelta::FromMilliseconds(kMinAdvertisingIntervalMilliseconds), |
| base::TimeDelta::FromMilliseconds(kMaxAdvertisingIntervalMilliseconds), |
| base::Bind(&TetherService::OnBluetoothAdapterAdvertisingIntervalSet, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::Bind(&TetherService::OnBluetoothAdapterAdvertisingIntervalError, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| bool TetherService::GetIsBleAdvertisingSupportedPref() { |
| return profile_->GetPrefs()->GetBoolean( |
| prefs::kInstantTetheringBleAdvertisingSupported); |
| } |
| |
| void TetherService::SetIsBleAdvertisingSupportedPref( |
| bool is_ble_advertising_supported) { |
| profile_->GetPrefs()->SetBoolean( |
| prefs::kInstantTetheringBleAdvertisingSupported, |
| is_ble_advertising_supported); |
| } |
| |
| bool TetherService::IsBluetoothPresent() const { |
| return adapter_.get() && adapter_->IsPresent(); |
| } |
| |
| bool TetherService::IsBluetoothPowered() const { |
| return IsBluetoothPresent() && adapter_->IsPowered(); |
| } |
| |
| bool TetherService::IsWifiPresent() const { |
| return network_state_handler_->IsTechnologyAvailable( |
| chromeos::NetworkTypePattern::WiFi()); |
| } |
| |
| bool TetherService::IsCellularAvailableButNotEnabled() const { |
| return (network_state_handler_->IsTechnologyAvailable( |
| chromeos::NetworkTypePattern::Cellular()) && |
| !network_state_handler_->IsTechnologyEnabled( |
| chromeos::NetworkTypePattern::Cellular())); |
| } |
| |
| bool TetherService::IsAllowedByPolicy() const { |
| return profile_->GetPrefs()->GetBoolean( |
| chromeos::multidevice_setup::kInstantTetheringAllowedPrefName); |
| } |
| |
| bool TetherService::IsEnabledByPreference() const { |
| return profile_->GetPrefs()->GetBoolean( |
| chromeos::multidevice_setup::kInstantTetheringEnabledPrefName); |
| } |
| |
| TetherService::TetherFeatureState TetherService::GetTetherFeatureState() { |
| if (shut_down_) |
| return SHUT_DOWN; |
| |
| if (suspended_) |
| return SUSPENDED; |
| |
| if (!IsBluetoothPresent()) |
| return BLE_NOT_PRESENT; |
| |
| if (!IsWifiPresent()) |
| return WIFI_NOT_PRESENT; |
| |
| if (!GetIsBleAdvertisingSupportedPref()) |
| return BLE_ADVERTISING_NOT_SUPPORTED; |
| |
| if (!HasSyncedTetherHosts()) |
| return NO_AVAILABLE_HOSTS; |
| |
| // If Cellular technology is available, then Tether technology is treated |
| // as a subset of Cellular, and it should only be enabled when Cellular |
| // technology is enabled. |
| if (IsCellularAvailableButNotEnabled()) |
| return CELLULAR_DISABLED; |
| |
| if (!IsBluetoothPowered()) |
| return BLUETOOTH_DISABLED; |
| |
| // For the cases below, the state is computed differently depending on whether |
| // the MultiDeviceSetup service is active. |
| chromeos::multidevice_setup::mojom::FeatureState tether_multidevice_state = |
| multidevice_setup_client_->GetFeatureState( |
| chromeos::multidevice_setup::mojom::Feature::kInstantTethering); |
| switch (tether_multidevice_state) { |
| case chromeos::multidevice_setup::mojom::FeatureState::kProhibitedByPolicy: |
| return PROHIBITED; |
| case chromeos::multidevice_setup::mojom::FeatureState::kDisabledByUser: |
| return USER_PREFERENCE_DISABLED; |
| case chromeos::multidevice_setup::mojom::FeatureState::kEnabledByUser: |
| return ENABLED; |
| case chromeos::multidevice_setup::mojom::FeatureState:: |
| kUnavailableSuiteDisabled: |
| return BETTER_TOGETHER_SUITE_DISABLED; |
| case chromeos::multidevice_setup::mojom::FeatureState:: |
| kUnavailableNoVerifiedHost: |
| // Note that because of the early return above after |
| // !HasSyncedTetherHosts, if this point is hit, there are synced tether |
| // hosts available, but the multidevice state is unverified. This switch |
| // case can only occur for legacy Magic Tether hosts, in which case the |
| // service should be enabled. |
| // TODO(crbug.com/894585): Remove this legacy special case after M71. |
| return ENABLED; |
| case chromeos::multidevice_setup::mojom::FeatureState:: |
| kNotSupportedByChromebook: |
| // CryptAuth may not yet know that this device supports |
| // MAGIC_TETHER_CLIENT (and the local device metadata is reflecting |
| // that). This should be resolved shortly once DeviceReenroller realizes |
| // reconciles the discrepancy. For now, fall through to mark as |
| // unavailable. |
| FALLTHROUGH; |
| case chromeos::multidevice_setup::mojom::FeatureState::kNotSupportedByPhone: |
| return NO_AVAILABLE_HOSTS; |
| default: |
| // Other FeatureStates: |
| // *kUnavailableInsufficientSecurity: Should never occur. |
| PA_LOG(ERROR) << "Invalid MultiDevice FeatureState: " |
| << tether_multidevice_state; |
| NOTREACHED(); |
| return NO_AVAILABLE_HOSTS; |
| } |
| |
| if (!IsAllowedByPolicy()) |
| return PROHIBITED; |
| |
| if (!IsEnabledByPreference()) |
| return USER_PREFERENCE_DISABLED; |
| |
| return ENABLED; |
| } |
| |
| void TetherService::RecordTetherFeatureState() { |
| TetherFeatureState tether_feature_state = GetTetherFeatureState(); |
| DCHECK(tether_feature_state != TetherFeatureState::TETHER_FEATURE_STATE_MAX); |
| |
| // If the feature is shut down, there is no need to log a metric. Since this |
| // state occurs every time the user logs out (as of crbug.com/782879), logging |
| // a metric here does not provide any value since it does not indicate |
| // anything about how the user utilizes Instant Tethering and would dilute the |
| // contributions of meaningful states. |
| if (tether_feature_state == TetherFeatureState::SHUT_DOWN) |
| return; |
| |
| UMA_HISTOGRAM_ENUMERATION("InstantTethering.FeatureState", |
| tether_feature_state, |
| TetherFeatureState::TETHER_FEATURE_STATE_MAX); |
| } |
| |
| void TetherService::RecordTetherFeatureStateIfPossible() { |
| if (HandleFeatureStateMetricIfUninitialized()) |
| return; |
| |
| // If the timer meant to record the initial |
| // TetherFeatureState::BLE_NOT_PRESENT value is running, cancel it -- it is a |
| // false positive report. |
| if (timer_->IsRunning()) |
| timer_->Stop(); |
| |
| RecordTetherFeatureState(); |
| } |
| |
| bool TetherService::HandleFeatureStateMetricIfUninitialized() { |
| TetherFeatureState current_state = GetTetherFeatureState(); |
| if (current_state != TetherFeatureState::BLE_NOT_PRESENT && |
| current_state != TetherFeatureState::NO_AVAILABLE_HOSTS) { |
| // These are the only two possible false-positive states. If the current |
| // state is another state, no processing is needed. |
| return false; |
| } |
| |
| // When TetherService starts up, we expect that BLE is not present. |
| // Eventually, BLE starts up, but at that point, Tether hosts are not yet |
| // fetched. During startup, these states are transient, so it would be |
| // incorrect to log a metric stating that this was the state upon startup. |
| bool should_start_timer = false; |
| if (current_state == TetherFeatureState::BLE_NOT_PRESENT && |
| !ble_not_present_false_positive_encountered_) { |
| ble_not_present_false_positive_encountered_ = true; |
| should_start_timer = true; |
| } else if (current_state == TetherFeatureState::NO_AVAILABLE_HOSTS && |
| !no_available_hosts_false_positive_encountered_) { |
| no_available_hosts_false_positive_encountered_ = true; |
| should_start_timer = true; |
| } |
| |
| if (!should_start_timer) |
| return false; |
| |
| // Start the timer. If it fires without being stopped, the metric will be |
| // recorded. |kMetricFalsePositiveSeconds| is chosen such that it is long |
| // enough that we can assume a false positive did not occur and that the |
| // metric value is actually correct. |
| timer_->Start(FROM_HERE, |
| base::TimeDelta::FromSeconds(kMetricFalsePositiveSeconds), |
| base::BindRepeating(&TetherService::RecordTetherFeatureState, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| return true; |
| } |
| |
| void TetherService::LogUserPreferenceChanged(bool is_now_enabled) { |
| UMA_HISTOGRAM_BOOLEAN("InstantTethering.UserPreference.OnToggle", |
| is_now_enabled); |
| PA_LOG(VERBOSE) << "Tether user preference changed. New value: " |
| << is_now_enabled; |
| } |
| |
| void TetherService::SetTestDoubles( |
| std::unique_ptr<chromeos::tether::NotificationPresenter> |
| notification_presenter, |
| std::unique_ptr<base::OneShotTimer> timer) { |
| notification_presenter_ = std::move(notification_presenter); |
| timer_ = std::move(timer); |
| } |