feature_state_manager_impl.cc 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  1. // Copyright 2018 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/services/multidevice_setup/feature_state_manager_impl.h"
  5. #include <array>
  6. #include "ash/components/multidevice/logging/logging.h"
  7. #include "ash/components/multidevice/remote_device_ref.h"
  8. #include "ash/components/multidevice/software_feature.h"
  9. #include "ash/constants/ash_features.h"
  10. #include "ash/services/multidevice_setup/global_state_feature_manager.h"
  11. #include "ash/services/multidevice_setup/public/cpp/prefs.h"
  12. #include "base/bind.h"
  13. #include "base/containers/contains.h"
  14. #include "base/containers/flat_map.h"
  15. #include "base/logging.h"
  16. #include "base/memory/ptr_util.h"
  17. #include "base/metrics/histogram_functions.h"
  18. #include "base/time/time.h"
  19. #include "components/prefs/pref_service.h"
  20. #include "third_party/abseil-cpp/absl/types/optional.h"
  21. namespace ash {
  22. namespace multidevice_setup {
  23. namespace {
  24. constexpr base::TimeDelta kFeatureStateLoggingPeriod = base::Minutes(30);
  25. constexpr std::array<mojom::Feature, 4> kPhoneHubSubFeatures{
  26. mojom::Feature::kPhoneHubNotifications, mojom::Feature::kPhoneHubCameraRoll,
  27. mojom::Feature::kPhoneHubTaskContinuation, mojom::Feature::kEche};
  28. base::flat_map<mojom::Feature, std::string>
  29. GenerateFeatureToEnabledPrefNameMap() {
  30. return base::flat_map<mojom::Feature, std::string>{
  31. {mojom::Feature::kBetterTogetherSuite,
  32. kBetterTogetherSuiteEnabledPrefName},
  33. {mojom::Feature::kInstantTethering, kInstantTetheringEnabledPrefName},
  34. {mojom::Feature::kMessages, kMessagesEnabledPrefName},
  35. {mojom::Feature::kSmartLock, kSmartLockEnabledPrefName},
  36. {mojom::Feature::kPhoneHub, kPhoneHubEnabledPrefName},
  37. {mojom::Feature::kPhoneHubCameraRoll, kPhoneHubCameraRollEnabledPrefName},
  38. {mojom::Feature::kPhoneHubNotifications,
  39. kPhoneHubNotificationsEnabledPrefName},
  40. {mojom::Feature::kPhoneHubTaskContinuation,
  41. kPhoneHubTaskContinuationEnabledPrefName},
  42. {mojom::Feature::kEche, kEcheEnabledPrefName}};
  43. }
  44. base::flat_map<mojom::Feature, std::string>
  45. GenerateFeatureToAllowedPrefNameMap() {
  46. return base::flat_map<mojom::Feature, std::string>{
  47. {mojom::Feature::kInstantTethering, kInstantTetheringAllowedPrefName},
  48. {mojom::Feature::kMessages, kMessagesAllowedPrefName},
  49. {mojom::Feature::kSmartLock, kSmartLockAllowedPrefName},
  50. {mojom::Feature::kPhoneHub, kPhoneHubAllowedPrefName},
  51. {mojom::Feature::kPhoneHubCameraRoll, kPhoneHubCameraRollAllowedPrefName},
  52. {mojom::Feature::kPhoneHubNotifications,
  53. kPhoneHubNotificationsAllowedPrefName},
  54. {mojom::Feature::kPhoneHubTaskContinuation,
  55. kPhoneHubTaskContinuationAllowedPrefName},
  56. {mojom::Feature::kWifiSync, kWifiSyncAllowedPrefName},
  57. {mojom::Feature::kEche, kEcheAllowedPrefName}};
  58. }
  59. // Each feature's default value is kUnavailableNoVerifiedHost_NoEligibleHosts
  60. // until proven otherwise.
  61. base::flat_map<mojom::Feature, mojom::FeatureState>
  62. GenerateInitialDefaultCachedStateMap() {
  63. return base::flat_map<mojom::Feature, mojom::FeatureState>{
  64. {mojom::Feature::kBetterTogetherSuite,
  65. mojom::FeatureState::kUnavailableNoVerifiedHost_NoEligibleHosts},
  66. {mojom::Feature::kInstantTethering,
  67. mojom::FeatureState::kUnavailableNoVerifiedHost_NoEligibleHosts},
  68. {mojom::Feature::kMessages,
  69. mojom::FeatureState::kUnavailableNoVerifiedHost_NoEligibleHosts},
  70. {mojom::Feature::kSmartLock,
  71. mojom::FeatureState::kUnavailableNoVerifiedHost_NoEligibleHosts},
  72. {mojom::Feature::kPhoneHub,
  73. mojom::FeatureState::kUnavailableNoVerifiedHost_NoEligibleHosts},
  74. {mojom::Feature::kPhoneHubCameraRoll,
  75. mojom::FeatureState::kUnavailableNoVerifiedHost_NoEligibleHosts},
  76. {mojom::Feature::kPhoneHubNotifications,
  77. mojom::FeatureState::kUnavailableNoVerifiedHost_NoEligibleHosts},
  78. {mojom::Feature::kPhoneHubTaskContinuation,
  79. mojom::FeatureState::kUnavailableNoVerifiedHost_NoEligibleHosts},
  80. {mojom::Feature::kWifiSync,
  81. mojom::FeatureState::kUnavailableNoVerifiedHost_NoEligibleHosts},
  82. {mojom::Feature::kEche,
  83. mojom::FeatureState::kUnavailableNoVerifiedHost_NoEligibleHosts},
  84. };
  85. }
  86. void ProcessSuiteEdgeCases(
  87. FeatureStateManager::FeatureStatesMap* feature_states_map_ptr) {
  88. FeatureStateManager::FeatureStatesMap& feature_states_map =
  89. *feature_states_map_ptr;
  90. // If the top-level Phone Hub feature is prohibited by policy, all of the
  91. // sub-features are implicitly prohibited as well.
  92. if (feature_states_map[mojom::Feature::kPhoneHub] ==
  93. mojom::FeatureState::kProhibitedByPolicy) {
  94. for (const auto& phone_hub_sub_feature : kPhoneHubSubFeatures) {
  95. feature_states_map[phone_hub_sub_feature] =
  96. mojom::FeatureState::kProhibitedByPolicy;
  97. }
  98. }
  99. bool are_all_sub_features_prohibited_or_unsupported = true;
  100. bool is_at_least_one_feature_supported = false;
  101. for (const auto& map_entry : feature_states_map) {
  102. // Skip the suite feature, since it doesn't have its own policy.
  103. if (map_entry.first == mojom::Feature::kBetterTogetherSuite)
  104. continue;
  105. const mojom::FeatureState feature_state = map_entry.second;
  106. if (feature_state != mojom::FeatureState::kNotSupportedByChromebook)
  107. is_at_least_one_feature_supported = true;
  108. // Also check for features that are not supported by the Chromebook, since
  109. // we should still consider the suite prohibited if all sub-features are
  110. // prohibited except for those that aren't even shown in the UI at all.
  111. if (feature_state != mojom::FeatureState::kProhibitedByPolicy &&
  112. feature_state != mojom::FeatureState::kNotSupportedByChromebook) {
  113. are_all_sub_features_prohibited_or_unsupported = false;
  114. }
  115. }
  116. // If no features are supported, the suite as a whole is considered
  117. // unsupported.
  118. if (!is_at_least_one_feature_supported) {
  119. feature_states_map[mojom::Feature::kBetterTogetherSuite] =
  120. mojom::FeatureState::kNotSupportedByChromebook;
  121. return;
  122. }
  123. // The Better Together suite does not have its own explicit device policy;
  124. // instead, if all supported sub-features are prohibited by policy, the entire
  125. // suite should be considered prohibited.
  126. if (are_all_sub_features_prohibited_or_unsupported) {
  127. feature_states_map[mojom::Feature::kBetterTogetherSuite] =
  128. mojom::FeatureState::kProhibitedByPolicy;
  129. return;
  130. }
  131. // If the Better Together suite is disabled by the user, any sub-features
  132. // which have been enabled by the user should be unavailable. The suite serves
  133. // as a gatekeeper to all sub-features.
  134. if (feature_states_map[mojom::Feature::kBetterTogetherSuite] ==
  135. mojom::FeatureState::kDisabledByUser) {
  136. for (auto& map_entry : feature_states_map) {
  137. mojom::FeatureState& feature_state = map_entry.second;
  138. if (feature_state == mojom::FeatureState::kEnabledByUser ||
  139. feature_state == mojom::FeatureState::kFurtherSetupRequired) {
  140. feature_state = mojom::FeatureState::kUnavailableSuiteDisabled;
  141. }
  142. }
  143. }
  144. // If the top-level Phone Hub feature is disabled, its sub-features are
  145. // unavailable.
  146. if (feature_states_map[mojom::Feature::kPhoneHub] ==
  147. mojom::FeatureState::kDisabledByUser) {
  148. for (const auto& phone_hub_sub_feature : kPhoneHubSubFeatures) {
  149. mojom::FeatureState& feature_state =
  150. feature_states_map[phone_hub_sub_feature];
  151. if (feature_state == mojom::FeatureState::kEnabledByUser ||
  152. feature_state == mojom::FeatureState::kUnavailableSuiteDisabled) {
  153. feature_state =
  154. mojom::FeatureState::kUnavailableTopLevelFeatureDisabled;
  155. }
  156. }
  157. }
  158. // If the top level Phone Hub feature is not supported by the phone, the
  159. // sub-features should also be not supported by the phone.
  160. if (feature_states_map[mojom::Feature::kPhoneHub] ==
  161. mojom::FeatureState::kNotSupportedByPhone) {
  162. for (const auto& phone_hub_sub_feature : kPhoneHubSubFeatures) {
  163. feature_states_map[phone_hub_sub_feature] =
  164. mojom::FeatureState::kNotSupportedByPhone;
  165. }
  166. }
  167. }
  168. } // namespace
  169. // static
  170. FeatureStateManagerImpl::Factory*
  171. FeatureStateManagerImpl::Factory::test_factory_ = nullptr;
  172. // static
  173. std::unique_ptr<FeatureStateManager> FeatureStateManagerImpl::Factory::Create(
  174. PrefService* pref_service,
  175. HostStatusProvider* host_status_provider,
  176. device_sync::DeviceSyncClient* device_sync_client,
  177. AndroidSmsPairingStateTracker* android_sms_pairing_state_tracker,
  178. const base::flat_map<mojom::Feature, GlobalStateFeatureManager*>&
  179. global_state_feature_managers,
  180. bool is_secondary_user) {
  181. if (test_factory_) {
  182. return test_factory_->CreateInstance(
  183. pref_service, host_status_provider, device_sync_client,
  184. android_sms_pairing_state_tracker, global_state_feature_managers,
  185. is_secondary_user);
  186. }
  187. return base::WrapUnique(new FeatureStateManagerImpl(
  188. pref_service, host_status_provider, device_sync_client,
  189. android_sms_pairing_state_tracker, global_state_feature_managers,
  190. is_secondary_user));
  191. }
  192. // static
  193. void FeatureStateManagerImpl::Factory::SetFactoryForTesting(
  194. Factory* test_factory) {
  195. test_factory_ = test_factory;
  196. }
  197. FeatureStateManagerImpl::Factory::~Factory() = default;
  198. FeatureStateManagerImpl::FeatureStateManagerImpl(
  199. PrefService* pref_service,
  200. HostStatusProvider* host_status_provider,
  201. device_sync::DeviceSyncClient* device_sync_client,
  202. AndroidSmsPairingStateTracker* android_sms_pairing_state_tracker,
  203. const base::flat_map<mojom::Feature, GlobalStateFeatureManager*>&
  204. global_state_feature_managers,
  205. bool is_secondary_user)
  206. : pref_service_(pref_service),
  207. host_status_provider_(host_status_provider),
  208. device_sync_client_(device_sync_client),
  209. android_sms_pairing_state_tracker_(android_sms_pairing_state_tracker),
  210. global_state_feature_managers_(global_state_feature_managers),
  211. is_secondary_user_(is_secondary_user),
  212. feature_to_enabled_pref_name_map_(GenerateFeatureToEnabledPrefNameMap()),
  213. feature_to_allowed_pref_name_map_(GenerateFeatureToAllowedPrefNameMap()),
  214. cached_feature_state_map_(GenerateInitialDefaultCachedStateMap()) {
  215. host_status_provider_->AddObserver(this);
  216. device_sync_client_->AddObserver(this);
  217. if (android_sms_pairing_state_tracker_)
  218. android_sms_pairing_state_tracker_->AddObserver(this);
  219. registrar_.Init(pref_service_);
  220. // Listen for changes to each of the "enabled" feature names.
  221. for (const auto& map_entry : feature_to_enabled_pref_name_map_) {
  222. registrar_.Add(
  223. map_entry.second,
  224. base::BindRepeating(&FeatureStateManagerImpl::OnPrefValueChanged,
  225. base::Unretained(this)));
  226. }
  227. // Also listen for changes to each of the "allowed" feature names.
  228. for (const auto& map_entry : feature_to_allowed_pref_name_map_) {
  229. registrar_.Add(
  230. map_entry.second,
  231. base::BindRepeating(&FeatureStateManagerImpl::OnPrefValueChanged,
  232. base::Unretained(this)));
  233. }
  234. // Prime the cache. Since this is the initial computation, it does not
  235. // represent a true change of feature state values, so observers should not be
  236. // notified.
  237. UpdateFeatureStateCache(false /* notify_observers_of_changes */);
  238. LogFeatureStates();
  239. feature_state_metric_timer_.Start(
  240. FROM_HERE, kFeatureStateLoggingPeriod,
  241. base::BindRepeating(&FeatureStateManagerImpl::LogFeatureStates,
  242. base::Unretained(this)));
  243. }
  244. FeatureStateManagerImpl::~FeatureStateManagerImpl() {
  245. host_status_provider_->RemoveObserver(this);
  246. device_sync_client_->RemoveObserver(this);
  247. if (android_sms_pairing_state_tracker_)
  248. android_sms_pairing_state_tracker_->RemoveObserver(this);
  249. }
  250. FeatureStateManager::FeatureStatesMap
  251. FeatureStateManagerImpl::GetFeatureStates() {
  252. return cached_feature_state_map_;
  253. }
  254. void FeatureStateManagerImpl::PerformSetFeatureEnabledState(
  255. mojom::Feature feature,
  256. bool enabled) {
  257. if (global_state_feature_managers_.contains(feature)) {
  258. global_state_feature_managers_.at(feature)->SetIsFeatureEnabled(enabled);
  259. // Need to manually trigger UpdateFeatureStateCache since changes to
  260. // this global feature state is not observed by |registrar_| and will not
  261. // invoke OnPrefValueChanged
  262. UpdateFeatureStateCache(true /* notify_observers_of_changes */);
  263. return;
  264. }
  265. // Note: Since |registrar_| observes changes to all relevant preferences,
  266. // this call will result in OnPrefValueChanged() being invoked, resulting in
  267. // observers being notified of the change.
  268. pref_service_->SetBoolean(feature_to_enabled_pref_name_map_[feature],
  269. enabled);
  270. }
  271. void FeatureStateManagerImpl::OnHostStatusChange(
  272. const HostStatusProvider::HostStatusWithDevice& host_status_with_device) {
  273. UpdateFeatureStateCache(true /* notify_observers_of_changes */);
  274. }
  275. void FeatureStateManagerImpl::OnNewDevicesSynced() {
  276. UpdateFeatureStateCache(true /* notify_observers_of_changes */);
  277. }
  278. void FeatureStateManagerImpl::OnPrefValueChanged() {
  279. UpdateFeatureStateCache(true /* notify_observers_of_changes */);
  280. }
  281. void FeatureStateManagerImpl::OnPairingStateChanged() {
  282. UpdateFeatureStateCache(true /* notify_observers_of_changes */);
  283. }
  284. void FeatureStateManagerImpl::UpdateFeatureStateCache(
  285. bool notify_observers_of_changes) {
  286. // Make a copy of |cached_feature_state_map_| before making edits to it.
  287. FeatureStatesMap previous_cached_feature_state_map =
  288. cached_feature_state_map_;
  289. // Update |cached_feature_state_map_| with computed values.
  290. auto it = cached_feature_state_map_.begin();
  291. while (it != cached_feature_state_map_.end()) {
  292. it->second = ComputeFeatureState(it->first);
  293. ++it;
  294. }
  295. // Some computed values must be updated to support various edge cases.
  296. ProcessSuiteEdgeCases(&cached_feature_state_map_);
  297. if (previous_cached_feature_state_map == cached_feature_state_map_)
  298. return;
  299. PA_LOG(INFO) << "Feature states map changed. Old map: "
  300. << previous_cached_feature_state_map
  301. << ", new map: " << cached_feature_state_map_;
  302. LogFeatureStates();
  303. NotifyFeatureStatesChange(cached_feature_state_map_);
  304. }
  305. mojom::FeatureState FeatureStateManagerImpl::ComputeFeatureState(
  306. mojom::Feature feature) {
  307. if (!IsAllowedByPolicy(feature))
  308. return mojom::FeatureState::kProhibitedByPolicy;
  309. HostStatusProvider::HostStatusWithDevice status_with_device =
  310. host_status_provider_->GetHostWithStatus();
  311. if (status_with_device.host_status() == mojom::HostStatus::kNoEligibleHosts)
  312. return mojom::FeatureState::kUnavailableNoVerifiedHost_NoEligibleHosts;
  313. if (status_with_device.host_status() != mojom::HostStatus::kHostVerified)
  314. return mojom::FeatureState::
  315. kUnavailableNoVerifiedHost_HostExistsButNotSetAndVerified;
  316. if (!IsSupportedByChromebook(feature))
  317. return mojom::FeatureState::kNotSupportedByChromebook;
  318. if (!HasSufficientSecurity(feature, *status_with_device.host_device()))
  319. return mojom::FeatureState::kUnavailableInsufficientSecurity;
  320. if (!HasBeenActivatedByPhone(feature, *status_with_device.host_device()))
  321. return mojom::FeatureState::kNotSupportedByPhone;
  322. if (RequiresFurtherSetup(feature))
  323. return mojom::FeatureState::kFurtherSetupRequired;
  324. return GetEnabledOrDisabledState(feature);
  325. }
  326. bool FeatureStateManagerImpl::IsAllowedByPolicy(mojom::Feature feature) {
  327. // If no policy preference exists for this feature, the feature is implicitly
  328. // allowed.
  329. if (!base::Contains(feature_to_allowed_pref_name_map_, feature))
  330. return true;
  331. return pref_service_->GetBoolean(feature_to_allowed_pref_name_map_[feature]);
  332. }
  333. bool FeatureStateManagerImpl::IsSupportedByChromebook(mojom::Feature feature) {
  334. static const std::pair<mojom::Feature, multidevice::SoftwareFeature>
  335. kFeatureAndClientSoftwareFeaturePairs[] = {
  336. {mojom::Feature::kBetterTogetherSuite,
  337. multidevice::SoftwareFeature::kBetterTogetherClient},
  338. {mojom::Feature::kInstantTethering,
  339. multidevice::SoftwareFeature::kInstantTetheringClient},
  340. {mojom::Feature::kMessages,
  341. multidevice::SoftwareFeature::kMessagesForWebClient},
  342. {mojom::Feature::kSmartLock,
  343. multidevice::SoftwareFeature::kSmartLockClient},
  344. // Note: Most Phone Hub-related features use the same SoftwareFeature.
  345. {mojom::Feature::kPhoneHub,
  346. multidevice::SoftwareFeature::kPhoneHubClient},
  347. {mojom::Feature::kPhoneHubNotifications,
  348. multidevice::SoftwareFeature::kPhoneHubClient},
  349. {mojom::Feature::kPhoneHubTaskContinuation,
  350. multidevice::SoftwareFeature::kPhoneHubClient},
  351. // Note: Camera Roll is launched separately from the rest of PhoneHub.
  352. {mojom::Feature::kPhoneHubCameraRoll,
  353. multidevice::SoftwareFeature::kPhoneHubCameraRollClient},
  354. {mojom::Feature::kWifiSync,
  355. multidevice::SoftwareFeature::kWifiSyncClient},
  356. {mojom::Feature::kEche, multidevice::SoftwareFeature::kEcheClient}};
  357. absl::optional<multidevice::RemoteDeviceRef> local_device =
  358. device_sync_client_->GetLocalDeviceMetadata();
  359. if (!local_device) {
  360. PA_LOG(ERROR) << "FeatureStateManagerImpl::" << __func__
  361. << ": Local device unexpectedly null.";
  362. return false;
  363. }
  364. for (const auto& pair : kFeatureAndClientSoftwareFeaturePairs) {
  365. if (pair.first != feature)
  366. continue;
  367. if ((pair.second == multidevice::SoftwareFeature::kPhoneHubClient ||
  368. pair.second == multidevice::SoftwareFeature::kEcheClient) &&
  369. is_secondary_user_) {
  370. return false;
  371. }
  372. return local_device->GetSoftwareFeatureState(pair.second) !=
  373. multidevice::SoftwareFeatureState::kNotSupported;
  374. }
  375. NOTREACHED();
  376. return false;
  377. }
  378. bool FeatureStateManagerImpl::HasSufficientSecurity(
  379. mojom::Feature feature,
  380. const multidevice::RemoteDeviceRef& host_device) {
  381. if (feature != mojom::Feature::kSmartLock)
  382. return true;
  383. // Special case for Smart Lock: if the host device does not have a lock screen
  384. // set, its SoftwareFeatureState for kSmartLockHost is supported but not
  385. // enabled.
  386. return host_device.GetSoftwareFeatureState(
  387. multidevice::SoftwareFeature::kSmartLockHost) !=
  388. multidevice::SoftwareFeatureState::kSupported;
  389. }
  390. bool FeatureStateManagerImpl::HasBeenActivatedByPhone(
  391. mojom::Feature feature,
  392. const multidevice::RemoteDeviceRef& host_device) {
  393. static const std::pair<mojom::Feature, multidevice::SoftwareFeature>
  394. kFeatureAndHostSoftwareFeaturePairs[] = {
  395. {mojom::Feature::kBetterTogetherSuite,
  396. multidevice::SoftwareFeature::kBetterTogetherHost},
  397. {mojom::Feature::kInstantTethering,
  398. multidevice::SoftwareFeature::kInstantTetheringHost},
  399. {mojom::Feature::kMessages,
  400. multidevice::SoftwareFeature::kMessagesForWebHost},
  401. {mojom::Feature::kSmartLock,
  402. multidevice::SoftwareFeature::kSmartLockHost},
  403. // Note: Most Phone Hub-related features use the same SoftwareFeature.
  404. {mojom::Feature::kPhoneHub,
  405. multidevice::SoftwareFeature::kPhoneHubHost},
  406. {mojom::Feature::kPhoneHubNotifications,
  407. multidevice::SoftwareFeature::kPhoneHubHost},
  408. {mojom::Feature::kPhoneHubTaskContinuation,
  409. multidevice::SoftwareFeature::kPhoneHubHost},
  410. // Note: Camera Roll is launched separately from the rest of PhoneHub.
  411. {mojom::Feature::kPhoneHubCameraRoll,
  412. multidevice::SoftwareFeature::kPhoneHubCameraRollHost},
  413. {mojom::Feature::kWifiSync,
  414. multidevice::SoftwareFeature::kWifiSyncHost},
  415. {mojom::Feature::kEche, multidevice::SoftwareFeature::kEcheHost}};
  416. for (const auto& pair : kFeatureAndHostSoftwareFeaturePairs) {
  417. if (pair.first != feature)
  418. continue;
  419. // The bluetooth public address is required in order to use PhoneHub/Eche
  420. // and its sub-features.
  421. if ((pair.second == multidevice::SoftwareFeature::kPhoneHubHost ||
  422. pair.second == multidevice::SoftwareFeature::kEcheHost) &&
  423. host_device.bluetooth_public_address().empty()) {
  424. return false;
  425. }
  426. multidevice::SoftwareFeatureState feature_state =
  427. host_device.GetSoftwareFeatureState(pair.second);
  428. if (feature_state == multidevice::SoftwareFeatureState::kEnabled) {
  429. return true;
  430. }
  431. // Edge Case: Eche is considered activated on the host when Phone Hub is
  432. // enabled and Eche's state is kSupported or kEnabled.
  433. if (feature == mojom::Feature::kEche) {
  434. return feature_state == multidevice::SoftwareFeatureState::kSupported &&
  435. host_device.GetSoftwareFeatureState(
  436. multidevice::SoftwareFeature::kPhoneHubHost) ==
  437. multidevice::SoftwareFeatureState::kEnabled;
  438. }
  439. // Edge Case: features with global states are considered activated on host
  440. // when the state is kSupported or kEnabled. kEnabled/kSupported correspond
  441. // to on/off for the global host state.
  442. return (global_state_feature_managers_.contains(feature) &&
  443. feature_state == multidevice::SoftwareFeatureState::kSupported);
  444. }
  445. NOTREACHED();
  446. return false;
  447. }
  448. // TODO(khorimoto): Add a way to determine whether Phone Hub notification
  449. // access has been granted by the user on the phone.
  450. bool FeatureStateManagerImpl::RequiresFurtherSetup(mojom::Feature feature) {
  451. if (feature != mojom::Feature::kMessages)
  452. return false;
  453. if (GetEnabledOrDisabledState(feature) ==
  454. mojom::FeatureState::kDisabledByUser) {
  455. return false;
  456. }
  457. return android_sms_pairing_state_tracker_ &&
  458. !android_sms_pairing_state_tracker_->IsAndroidSmsPairingComplete();
  459. }
  460. mojom::FeatureState FeatureStateManagerImpl::GetEnabledOrDisabledState(
  461. mojom::Feature feature) {
  462. if (global_state_feature_managers_.contains(feature)) {
  463. return (global_state_feature_managers_.at(feature)->IsFeatureEnabled()
  464. ? mojom::FeatureState::kEnabledByUser
  465. : mojom::FeatureState::kDisabledByUser);
  466. }
  467. if (!base::Contains(feature_to_enabled_pref_name_map_, feature)) {
  468. PA_LOG(ERROR) << "FeatureStateManagerImpl::GetEnabledOrDisabledState(): "
  469. << "Feature not present in \"enabled pref\" map: " << feature;
  470. NOTREACHED();
  471. }
  472. return pref_service_->GetBoolean(feature_to_enabled_pref_name_map_[feature])
  473. ? mojom::FeatureState::kEnabledByUser
  474. : mojom::FeatureState::kDisabledByUser;
  475. }
  476. void FeatureStateManagerImpl::LogFeatureStates() const {
  477. base::UmaHistogramEnumeration(
  478. "MultiDevice.BetterTogetherSuite.MultiDeviceFeatureState",
  479. cached_feature_state_map_.find(mojom::Feature::kBetterTogetherSuite)
  480. ->second);
  481. base::UmaHistogramEnumeration(
  482. "InstantTethering.MultiDeviceFeatureState",
  483. cached_feature_state_map_.find(mojom::Feature::kInstantTethering)
  484. ->second);
  485. base::UmaHistogramEnumeration(
  486. "AndroidSms.MultiDeviceFeatureState",
  487. cached_feature_state_map_.find(mojom::Feature::kMessages)->second);
  488. base::UmaHistogramEnumeration(
  489. "SmartLock.MultiDeviceFeatureState",
  490. cached_feature_state_map_.find(mojom::Feature::kSmartLock)->second);
  491. base::UmaHistogramEnumeration(
  492. "PhoneHub.MultiDeviceFeatureState.TopLevelFeature",
  493. cached_feature_state_map_.find(mojom::Feature::kPhoneHub)->second);
  494. base::UmaHistogramEnumeration(
  495. "PhoneHub.MultiDeviceFeatureState.CameraRoll",
  496. cached_feature_state_map_.find(mojom::Feature::kPhoneHubCameraRoll)
  497. ->second);
  498. base::UmaHistogramEnumeration(
  499. "PhoneHub.MultiDeviceFeatureState.Notifications",
  500. cached_feature_state_map_.find(mojom::Feature::kPhoneHubNotifications)
  501. ->second);
  502. base::UmaHistogramEnumeration(
  503. "PhoneHub.MultiDeviceFeatureState.TaskContinuation",
  504. cached_feature_state_map_.find(mojom::Feature::kPhoneHubTaskContinuation)
  505. ->second);
  506. base::UmaHistogramEnumeration(
  507. "WifiSync.MultiDeviceFeatureState",
  508. cached_feature_state_map_.find(mojom::Feature::kWifiSync)->second);
  509. base::UmaHistogramEnumeration(
  510. "Eche.MultiDeviceFeatureState",
  511. cached_feature_state_map_.find(mojom::Feature::kEche)->second);
  512. }
  513. } // namespace multidevice_setup
  514. } // namespace ash