Commit d5b3aa0d authored by Kyle Horimoto's avatar Kyle Horimoto Committed by Commit Bot

[CrOS PhoneHub] Add FeatureStatus enum and FeatureStatusProvider

This CL creates the //chromeos/components/phonehub/ directory and
introduces a new class which provides the status of the Phone Hub
feature.

Currently, this new class is unit tested but not actually instantiated
in real code yet. A future CL will create a KeyedService and initialize
the class when the KeyedService starts up.

Bug: 1106937
Change-Id: I27f46f62d3246149d0cef53e4ea01dd41bb879e4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2345665
Auto-Submit: Kyle Horimoto <khorimoto@chromium.org>
Reviewed-by: default avatarJosh Nohle <nohle@chromium.org>
Reviewed-by: default avatarReilly Grant <reillyg@chromium.org>
Commit-Queue: Kyle Horimoto <khorimoto@chromium.org>
Cr-Commit-Position: refs/heads/master@{#796811}
parent 67c6505b
...@@ -22,6 +22,7 @@ test("chromeos_components_unittests") { ...@@ -22,6 +22,7 @@ test("chromeos_components_unittests") {
"//chromeos/components/local_search_service/mojom:unit_tests", "//chromeos/components/local_search_service/mojom:unit_tests",
"//chromeos/components/mojo_bootstrap:unit_tests", "//chromeos/components/mojo_bootstrap:unit_tests",
"//chromeos/components/multidevice:unit_tests", "//chromeos/components/multidevice:unit_tests",
"//chromeos/components/phonehub:unit_tests",
"//chromeos/components/power:unit_tests", "//chromeos/components/power:unit_tests",
"//chromeos/components/proximity_auth:unit_tests", "//chromeos/components/proximity_auth:unit_tests",
"//chromeos/components/quick_answers:unit_tests", "//chromeos/components/quick_answers:unit_tests",
......
# Copyright 2020 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.
import("//testing/test.gni")
assert(is_chromeos, "Phone Hub is Chrome OS only")
static_library("phonehub") {
sources = [
"feature_status.cc",
"feature_status.h",
"feature_status_provider.cc",
"feature_status_provider.h",
"feature_status_provider_impl.cc",
"feature_status_provider_impl.h",
]
deps = [
"//base",
"//chromeos/components/multidevice",
"//chromeos/components/multidevice/logging",
"//chromeos/services/device_sync/public/cpp",
"//chromeos/services/multidevice_setup/public/cpp",
"//device/bluetooth",
]
}
static_library("test_support") {
testonly = true
sources = [
"fake_feature_status_provider.cc",
"fake_feature_status_provider.h",
]
public_deps = [ ":phonehub" ]
deps = [ "//base" ]
}
source_set("unit_tests") {
testonly = true
sources = [ "feature_status_provider_impl_unittest.cc" ]
deps = [
":phonehub",
":test_support",
"//base",
"//base/test:test_support",
"//chromeos/components/multidevice",
"//chromeos/components/multidevice:test_support",
"//chromeos/services/device_sync/public/cpp",
"//chromeos/services/device_sync/public/cpp:test_support",
"//chromeos/services/multidevice_setup/public/cpp",
"//chromeos/services/multidevice_setup/public/cpp:test_support",
"//device/bluetooth:mocks",
"//testing/gtest",
]
}
include_rules = [
"+chromeos/components/multidevice",
"+chromeos/services/device_sync/public/cpp",
"+chromeos/services/multidevice_setup/public/cpp",
"+device/bluetooth",
]
// Copyright 2020 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 "chromeos/components/phonehub/fake_feature_status_provider.h"
namespace chromeos {
namespace phonehub {
FakeFeatureStatusProvider::FakeFeatureStatusProvider()
: FakeFeatureStatusProvider(FeatureStatus::kEnabledAndConnected) {}
FakeFeatureStatusProvider::FakeFeatureStatusProvider(
FeatureStatus initial_status)
: status_(initial_status) {}
FakeFeatureStatusProvider::~FakeFeatureStatusProvider() = default;
void FakeFeatureStatusProvider::SetStatus(FeatureStatus status) {
if (status == status_)
return;
status_ = status;
NotifyStatusChanged();
}
FeatureStatus FakeFeatureStatusProvider::GetStatus() const {
return status_;
}
} // namespace phonehub
} // namespace chromeos
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMEOS_COMPONENTS_PHONEHUB_FAKE_FEATURE_STATUS_PROVIDER_H_
#define CHROMEOS_COMPONENTS_PHONEHUB_FAKE_FEATURE_STATUS_PROVIDER_H_
#include "chromeos/components/phonehub/feature_status_provider.h"
namespace chromeos {
namespace phonehub {
class FakeFeatureStatusProvider : public FeatureStatusProvider {
public:
// Defaults initial status to kEnabledAndConnected.
FakeFeatureStatusProvider();
FakeFeatureStatusProvider(FeatureStatus initial_status);
~FakeFeatureStatusProvider() override;
void SetStatus(FeatureStatus status);
// FeatureStatusProvider:
FeatureStatus GetStatus() const override;
private:
FeatureStatus status_;
};
} // namespace phonehub
} // namespace chromeos
#endif // CHROMEOS_COMPONENTS_PHONEHUB_FAKE_FEATURE_STATUS_PROVIDER_H_
// Copyright 2020 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 "chromeos/components/phonehub/feature_status.h"
namespace chromeos {
namespace phonehub {
std::ostream& operator<<(std::ostream& stream, FeatureStatus status) {
switch (status) {
case FeatureStatus::kNotEligibleForFeature:
stream << "[Not eligible for feature]";
break;
case FeatureStatus::kEligiblePhoneButNotSetUp:
stream << "[Eligible phone but not set up]";
break;
case FeatureStatus::kPhoneSelectedAndPendingSetup:
stream << "[Phone selected and pending setup]";
break;
case FeatureStatus::kProhibitedByPolicy:
stream << "[Prohibited by policy]";
break;
case FeatureStatus::kDisabled:
stream << "[Disabled]";
break;
case FeatureStatus::kUnavailableBluetoothOff:
stream << "[Unavailable; Bluetooth off]";
break;
case FeatureStatus::kEnabledButDisconnected:
stream << "[Enabled; disconnected]";
break;
case FeatureStatus::kEnabledAndConnecting:
stream << "[Enabled; connecting]";
break;
case FeatureStatus::kEnabledAndConnected:
stream << "[Enabled; connected]";
break;
}
return stream;
}
} // namespace phonehub
} // namespace chromeos
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMEOS_COMPONENTS_PHONEHUB_FEATURE_STATUS_H_
#define CHROMEOS_COMPONENTS_PHONEHUB_FEATURE_STATUS_H_
#include <ostream>
namespace chromeos {
namespace phonehub {
// Enum representing potential status values for the Phone Hub feature.
enum class FeatureStatus {
// The user's devices are not eligible for the feature. This means that either
// the Chrome OS device or the user's phone (or both) have not enrolled with
// the requisite feature enum values.
kNotEligibleForFeature = 0,
// The user has a phone eligible for the feature, but they have not yet
// started the opt-in flow.
kEligiblePhoneButNotSetUp = 1,
// The user has selected a phone in the opt-in flow, but setup is not yet
// complete. Note that setting up the feature requires interaction with a
// server and with the phone itself.
kPhoneSelectedAndPendingSetup = 2,
// An enterprise policy has prohibited this feature from running.
kProhibitedByPolicy = 3,
// The feature is disabled, but the user could enable it via settings.
kDisabled = 4,
// The feature is enabled, but it is currently unavailable because Bluetooth
// is disabled (the feature cannot run without Bluetooth).
kUnavailableBluetoothOff = 5,
// The feature is enabled, but currently there is no active connection to
// the phone.
kEnabledButDisconnected = 6,
// The feature is enabled, and there is an active attempt to connect to the
// phone.
kEnabledAndConnecting = 7,
// The feature is enabled, and there is an active connection with the phone.
kEnabledAndConnected = 8
};
std::ostream& operator<<(std::ostream& stream, FeatureStatus status);
} // namespace phonehub
} // namespace chromeos
#endif // CHROMEOS_CO MPONENTS_PHONEHUB_FEATURE_STATUS_H_
// Copyright 2020 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 "chromeos/components/phonehub/feature_status_provider.h"
namespace chromeos {
namespace phonehub {
FeatureStatusProvider::FeatureStatusProvider() = default;
FeatureStatusProvider::~FeatureStatusProvider() = default;
void FeatureStatusProvider::AddObserver(Observer* observer) {
observer_list_.AddObserver(observer);
}
void FeatureStatusProvider::RemoveObserver(Observer* observer) {
observer_list_.RemoveObserver(observer);
}
void FeatureStatusProvider::NotifyStatusChanged() {
for (auto& observer : observer_list_)
observer.OnStatusChanged();
}
} // namespace phonehub
} // namespace chromeos
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMEOS_COMPONENTS_PHONEHUB_FEATURE_STATUS_PROVIDER_H_
#define CHROMEOS_COMPONENTS_PHONEHUB_FEATURE_STATUS_PROVIDER_H_
#include "base/observer_list.h"
#include "base/observer_list_types.h"
#include "chromeos/components/phonehub/feature_status.h"
namespace chromeos {
namespace phonehub {
// Provides the current status of Phone Hub and notifies observers when the
// status changes.
class FeatureStatusProvider {
public:
class Observer : public base::CheckedObserver {
public:
~Observer() override = default;
// Called when the status has changed; use GetStatus() for the new status.
virtual void OnStatusChanged() = 0;
};
FeatureStatusProvider(const FeatureStatusProvider&) = delete;
FeatureStatusProvider& operator=(const FeatureStatusProvider&) = delete;
virtual ~FeatureStatusProvider();
virtual FeatureStatus GetStatus() const = 0;
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
protected:
FeatureStatusProvider();
void NotifyStatusChanged();
private:
base::ObserverList<Observer> observer_list_;
};
} // namespace phonehub
} // namespace chromeos
#endif // CHROMEOS_COMPONENTS_PHONEHUB_FEATURE_STATUS_PROVIDER_H_
// Copyright 2020 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 "chromeos/components/phonehub/feature_status_provider_impl.h"
#include <utility>
#include "base/bind.h"
#include "chromeos/components/multidevice/logging/logging.h"
#include "chromeos/components/multidevice/remote_device_ref.h"
#include "chromeos/components/multidevice/software_feature.h"
#include "chromeos/components/multidevice/software_feature_state.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
namespace chromeos {
namespace phonehub {
namespace {
using multidevice::RemoteDeviceRef;
using multidevice::RemoteDeviceRefList;
using multidevice::SoftwareFeature;
using multidevice::SoftwareFeatureState;
using multidevice_setup::mojom::Feature;
using multidevice_setup::mojom::FeatureState;
using multidevice_setup::mojom::HostStatus;
bool IsEligibleForFeature(
const base::Optional<multidevice::RemoteDeviceRef>& local_device,
const RemoteDeviceRefList& remote_devices) {
// If the local device has not yet been enrolled, no phone can serve as its
// Phone Hub host.
if (!local_device)
return false;
// If the local device does not support being a Phone Hub client, no phone can
// serve as its host.
if (local_device->GetSoftwareFeatureState(SoftwareFeature::kPhoneHubClient) ==
SoftwareFeatureState::kNotSupported) {
return false;
}
// If the local device does not have an enrolled Bluetooth address, no phone
// can serve as its host.
if (local_device->bluetooth_public_address().empty())
return false;
for (const RemoteDeviceRef& device : remote_devices) {
// Device must be capable of being a multi-device host.
if (device.GetSoftwareFeatureState(SoftwareFeature::kBetterTogetherHost) ==
SoftwareFeatureState::kNotSupported) {
continue;
}
// Device must be capable of being a Phone Hub host.
if (device.GetSoftwareFeatureState(SoftwareFeature::kPhoneHubHost) ==
SoftwareFeatureState::kNotSupported) {
continue;
}
// Device must have a synced Bluetooth public address, which is used to
// bootstrap Phone Hub connections.
if (device.bluetooth_public_address().empty())
continue;
return true;
};
// If none of the devices return true above, there are no phones capable of
// Phone Hub connections on the account.
return false;
}
bool IsPhonePendingSetup(HostStatus host_status, FeatureState feature_state) {
// The user has completed the opt-in flow, but we have not yet notified the
// back-end of this selection. One common cause of this state is when the user
// completes setup while offline.
if (host_status ==
HostStatus::kHostSetLocallyButWaitingForBackendConfirmation) {
return true;
}
// The device has been set up with the back-end, but the phone has not yet
// enabled itself.
if (host_status == HostStatus::kHostSetButNotYetVerified)
return true;
// The phone has enabled itself for the multi-device suite but has not yet
// enabled itself for Phone Hub. Note that kNotSupportedByPhone is a bit of a
// misnomer here; this value means that the phone has advertised support for
// the feature but has not yet enabled it.
return host_status == HostStatus::kHostVerified &&
feature_state == FeatureState::kNotSupportedByPhone;
}
bool IsFeatureDisabledByUser(FeatureState feature_state) {
return feature_state == FeatureState::kDisabledByUser ||
feature_state == FeatureState::kUnavailableSuiteDisabled ||
feature_state == FeatureState::kUnavailableTopLevelFeatureDisabled;
}
} // namespace
FeatureStatusProviderImpl::FeatureStatusProviderImpl(
device_sync::DeviceSyncClient* device_sync_client,
multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client)
: device_sync_client_(device_sync_client),
multidevice_setup_client_(multidevice_setup_client) {
device_sync_client_->AddObserver(this);
multidevice_setup_client_->AddObserver(this);
device::BluetoothAdapterFactory::Get()->GetAdapter(
base::BindOnce(&FeatureStatusProviderImpl::OnBluetoothAdapterReceived,
weak_ptr_factory_.GetWeakPtr()));
status_ = ComputeStatus();
}
FeatureStatusProviderImpl::~FeatureStatusProviderImpl() {
device_sync_client_->RemoveObserver(this);
multidevice_setup_client_->RemoveObserver(this);
if (bluetooth_adapter_)
bluetooth_adapter_->RemoveObserver(this);
}
FeatureStatus FeatureStatusProviderImpl::GetStatus() const {
return *status_;
}
void FeatureStatusProviderImpl::OnReady() {
UpdateStatus();
}
void FeatureStatusProviderImpl::OnNewDevicesSynced() {
UpdateStatus();
}
void FeatureStatusProviderImpl::OnHostStatusChanged(
const multidevice_setup::MultiDeviceSetupClient::HostStatusWithDevice&
host_device_with_status) {
UpdateStatus();
}
void FeatureStatusProviderImpl::OnFeatureStatesChanged(
const multidevice_setup::MultiDeviceSetupClient::FeatureStatesMap&
feature_states_map) {
UpdateStatus();
}
void FeatureStatusProviderImpl::AdapterPresentChanged(
device::BluetoothAdapter* adapter,
bool present) {
UpdateStatus();
}
void FeatureStatusProviderImpl::AdapterPoweredChanged(
device::BluetoothAdapter* adapter,
bool powered) {
UpdateStatus();
}
void FeatureStatusProviderImpl::OnBluetoothAdapterReceived(
scoped_refptr<device::BluetoothAdapter> bluetooth_adapter) {
bluetooth_adapter_ = std::move(bluetooth_adapter);
bluetooth_adapter_->AddObserver(this);
// If |status_| has not yet been set, this call occurred synchronously in the
// constructor, so status_ has not yet been initialized.
if (status_.has_value())
UpdateStatus();
}
void FeatureStatusProviderImpl::UpdateStatus() {
DCHECK(status_.has_value());
FeatureStatus computed_status = ComputeStatus();
if (computed_status == *status_)
return;
PA_LOG(INFO) << "Phone Hub feature status: " << *status_ << " => "
<< computed_status;
*status_ = computed_status;
NotifyStatusChanged();
}
FeatureStatus FeatureStatusProviderImpl::ComputeStatus() {
if (!IsEligibleForFeature(device_sync_client_->GetLocalDeviceMetadata(),
device_sync_client_->GetSyncedDevices())) {
return FeatureStatus::kNotEligibleForFeature;
}
HostStatus host_status = multidevice_setup_client_->GetHostStatus().first;
if (host_status == HostStatus::kEligibleHostExistsButNoHostSet)
return FeatureStatus::kEligiblePhoneButNotSetUp;
FeatureState feature_state =
multidevice_setup_client_->GetFeatureState(Feature::kPhoneHub);
if (IsPhonePendingSetup(host_status, feature_state))
return FeatureStatus::kPhoneSelectedAndPendingSetup;
if (feature_state == FeatureState::kProhibitedByPolicy)
return FeatureStatus::kProhibitedByPolicy;
if (IsFeatureDisabledByUser(feature_state))
return FeatureStatus::kDisabled;
if (!IsBluetoothOn())
return FeatureStatus::kUnavailableBluetoothOff;
// TODO(khorimoto): Return different statuses based on whether we have an
// active connection.
return FeatureStatus::kEnabledButDisconnected;
}
bool FeatureStatusProviderImpl::IsBluetoothOn() const {
if (!bluetooth_adapter_)
return false;
return bluetooth_adapter_->IsPresent() && bluetooth_adapter_->IsPowered();
}
} // namespace phonehub
} // namespace chromeos
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMEOS_COMPONENTS_PHONEHUB_FEATURE_STATUS_PROVIDER_IMPL_H_
#define CHROMEOS_COMPONENTS_PHONEHUB_FEATURE_STATUS_PROVIDER_IMPL_H_
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "chromeos/components/phonehub/feature_status_provider.h"
#include "chromeos/services/device_sync/public/cpp/device_sync_client.h"
#include "chromeos/services/multidevice_setup/public/cpp/multidevice_setup_client.h"
#include "device/bluetooth/bluetooth_adapter.h"
namespace chromeos {
namespace phonehub {
// FeatureStatusProvider implementation which utilizes DeviceSyncClient,
// MultiDeviceSetupClient and BluetoothAdapter to determine the current status.
// TODO(khorimoto): Add metrics for initial status and status changes.
class FeatureStatusProviderImpl
: public FeatureStatusProvider,
public device_sync::DeviceSyncClient::Observer,
public multidevice_setup::MultiDeviceSetupClient::Observer,
public device::BluetoothAdapter::Observer {
public:
FeatureStatusProviderImpl(
device_sync::DeviceSyncClient* device_sync_client,
multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client);
~FeatureStatusProviderImpl() override;
private:
friend class FeatureStatusProviderImplTest;
// FeatureStatusProvider:
FeatureStatus GetStatus() const override;
// device_sync::DeviceSyncClient::Observer:
void OnReady() override;
void OnNewDevicesSynced() override;
// multidevice_setup::MultiDeviceSetupClient::Observer:
void OnHostStatusChanged(
const multidevice_setup::MultiDeviceSetupClient::HostStatusWithDevice&
host_device_with_status) override;
void OnFeatureStatesChanged(
const multidevice_setup::MultiDeviceSetupClient::FeatureStatesMap&
feature_states_map) override;
void OnBluetoothAdapterReceived(
scoped_refptr<device::BluetoothAdapter> bluetooth_adapter);
void UpdateStatus();
FeatureStatus ComputeStatus();
bool IsBluetoothOn() const;
// device::BluetoothAdapter::Observer:
void AdapterPresentChanged(device::BluetoothAdapter* adapter,
bool present) override;
void AdapterPoweredChanged(device::BluetoothAdapter* adapter,
bool powered) override;
device_sync::DeviceSyncClient* device_sync_client_;
multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client_;
scoped_refptr<device::BluetoothAdapter> bluetooth_adapter_;
base::Optional<FeatureStatus> status_;
base::WeakPtrFactory<FeatureStatusProviderImpl> weak_ptr_factory_{this};
};
} // namespace phonehub
} // namespace chromeos
#endif // CHROMEOS_COMPONENTS_PHONEHUB_FEATURE_STATUS_PROVIDER_IMPL_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment