phone_hub_ui_controller.cc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. // Copyright 2020 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/system/phonehub/phone_hub_ui_controller.h"
  5. #include <memory>
  6. #include "ash/components/multidevice/logging/logging.h"
  7. #include "ash/components/phonehub/browser_tabs_model_provider.h"
  8. #include "ash/components/phonehub/connection_scheduler.h"
  9. #include "ash/components/phonehub/phone_hub_manager.h"
  10. #include "ash/components/phonehub/tether_controller.h"
  11. #include "ash/components/phonehub/user_action_recorder.h"
  12. #include "ash/constants/ash_features.h"
  13. #include "ash/root_window_controller.h"
  14. #include "ash/session/session_controller_impl.h"
  15. #include "ash/shell.h"
  16. #include "ash/system/eche/eche_tray.h"
  17. #include "ash/system/phonehub/bluetooth_disabled_view.h"
  18. #include "ash/system/phonehub/onboarding_view.h"
  19. #include "ash/system/phonehub/phone_connected_view.h"
  20. #include "ash/system/phonehub/phone_connecting_view.h"
  21. #include "ash/system/phonehub/phone_disconnected_view.h"
  22. #include "ash/system/phonehub/phone_hub_content_view.h"
  23. #include "ash/system/phonehub/tether_connection_pending_view.h"
  24. #include "ash/system/status_area_widget.h"
  25. #include "base/bind.h"
  26. #include "base/metrics/histogram_functions.h"
  27. #include "base/time/time.h"
  28. namespace ash {
  29. namespace {
  30. using FeatureStatus = phonehub::FeatureStatus;
  31. using TetherStatus = phonehub::TetherController::Status;
  32. constexpr base::TimeDelta kConnectingViewGracePeriod = base::Seconds(40);
  33. phone_hub_metrics::Screen GetMetricsScreen(
  34. PhoneHubUiController::UiState ui_state) {
  35. switch (ui_state) {
  36. case PhoneHubUiController::UiState::kOnboardingWithoutPhone:
  37. return phone_hub_metrics::Screen::kOnboardingNewMultideviceUser;
  38. case PhoneHubUiController::UiState::kOnboardingWithPhone:
  39. return phone_hub_metrics::Screen::kOnboardingExistingMultideviceUser;
  40. case PhoneHubUiController::UiState::kPhoneConnected:
  41. return phone_hub_metrics::Screen::kPhoneConnected;
  42. case PhoneHubUiController::UiState::kPhoneDisconnected:
  43. return phone_hub_metrics::Screen::kPhoneDisconnected;
  44. case PhoneHubUiController::UiState::kPhoneConnecting:
  45. return phone_hub_metrics::Screen::kPhoneConnecting;
  46. case PhoneHubUiController::UiState::kBluetoothDisabled:
  47. return phone_hub_metrics::Screen::kBluetoothOrWifiDisabled;
  48. case PhoneHubUiController::UiState::kTetherConnectionPending:
  49. return phone_hub_metrics::Screen::kTetherConnectionPending;
  50. case PhoneHubUiController::UiState::kHidden:
  51. return phone_hub_metrics::Screen::kInvalid;
  52. }
  53. }
  54. std::string PhoneHubUIStateToString(PhoneHubUiController::UiState ui_state) {
  55. switch (ui_state) {
  56. case PhoneHubUiController::UiState::kOnboardingWithoutPhone:
  57. return "[kOnboardingWithoutPhone]";
  58. case PhoneHubUiController::UiState::kOnboardingWithPhone:
  59. return "[kOnboardingWithPhone]";
  60. case PhoneHubUiController::UiState::kPhoneConnected:
  61. return "[kPhoneConnected]";
  62. case PhoneHubUiController::UiState::kPhoneDisconnected:
  63. return "[kPhoneDisconnected]";
  64. case PhoneHubUiController::UiState::kPhoneConnecting:
  65. return "[kPhoneConnecting]";
  66. case PhoneHubUiController::UiState::kBluetoothDisabled:
  67. return "[kBluetoothDisabled]";
  68. case PhoneHubUiController::UiState::kTetherConnectionPending:
  69. return "[kTetherConnectionPending]";
  70. case PhoneHubUiController::UiState::kHidden:
  71. return "[kHidden]";
  72. }
  73. }
  74. std::string FeatureStatusToString(FeatureStatus feature_status) {
  75. switch (feature_status) {
  76. case FeatureStatus::kNotEligibleForFeature:
  77. return "[kNotEligibleForFeature]";
  78. case FeatureStatus::kEligiblePhoneButNotSetUp:
  79. return "[kEligiblePhoneButNotSetUp]";
  80. case FeatureStatus::kPhoneSelectedAndPendingSetup:
  81. return "[kPhoneSelectedAndPendingSetup]";
  82. case FeatureStatus::kDisabled:
  83. return "[kDisabled]";
  84. case FeatureStatus::kUnavailableBluetoothOff:
  85. return "[kUnavailableBluetoothOff]";
  86. case FeatureStatus::kEnabledButDisconnected:
  87. return "[kEnabledButDisconnected]";
  88. case FeatureStatus::kEnabledAndConnecting:
  89. return "[kEnabledAndConnecting]";
  90. case FeatureStatus::kEnabledAndConnected:
  91. return "[kEnabledAndConnected]";
  92. case FeatureStatus::kLockOrSuspended:
  93. return "[kLockOrSuspended]";
  94. }
  95. }
  96. } // namespace
  97. PhoneHubUiController::PhoneHubUiController() {
  98. // ash::Shell may not exist in tests.
  99. if (ash::Shell::HasInstance())
  100. Shell::Get()->session_controller()->AddObserver(this);
  101. }
  102. PhoneHubUiController::~PhoneHubUiController() {
  103. // ash::Shell may not exist in tests.
  104. if (ash::Shell::HasInstance())
  105. Shell::Get()->session_controller()->RemoveObserver(this);
  106. CleanUpPhoneHubManager();
  107. }
  108. void PhoneHubUiController::SetPhoneHubManager(
  109. phonehub::PhoneHubManager* phone_hub_manager) {
  110. if (phone_hub_manager == phone_hub_manager_)
  111. return;
  112. CleanUpPhoneHubManager();
  113. phone_hub_manager_ = phone_hub_manager;
  114. if (phone_hub_manager_) {
  115. phone_hub_manager_->GetFeatureStatusProvider()->AddObserver(this);
  116. phone_hub_manager_->GetOnboardingUiTracker()->AddObserver(this);
  117. phone_hub_manager_->GetPhoneModel()->AddObserver(this);
  118. }
  119. UpdateUiState(GetUiStateFromPhoneHubManager());
  120. }
  121. std::unique_ptr<views::View> PhoneHubUiController::CreateStatusHeaderView(
  122. PhoneStatusView::Delegate* delegate) {
  123. if (!phone_hub_manager_)
  124. return nullptr;
  125. return std::make_unique<PhoneStatusView>(phone_hub_manager_->GetPhoneModel(),
  126. delegate);
  127. }
  128. std::unique_ptr<PhoneHubContentView> PhoneHubUiController::CreateContentView(
  129. OnboardingView::Delegate* delegate) {
  130. PA_LOG(VERBOSE) << __func__
  131. << ": ui state = " << PhoneHubUIStateToString(ui_state_);
  132. switch (ui_state_) {
  133. case UiState::kHidden:
  134. return nullptr;
  135. case UiState::kOnboardingWithoutPhone:
  136. return std::make_unique<OnboardingView>(
  137. phone_hub_manager_->GetOnboardingUiTracker(), delegate,
  138. OnboardingView::kNewMultideviceUser);
  139. case UiState::kOnboardingWithPhone:
  140. return std::make_unique<OnboardingView>(
  141. phone_hub_manager_->GetOnboardingUiTracker(), delegate,
  142. OnboardingView::kExistingMultideviceUser);
  143. case UiState::kBluetoothDisabled:
  144. return std::make_unique<BluetoothDisabledView>();
  145. case UiState::kPhoneConnecting:
  146. return std::make_unique<PhoneConnectingView>();
  147. case UiState::kTetherConnectionPending:
  148. return std::make_unique<TetherConnectionPendingView>();
  149. case UiState::kPhoneDisconnected:
  150. if (connecting_view_grace_period_timer_.IsRunning())
  151. return std::make_unique<PhoneConnectingView>();
  152. return std::make_unique<PhoneDisconnectedView>(
  153. phone_hub_manager_->GetConnectionScheduler());
  154. case UiState::kPhoneConnected:
  155. return std::make_unique<PhoneConnectedView>(phone_hub_manager_);
  156. }
  157. }
  158. void PhoneHubUiController::HandleBubbleOpened() {
  159. // Make sure Eche window is not shown.
  160. if (features::IsEcheSWAEnabled()) {
  161. EcheTray* eche_tray = Shell::GetPrimaryRootWindowController()
  162. ->GetStatusAreaWidget()
  163. ->eche_tray();
  164. if (eche_tray)
  165. eche_tray->CloseBubble();
  166. }
  167. if (!phone_hub_manager_)
  168. return;
  169. auto feature_status =
  170. phone_hub_manager_->GetFeatureStatusProvider()->GetStatus();
  171. if (feature_status == FeatureStatus::kEnabledButDisconnected)
  172. phone_hub_manager_->GetConnectionScheduler()->ScheduleConnectionNow();
  173. phone_hub_manager_->GetBrowserTabsModelProvider()->TriggerRefresh();
  174. RecordStatusOnBubbleOpened();
  175. bool is_feature_enabled =
  176. feature_status == FeatureStatus::kEnabledAndConnected ||
  177. feature_status == FeatureStatus::kEnabledButDisconnected ||
  178. feature_status == FeatureStatus::kEnabledAndConnected;
  179. if (!is_feature_enabled) {
  180. PA_LOG(VERBOSE) << __func__ << ": feature is not enabled. Feature status = "
  181. << FeatureStatusToString(feature_status);
  182. return;
  183. }
  184. PA_LOG(VERBOSE) << __func__ << ": feature is enabled. Feature status = "
  185. << FeatureStatusToString(feature_status);
  186. if (!has_requested_tether_scan_during_session_ &&
  187. phone_hub_manager_->GetTetherController()->GetStatus() ==
  188. TetherStatus::kConnectionUnavailable) {
  189. phone_hub_manager_->GetTetherController()->ScanForAvailableConnection();
  190. has_requested_tether_scan_during_session_ = true;
  191. }
  192. }
  193. void PhoneHubUiController::RecordStatusOnBubbleOpened() {
  194. switch (ui_state_) {
  195. case UiState::kHidden:
  196. case UiState::kOnboardingWithoutPhone:
  197. case UiState::kOnboardingWithPhone:
  198. case UiState::kBluetoothDisabled:
  199. case UiState::kTetherConnectionPending:
  200. return;
  201. case UiState::kPhoneConnected:
  202. base::UmaHistogramEnumeration("PhoneHub.BubbleOpened.Connectable.Page",
  203. phone_hub_metrics::Screen::kPhoneConnected);
  204. return;
  205. case UiState::kPhoneDisconnected:
  206. case UiState::kPhoneConnecting:
  207. phone_hub_manager_->GetHostLastSeenTimestamp(
  208. base::BindOnce(&PhoneHubUiController::OnGetHostLastSeenTimestamp,
  209. weak_ptr_factory_.GetWeakPtr(), ui_state_));
  210. return;
  211. }
  212. }
  213. void PhoneHubUiController::OnGetHostLastSeenTimestamp(
  214. UiState ui_state_when_opened,
  215. absl::optional<base::Time> timestamp) {
  216. if (timestamp) {
  217. base::UmaHistogramLongTimes(
  218. "PhoneHub.BubbleOpened.Connectable.Failed.HostLastSeen",
  219. base::Time::Now() - timestamp.value());
  220. }
  221. // Only log when we've seen the host phone within the last 2 minutes.
  222. if (!timestamp || base::Time::Now() - timestamp.value() > base::Minutes(2)) {
  223. return;
  224. }
  225. base::UmaHistogramEnumeration("PhoneHub.BubbleOpened.Connectable.Page",
  226. GetMetricsScreen(ui_state_when_opened));
  227. }
  228. void PhoneHubUiController::AddObserver(Observer* observer) {
  229. observer_list_.AddObserver(observer);
  230. }
  231. void PhoneHubUiController::RemoveObserver(Observer* observer) {
  232. observer_list_.RemoveObserver(observer);
  233. }
  234. void PhoneHubUiController::OnFeatureStatusChanged() {
  235. UpdateUiState(GetUiStateFromPhoneHubManager());
  236. }
  237. void PhoneHubUiController::OnShouldShowOnboardingUiChanged() {
  238. UpdateUiState(GetUiStateFromPhoneHubManager());
  239. }
  240. void PhoneHubUiController::OnModelChanged() {
  241. UpdateUiState(GetUiStateFromPhoneHubManager());
  242. }
  243. void PhoneHubUiController::OnActiveUserSessionChanged(
  244. const AccountId& account_id) {
  245. UpdateUiState(GetUiStateFromPhoneHubManager());
  246. }
  247. void PhoneHubUiController::UpdateUiState(
  248. PhoneHubUiController::UiState new_state) {
  249. if (new_state == ui_state_)
  250. return;
  251. PA_LOG(VERBOSE) << __func__
  252. << ": old ui = " << PhoneHubUIStateToString(ui_state_)
  253. << ", new ui = " << PhoneHubUIStateToString(new_state);
  254. ui_state_ = new_state;
  255. for (auto& observer : observer_list_)
  256. observer.OnPhoneHubUiStateChanged();
  257. }
  258. PhoneHubUiController::UiState
  259. PhoneHubUiController::GetUiStateFromPhoneHubManager() {
  260. if (!Shell::Get()->session_controller()->IsUserPrimary() ||
  261. !phone_hub_manager_)
  262. return UiState::kHidden;
  263. auto feature_status =
  264. phone_hub_manager_->GetFeatureStatusProvider()->GetStatus();
  265. auto* tracker = phone_hub_manager_->GetOnboardingUiTracker();
  266. auto* phone_model = phone_hub_manager_->GetPhoneModel();
  267. bool should_show_onboarding_ui = tracker->ShouldShowOnboardingUi();
  268. bool is_tether_connecting =
  269. phone_hub_manager_->GetTetherController()->GetStatus() ==
  270. TetherStatus::kConnecting;
  271. switch (feature_status) {
  272. case FeatureStatus::kPhoneSelectedAndPendingSetup:
  273. [[fallthrough]];
  274. case FeatureStatus::kNotEligibleForFeature:
  275. return UiState::kHidden;
  276. case FeatureStatus::kEligiblePhoneButNotSetUp:
  277. return should_show_onboarding_ui ? UiState::kOnboardingWithoutPhone
  278. : UiState::kHidden;
  279. case FeatureStatus::kDisabled:
  280. return should_show_onboarding_ui ? UiState::kOnboardingWithPhone
  281. : UiState::kHidden;
  282. case FeatureStatus::kUnavailableBluetoothOff:
  283. return UiState::kBluetoothDisabled;
  284. case FeatureStatus::kEnabledButDisconnected:
  285. return UiState::kPhoneDisconnected;
  286. case FeatureStatus::kEnabledAndConnecting:
  287. connecting_view_grace_period_timer_.Start(
  288. FROM_HERE, kConnectingViewGracePeriod,
  289. base::BindOnce(&PhoneHubUiController::OnConnectingViewTimerEnd,
  290. base::Unretained(this)));
  291. // If a tether network is being connected to, or the |ui_state_|
  292. // was UiState::kTetherConnectionPending, continue returning
  293. // the UiState::kTetherConnectionPending state.
  294. return is_tether_connecting ||
  295. ui_state_ == UiState::kTetherConnectionPending
  296. ? UiState::kTetherConnectionPending
  297. : UiState::kPhoneConnecting;
  298. case FeatureStatus::kEnabledAndConnected:
  299. // If the timer is running, reset the timer so if we disconnect, we will
  300. // show the connecting view instead of the disconnecting view.
  301. if (connecting_view_grace_period_timer_.IsRunning())
  302. connecting_view_grace_period_timer_.Reset();
  303. // Delay displaying the connected view until the phone model is ready.
  304. if (phone_model->phone_status_model().has_value())
  305. return UiState::kPhoneConnected;
  306. // If the the |ui_state_| was UiState::kTetherConnectionPending, continue
  307. // returning the UiState::kTetherConnectionPending state.
  308. if (ui_state_ == UiState::kTetherConnectionPending)
  309. return UiState::kTetherConnectionPending;
  310. return UiState::kPhoneConnecting;
  311. case FeatureStatus::kLockOrSuspended:
  312. return UiState::kHidden;
  313. }
  314. }
  315. void PhoneHubUiController::OnConnectingViewTimerEnd() {
  316. // Update the UI state if the UI state has changed.
  317. if (ui_state_ != UiState::kPhoneDisconnected) {
  318. UpdateUiState(GetUiStateFromPhoneHubManager());
  319. return;
  320. }
  321. // If we are still disconnected, force the observation. We cannot call
  322. // |GetUiStateFromPhoneHubManager()| in this case because it will reset the
  323. // timer, and thus the disconnected view will never be shown. This way, the
  324. // disconnected view will be shown.
  325. for (auto& observer : observer_list_)
  326. observer.OnPhoneHubUiStateChanged();
  327. }
  328. void PhoneHubUiController::CleanUpPhoneHubManager() {
  329. if (!phone_hub_manager_)
  330. return;
  331. phone_hub_manager_->GetFeatureStatusProvider()->RemoveObserver(this);
  332. phone_hub_manager_->GetOnboardingUiTracker()->RemoveObserver(this);
  333. phone_hub_manager_->GetPhoneModel()->RemoveObserver(this);
  334. }
  335. } // namespace ash