Commit ac56b3e7 authored by Tim Song's avatar Tim Song Committed by Commit Bot

[CrOS PhoneHub] Add PhoneHubUiController.

This class translates the PhoneHubManager state into the corresponding
UI state and view that is shown in the tray bubble. Each state has
it's own view class and the PhoneHubTray bubble transitions through
these content views as the UI state changes.

This CL also refactors PhoneConnectedView into it's own class.

BUG=1106937,1126208

Change-Id: Ie012077a1905f80be91fbfb0b084d89219bd6c26
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2427060
Commit-Queue: Tim Song <tengs@chromium.org>
Reviewed-by: default avatarKyle Horimoto <khorimoto@chromium.org>
Cr-Commit-Position: refs/heads/master@{#810942}
parent afe2a59e
......@@ -1124,12 +1124,16 @@ component("ash") {
"system/phonehub/notification_opt_in_view.h",
"system/phonehub/onboarding_view.cc",
"system/phonehub/onboarding_view.h",
"system/phonehub/phone_connected_view.cc",
"system/phonehub/phone_connected_view.h",
"system/phonehub/phone_hub_interstitial_view.cc",
"system/phonehub/phone_hub_interstitial_view.h",
"system/phonehub/phone_hub_notification_controller.cc",
"system/phonehub/phone_hub_notification_controller.h",
"system/phonehub/phone_hub_tray.cc",
"system/phonehub/phone_hub_tray.h",
"system/phonehub/phone_hub_ui_controller.cc",
"system/phonehub/phone_hub_ui_controller.h",
"system/phonehub/phone_hub_view_ids.h",
"system/phonehub/phone_status_view.cc",
"system/phonehub/phone_status_view.h",
......@@ -2116,6 +2120,7 @@ test("ash_unittests") {
"system/palette/tools/screenshot_unittest.cc",
"system/phonehub/phone_hub_notification_controller_unittest.cc",
"system/phonehub/phone_hub_tray_unittest.cc",
"system/phonehub/phone_hub_ui_controller_unittest.cc",
"system/phonehub/phone_status_view_unittest.cc",
"system/phonehub/task_continuation_view_unittest.cc",
"system/power/backlights_forced_off_setter_unittest.cc",
......
......@@ -12,12 +12,6 @@ namespace ash {
class PhoneHubInterstitialView;
// Defines possible connection error states of the Phone Hub feature.
enum class ErrorStatus {
kDisconnected, // The connection to the phone has been interrupted.
kReconnecting, // Attempts to resume the connection to the phone.
};
// An interstitial view represeting that the Phone Hub feature is not available
// due to connection issues.
class ASH_EXPORT ConnectionErrorView : public views::View,
......@@ -25,6 +19,12 @@ class ASH_EXPORT ConnectionErrorView : public views::View,
public:
METADATA_HEADER(ConnectionErrorView);
// Defines possible connection error states of the Phone Hub feature.
enum class ErrorStatus {
kDisconnected, // The connection to the phone has been interrupted.
kReconnecting, // Attempts to resume the connection to the phone.
};
explicit ConnectionErrorView(ErrorStatus error);
ConnectionErrorView(const ConnectionErrorView&) = delete;
ConnectionErrorView& operator=(const ConnectionErrorView&) = delete;
......
// 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 "ash/system/phonehub/phone_connected_view.h"
#include <memory>
#include "ash/style/ash_color_provider.h"
#include "ash/system/phonehub/notification_opt_in_view.h"
#include "ash/system/phonehub/phone_status_view.h"
#include "ash/system/phonehub/quick_actions_view.h"
#include "ash/system/phonehub/task_continuation_view.h"
#include "ash/system/tray/tray_constants.h"
#include "chromeos/components/phonehub/notification_access_manager.h"
#include "chromeos/components/phonehub/phone_hub_manager.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/separator.h"
#include "ui/views/layout/box_layout.h"
namespace ash {
namespace {
constexpr int kPaddingBetweenTitleAndSeparator = 3;
} // namespace
PhoneConnectedView::PhoneConnectedView(
TrayBubbleView* bubble_view,
chromeos::phonehub::PhoneHubManager* phone_hub_manager) {
auto setup_layered_view = [](views::View* view) {
view->SetPaintToLayer();
view->layer()->SetFillsBoundsOpaquely(false);
};
auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical, gfx::Insets(0, 0, 0, 0)));
layout->SetDefaultFlex(1);
AddSeparator();
// TODO(meilinw): handle the case when the user has dismissed this opt in
// view once, we shouldn't show it again.
if (!phone_hub_manager->GetNotificationAccessManager()
->HasAccessBeenGranted()) {
AddChildView(std::make_unique<NotificationOptInView>(bubble_view));
}
setup_layered_view(AddChildView(std::make_unique<QuickActionsView>()));
AddSeparator();
auto* phone_model = phone_hub_manager->GetPhoneModel();
if (phone_model) {
setup_layered_view(
AddChildView(std::make_unique<TaskContinuationView>(phone_model)));
}
}
PhoneConnectedView::~PhoneConnectedView() = default;
const char* PhoneConnectedView::GetClassName() const {
return "PhoneConnectedView";
}
void PhoneConnectedView::AddSeparator() {
auto* separator = AddChildView(std::make_unique<views::Separator>());
separator->SetPaintToLayer();
separator->layer()->SetFillsBoundsOpaquely(false);
separator->SetColor(AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kSeparatorColor));
separator->SetBorder(views::CreateEmptyBorder(gfx::Insets(
kPaddingBetweenTitleAndSeparator, 0, kMenuSeparatorVerticalPadding, 0)));
}
} // namespace ash
// 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 ASH_SYSTEM_PHONEHUB_PHONE_CONNECTED_VIEW_H_
#define ASH_SYSTEM_PHONEHUB_PHONE_CONNECTED_VIEW_H_
#include "ash/ash_export.h"
#include "ui/views/view.h"
namespace chromeos {
namespace phonehub {
class PhoneHubManager;
} // namespace phonehub
} // namespace chromeos
namespace ash {
class TrayBubbleView;
// A view of the Phone Hub panel, displaying phone status and utility actions
// such as phone status, task continuation, etc.
class PhoneConnectedView : public views::View {
public:
PhoneConnectedView(TrayBubbleView* bubble_view,
chromeos::phonehub::PhoneHubManager* phone_hub_manager);
~PhoneConnectedView() override;
// views::View:
const char* GetClassName() const override;
private:
void AddSeparator();
};
} // namespace ash
#endif // ASH_SYSTEM_PHONEHUB_PHONE_CONNECTED_VIEW_H_
......@@ -4,15 +4,12 @@
#include "ash/system/phonehub/phone_hub_tray.h"
#include <memory>
#include "ash/accessibility/accessibility_controller_impl.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/shelf/shelf.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_provider.h"
#include "ash/system/phonehub/notification_opt_in_view.h"
#include "ash/system/phonehub/phone_status_view.h"
#include "ash/system/phonehub/quick_actions_view.h"
#include "ash/system/phonehub/task_continuation_view.h"
......@@ -24,7 +21,6 @@
#include "ash/system/tray/tray_popup_utils.h"
#include "ash/system/tray/tray_utils.h"
#include "base/bind.h"
#include "chromeos/components/phonehub/notification_access_manager.h"
#include "chromeos/components/phonehub/phone_hub_manager.h"
#include "chromeos/components/phonehub/phone_model.h"
#include "chromeos/constants/chromeos_features.h"
......@@ -33,8 +29,6 @@
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/separator.h"
namespace ash {
......@@ -46,67 +40,13 @@ constexpr int kTrayIconCrossAxisInset = 0;
constexpr gfx::Insets kBubblePadding(4, 16);
constexpr int kBubbleWidth = 400;
constexpr int kPaddingBetweenTitleAndSeparator = 3;
// A content view of the Phone Hub panel, displaying utility actions
// such as quick actions, task continuation, etc.
class PhoneHubView : public views ::View {
public:
explicit PhoneHubView(TrayBubbleView* bubble_view,
chromeos::phonehub::PhoneHubManager* phone_hub_manager)
: bubble_view_(bubble_view) {
auto setup_layered_view = [](views::View* view) {
view->SetPaintToLayer();
view->layer()->SetFillsBoundsOpaquely(false);
};
AddSeparator();
// TODO(meilinw): handle the case when the user has dismissed this opt in
// view once, we shouldn't show it again.
if (!phone_hub_manager->GetNotificationAccessManager()
->HasAccessBeenGranted()) {
bubble_view_->AddChildView(
std::make_unique<NotificationOptInView>(bubble_view_));
}
setup_layered_view(
bubble_view_->AddChildView(std::make_unique<QuickActionsView>()));
AddSeparator();
chromeos::phonehub::PhoneModel* phone_model =
phone_hub_manager->GetPhoneModel();
if (phone_model) {
setup_layered_view(bubble_view->AddChildView(
std::make_unique<TaskContinuationView>(phone_model)));
}
}
~PhoneHubView() override = default;
// views::View:
const char* GetClassName() const override { return "PhoneHubView"; }
private:
void AddSeparator() {
auto* separator =
bubble_view_->AddChildView(std::make_unique<views::Separator>());
separator->SetPaintToLayer();
separator->layer()->SetFillsBoundsOpaquely(false);
separator->SetColor(AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kSeparatorColor));
separator->SetBorder(views::CreateEmptyBorder(
gfx::Insets(kPaddingBetweenTitleAndSeparator, 0,
kMenuSeparatorVerticalPadding, 0)));
}
TrayBubbleView* bubble_view_ = nullptr;
};
} // namespace
PhoneHubTray::PhoneHubTray(Shelf* shelf) : TrayBackgroundView(shelf) {
PhoneHubTray::PhoneHubTray(Shelf* shelf)
: TrayBackgroundView(shelf), ui_controller_(new PhoneHubUiController()) {
observed_phone_hub_ui_controller_.Add(ui_controller_.get());
// TODO(tengs): Update icon to spec.
auto icon = std::make_unique<views::ImageView>();
icon->SetTooltipText(
......@@ -123,21 +63,11 @@ PhoneHubTray::PhoneHubTray(Shelf* shelf) : TrayBackgroundView(shelf) {
PhoneHubTray::~PhoneHubTray() {
if (bubble_)
bubble_->bubble_view()->ResetDelegate();
CleanUpPhoneHubManager();
}
void PhoneHubTray::SetPhoneHubManager(
chromeos::phonehub::PhoneHubManager* phone_hub_manager) {
if (phone_hub_manager == phone_hub_manager_)
return;
CleanUpPhoneHubManager();
phone_hub_manager_ = phone_hub_manager;
if (phone_hub_manager_)
phone_hub_manager_->GetFeatureStatusProvider()->AddObserver(this);
OnFeatureStatusChanged();
ui_controller_->SetPhoneHubManager(phone_hub_manager);
}
void PhoneHubTray::ClickedOutsideBubble() {
......@@ -170,6 +100,27 @@ void PhoneHubTray::HideBubble(const TrayBubbleView* bubble_view) {
HideBubbleWithView(bubble_view);
}
void PhoneHubTray::OnPhoneHubUiStateChanged() {
UpdateVisibility();
if (!bubble_)
return;
TrayBubbleView* bubble_view = bubble_->bubble_view();
DCHECK(ui_controller_.get());
std::unique_ptr<views::View> content_view =
ui_controller_->CreateContentView(bubble_view);
if (!content_view.get()) {
CloseBubble();
return;
}
if (content_view_)
bubble_view->RemoveChildView(content_view_);
content_view_ = content_view.get();
bubble_view->AddChildView(std::move(content_view));
}
void PhoneHubTray::AnchorUpdated() {
if (bubble_)
bubble_->bubble_view()->UpdateBubble();
......@@ -194,8 +145,6 @@ void PhoneHubTray::ShowBubble(bool show_by_click) {
if (bubble_)
return;
DCHECK(phone_hub_manager_);
TrayBubbleView::InitParams init_params;
init_params.delegate = this;
init_params.parent_window = GetBubbleWindowContainer();
......@@ -215,23 +164,25 @@ void PhoneHubTray::ShowBubble(bool show_by_click) {
// We will always have this phone status view on top of the bubble view
// to display any available phone status and the settings icon.
chromeos::phonehub::PhoneModel* phone_model =
phone_hub_manager_->GetPhoneModel();
if (phone_model) {
auto* phone_status = bubble_view->AddChildView(
std::make_unique<PhoneStatusView>(phone_model));
std::unique_ptr<views::View> phone_status =
ui_controller_->CreateStatusHeaderView();
if (phone_status) {
phone_status->SetPaintToLayer();
phone_status->layer()->SetFillsBoundsOpaquely(false);
bubble_view->AddChildView(std::move(phone_status));
}
// Other contents, i.e. the connected view and the interstitial views,
// will be positioned underneath the phone status view and updated based
// on the current mode.
bubble_view->AddChildView(
std::make_unique<PhoneHubView>(bubble_view, phone_hub_manager_));
auto content_view = ui_controller_->CreateContentView(bubble_view);
content_view_ = content_view.get();
if (content_view_)
bubble_view->AddChildView(std::move(content_view));
bubble_ = std::make_unique<TrayBubbleWrapper>(this, bubble_view,
false /* is_persistent */);
SetIsActive(true);
}
......@@ -244,43 +195,16 @@ const char* PhoneHubTray::GetClassName() const {
}
void PhoneHubTray::CloseBubble() {
content_view_ = nullptr;
bubble_.reset();
SetIsActive(false);
shelf()->UpdateAutoHideState();
}
void PhoneHubTray::OnFeatureStatusChanged() {
UpdateVisibility();
}
void PhoneHubTray::UpdateVisibility() {
if (!phone_hub_manager_) {
SetVisiblePreferred(false);
return;
}
auto feature_status =
phone_hub_manager_->GetFeatureStatusProvider()->GetStatus();
bool is_visible;
switch (feature_status) {
case chromeos::phonehub::FeatureStatus::kNotEligibleForFeature:
FALLTHROUGH;
case chromeos::phonehub::FeatureStatus::kDisabled:
is_visible = false;
break;
default:
is_visible = true;
break;
}
SetVisiblePreferred(is_visible);
}
void PhoneHubTray::CleanUpPhoneHubManager() {
if (!phone_hub_manager_)
return;
phone_hub_manager_->GetFeatureStatusProvider()->RemoveObserver(this);
DCHECK(ui_controller_.get());
auto ui_state = ui_controller_->ui_state();
SetVisiblePreferred(ui_state != PhoneHubUiController::UiState::kHidden);
}
} // namespace ash
......@@ -6,8 +6,9 @@
#define ASH_SYSTEM_PHONEHUB_PHONE_HUB_TRAY_H_
#include "ash/ash_export.h"
#include "ash/system/phonehub/phone_hub_ui_controller.h"
#include "ash/system/tray/tray_background_view.h"
#include "chromeos/components/phonehub/feature_status_provider.h"
#include "base/scoped_observer.h"
namespace chromeos {
namespace phonehub {
......@@ -25,9 +26,8 @@ class TrayBubbleWrapper;
// This class represents the Phone Hub tray button in the status area and
// controls the bubble that is shown when the tray button is clicked.
class ASH_EXPORT PhoneHubTray
: public TrayBackgroundView,
public chromeos::phonehub::FeatureStatusProvider::Observer {
class ASH_EXPORT PhoneHubTray : public TrayBackgroundView,
public PhoneHubUiController::Observer {
public:
explicit PhoneHubTray(Shelf* shelf);
PhoneHubTray(const PhoneHubTray&) = delete;
......@@ -51,29 +51,37 @@ class ASH_EXPORT PhoneHubTray
TrayBubbleView* GetBubbleView() override;
const char* GetClassName() const override;
views::View* content_view_for_testing() { return content_view_; }
private:
// TrayBubbleView::Delegate:
base::string16 GetAccessibleNameForBubble() override;
bool ShouldEnableExtraKeyboardAccessibility() override;
void HideBubble(const TrayBubbleView* bubble_view) override;
// chromeos::phonehub::FeatureStatusProvider::Observer:
void OnFeatureStatusChanged() override;
// PhoneHubUiController::Observer:
void OnPhoneHubUiStateChanged() override;
// Updates the visibility of the tray in the shelf based on the feature is
// enabled.
void UpdateVisibility();
// Cleans up |phone_hub_manager_| by removing all observers.
void CleanUpPhoneHubManager();
// Icon of the tray. Unowned.
views::ImageView* icon_;
// The PhoneHubManager that provides data for the UI.
chromeos::phonehub::PhoneHubManager* phone_hub_manager_ = nullptr;
// Controls the main content view displayed in the bubble based on the current
// PhoneHub state.
std::unique_ptr<PhoneHubUiController> ui_controller_;
// The bubble that appears after clicking the tray button.
std::unique_ptr<TrayBubbleWrapper> bubble_;
// The main content view of the bubble, which changes depending on the state.
// Unowned.
views::View* content_view_;
ScopedObserver<PhoneHubUiController, PhoneHubUiController::Observer>
observed_phone_hub_ui_controller_{this};
};
} // namespace ash
......
......@@ -15,6 +15,7 @@
#include "chromeos/components/phonehub/fake_phone_hub_manager.h"
#include "chromeos/constants/chromeos_features.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/events/event.h"
namespace ash {
......@@ -86,26 +87,26 @@ class PhoneHubTrayTest : public AshTestBase {
protected:
PhoneHubTray* phone_hub_tray_ = nullptr;
chromeos::phonehub::FakePhoneHubManager phone_hub_manager_;
MockNewWindowDelegate new_window_delegate_;
base::test::ScopedFeatureList feature_list_;
MockNewWindowDelegate new_window_delegate_;
};
TEST_F(PhoneHubTrayTest, SetPhoneHubManager) {
// Set a new manager.
chromeos::phonehub::FakePhoneHubManager new_manager;
new_manager.fake_feature_status_provider()->SetStatus(
chromeos::phonehub::FeatureStatus::kEligiblePhoneButNotSetUp);
chromeos::phonehub::FeatureStatus::kEnabledAndConnected);
phone_hub_tray_->SetPhoneHubManager(&new_manager);
EXPECT_TRUE(phone_hub_tray_->GetVisible());
// Changing the old manager should have no effect.
GetFeatureStatusProvider()->SetStatus(
chromeos::phonehub::FeatureStatus::kDisabled);
chromeos::phonehub::FeatureStatus::kNotEligibleForFeature);
EXPECT_TRUE(phone_hub_tray_->GetVisible());
// Only the new manager should work.
new_manager.fake_feature_status_provider()->SetStatus(
chromeos::phonehub::FeatureStatus::kDisabled);
chromeos::phonehub::FeatureStatus::kNotEligibleForFeature);
EXPECT_FALSE(phone_hub_tray_->GetVisible());
// Set no manager.
......@@ -166,4 +167,33 @@ TEST_F(PhoneHubTrayTest, StartNotificationSetUpFlow) {
ClickOnAndWait(notification_opt_in_view()->set_up_button_for_testing());
}
TEST_F(PhoneHubTrayTest, HideTrayItemOnUiStateChange) {
ClickTrayButton();
EXPECT_TRUE(phone_hub_tray_->is_active());
GetFeatureStatusProvider()->SetStatus(
chromeos::phonehub::FeatureStatus::kNotEligibleForFeature);
EXPECT_FALSE(phone_hub_tray_->is_active());
EXPECT_FALSE(phone_hub_tray_->GetVisible());
}
TEST_F(PhoneHubTrayTest, TransitionContentView) {
ClickTrayButton();
EXPECT_TRUE(phone_hub_tray_->is_active());
auto* content_view = phone_hub_tray_->content_view_for_testing();
EXPECT_TRUE(content_view);
// TODO(tengs) Test the actual view id.
EXPECT_EQ(0, content_view->GetID());
GetFeatureStatusProvider()->SetStatus(
chromeos::phonehub::FeatureStatus::kEnabledButDisconnected);
content_view = phone_hub_tray_->content_view_for_testing();
EXPECT_TRUE(content_view);
// TODO(tengs) Test the actual view id.
EXPECT_EQ(0, content_view->GetID());
}
} // namespace ash
// 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 "ash/system/phonehub/phone_hub_ui_controller.h"
#include "ash/system/phonehub/bluetooth_disabled_view.h"
#include "ash/system/phonehub/connection_error_view.h"
#include "ash/system/phonehub/initial_connecting_view.h"
#include "ash/system/phonehub/onboarding_view.h"
#include "ash/system/phonehub/phone_connected_view.h"
#include "ash/system/phonehub/phone_status_view.h"
#include "base/logging.h"
#include "chromeos/components/phonehub/phone_hub_manager.h"
using FeatureStatus = chromeos::phonehub::FeatureStatus;
namespace ash {
PhoneHubUiController::PhoneHubUiController() = default;
PhoneHubUiController::~PhoneHubUiController() {
CleanUpPhoneHubManager();
}
void PhoneHubUiController::SetPhoneHubManager(
chromeos::phonehub::PhoneHubManager* phone_hub_manager) {
if (phone_hub_manager == phone_hub_manager_)
return;
CleanUpPhoneHubManager();
phone_hub_manager_ = phone_hub_manager;
if (phone_hub_manager_) {
phone_hub_manager_->GetFeatureStatusProvider()->AddObserver(this);
phone_hub_manager_->GetOnboardingUiTracker()->AddObserver(this);
}
UpdateUiState();
}
std::unique_ptr<views::View> PhoneHubUiController::CreateStatusHeaderView() {
if (!phone_hub_manager_)
return nullptr;
return std::make_unique<PhoneStatusView>(phone_hub_manager_->GetPhoneModel());
}
std::unique_ptr<views::View> PhoneHubUiController::CreateContentView(
TrayBubbleView* bubble_view) {
switch (ui_state_) {
case UiState::kHidden:
return nullptr;
case UiState::kOnboardingWithoutPhone:
// TODO(tengs): distinguish this onboarding with phone state.
FALLTHROUGH;
case UiState::kOnboardingWithPhone:
return std::make_unique<OnboardingView>();
case UiState::kBluetoothDisabled:
return std::make_unique<BluetoothDisabledView>();
case UiState::kInitialConnecting:
return std::make_unique<InitialConnectingView>();
case UiState::kPhoneConnecting:
return std::make_unique<ConnectionErrorView>(
ConnectionErrorView::ErrorStatus::kReconnecting);
case UiState::kConnectionError:
return std::make_unique<ConnectionErrorView>(
ConnectionErrorView::ErrorStatus::kDisconnected);
case UiState::kPhoneConnected:
return std::make_unique<PhoneConnectedView>(bubble_view,
phone_hub_manager_);
}
}
void PhoneHubUiController::AddObserver(Observer* observer) {
observer_list_.AddObserver(observer);
}
void PhoneHubUiController::RemoveObserver(Observer* observer) {
observer_list_.RemoveObserver(observer);
}
void PhoneHubUiController::OnFeatureStatusChanged() {
UpdateUiState();
}
void PhoneHubUiController::OnShouldShowOnboardingUiChanged() {
UpdateUiState();
}
void PhoneHubUiController::UpdateUiState() {
auto new_state = GetUiStateFromPhoneHubManager();
if (new_state == ui_state_)
return;
ui_state_ = new_state;
for (auto& observer : observer_list_)
observer.OnPhoneHubUiStateChanged();
}
PhoneHubUiController::UiState
PhoneHubUiController::GetUiStateFromPhoneHubManager() {
if (!phone_hub_manager_)
return UiState::kHidden;
auto feature_status =
phone_hub_manager_->GetFeatureStatusProvider()->GetStatus();
auto* tracker = phone_hub_manager_->GetOnboardingUiTracker();
bool should_show_onboarding_ui = tracker->ShouldShowOnboardingUi();
switch (feature_status) {
case FeatureStatus::kNotEligibleForFeature:
return UiState::kHidden;
case FeatureStatus::kEligiblePhoneButNotSetUp:
return should_show_onboarding_ui ? UiState::kOnboardingWithPhone
: UiState::kHidden;
case FeatureStatus::kDisabled:
return should_show_onboarding_ui ? UiState::kOnboardingWithoutPhone
: UiState::kHidden;
case FeatureStatus::kPhoneSelectedAndPendingSetup:
return UiState::kInitialConnecting;
case FeatureStatus::kUnavailableBluetoothOff:
return UiState::kBluetoothDisabled;
case FeatureStatus::kEnabledButDisconnected:
return UiState::kConnectionError;
case FeatureStatus::kEnabledAndConnecting:
return UiState::kPhoneConnecting;
case FeatureStatus::kEnabledAndConnected:
return UiState::kPhoneConnected;
}
}
void PhoneHubUiController::CleanUpPhoneHubManager() {
if (!phone_hub_manager_)
return;
phone_hub_manager_->GetFeatureStatusProvider()->RemoveObserver(this);
phone_hub_manager_->GetOnboardingUiTracker()->RemoveObserver(this);
}
} // namespace ash
// 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 ASH_SYSTEM_PHONEHUB_PHONE_HUB_UI_CONTROLLER_H_
#define ASH_SYSTEM_PHONEHUB_PHONE_HUB_UI_CONTROLLER_H_
#include "ash/ash_export.h"
#include "base/observer_list.h"
#include "base/observer_list_types.h"
#include "chromeos/components/phonehub/feature_status_provider.h"
#include "chromeos/components/phonehub/onboarding_ui_tracker.h"
namespace chromeos {
namespace phonehub {
class PhoneHubManager;
} // namespace phonehub
} // namespace chromeos
namespace views {
class View;
} // namespace views
namespace ash {
class TrayBubbleView;
// This controller translates the state received from PhoneHubManager into the
// corresponding main content view to be displayed in the tray bubble.
class ASH_EXPORT PhoneHubUiController
: public chromeos::phonehub::FeatureStatusProvider::Observer,
public chromeos::phonehub::OnboardingUiTracker::Observer {
public:
class Observer : public base::CheckedObserver {
public:
~Observer() override = default;
virtual void OnPhoneHubUiStateChanged() = 0;
};
// All the possible states that the main content view can be in. Each state
// has a corresponding view class.
enum class UiState {
kHidden = 0,
kOnboardingWithoutPhone,
kOnboardingWithPhone,
kBluetoothDisabled,
kInitialConnecting,
kPhoneConnecting,
kConnectionError,
kPhoneConnected,
};
PhoneHubUiController();
PhoneHubUiController(const PhoneHubUiController&) = delete;
~PhoneHubUiController() override;
PhoneHubUiController& operator=(const PhoneHubUiController&) = delete;
// Sets the PhoneHubManager that provides the data to drive the UI.
void SetPhoneHubManager(
chromeos::phonehub::PhoneHubManager* phone_hub_manager);
// Creates the corresponding content view for the current UI state.
// |bubble_view| will be the parent the created content view.
std::unique_ptr<views::View> CreateContentView(TrayBubbleView* bubble_view);
// Creates the header view displaying the phone status.
std::unique_ptr<views::View> CreateStatusHeaderView();
// Observer functions.
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
UiState ui_state() const { return ui_state_; }
private:
// chromeos::phonehub::FeatureStatusProvider::Observer:
void OnFeatureStatusChanged() override;
// chromeos::phonehub::OnboardingUiTracker::Observer:
void OnShouldShowOnboardingUiChanged() override;
// Updates the current UI state and notifies observers.
void UpdateUiState();
// Returns the UiState from the PhoneHubManager.
UiState GetUiStateFromPhoneHubManager();
// Cleans up |phone_hub_manager_| by removing all observers.
void CleanUpPhoneHubManager();
// The PhoneHubManager that provides data for the UI.
chromeos::phonehub::PhoneHubManager* phone_hub_manager_ = nullptr;
// The current UI state.
UiState ui_state_ = UiState::kHidden;
// Registered observers.
base::ObserverList<Observer> observer_list_;
};
} // namespace ash
#endif // ASH_SYSTEM_PHONEHUB_PHONE_HUB_UI_CONTROLLER_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 "ash/system/phonehub/phone_hub_ui_controller.h"
#include "ash/test/ash_test_base.h"
#include "chromeos/components/phonehub/fake_phone_hub_manager.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/views/view.h"
using FeatureStatus = chromeos::phonehub::FeatureStatus;
namespace ash {
class PhoneHubUiControllerTest : public AshTestBase,
public PhoneHubUiController::Observer {
public:
PhoneHubUiControllerTest() = default;
~PhoneHubUiControllerTest() override { controller_.RemoveObserver(this); }
// AshTestBase:
void SetUp() override {
AshTestBase::SetUp();
controller_.AddObserver(this);
GetFeatureStatusProvider()->SetStatus(FeatureStatus::kEnabledAndConnected);
GetOnboardingUiTracker()->SetShouldShowOnboardingUi(false);
controller_.SetPhoneHubManager(&phone_hub_manager_);
CHECK(ui_state_changed_);
ui_state_changed_ = false;
}
chromeos::phonehub::FakeFeatureStatusProvider* GetFeatureStatusProvider() {
return phone_hub_manager_.fake_feature_status_provider();
}
chromeos::phonehub::FakeOnboardingUiTracker* GetOnboardingUiTracker() {
return phone_hub_manager_.fake_onboarding_ui_tracker();
}
protected:
// PhoneHubUiController::Observer:
void OnPhoneHubUiStateChanged() override {
CHECK(!ui_state_changed_);
ui_state_changed_ = true;
}
PhoneHubUiController controller_;
chromeos::phonehub::FakePhoneHubManager phone_hub_manager_;
bool ui_state_changed_ = false;
};
TEST_F(PhoneHubUiControllerTest, NotEligibleForFeature) {
GetFeatureStatusProvider()->SetStatus(FeatureStatus::kNotEligibleForFeature);
EXPECT_EQ(PhoneHubUiController::UiState::kHidden, controller_.ui_state());
EXPECT_TRUE(ui_state_changed_);
EXPECT_FALSE(controller_.CreateContentView(/*bubble_view=*/nullptr).get());
}
TEST_F(PhoneHubUiControllerTest, OnboardingNotEligible) {
GetFeatureStatusProvider()->SetStatus(FeatureStatus::kDisabled);
EXPECT_EQ(PhoneHubUiController::UiState::kHidden, controller_.ui_state());
EXPECT_FALSE(controller_.CreateContentView(/*bubble_view=*/nullptr).get());
}
TEST_F(PhoneHubUiControllerTest, ShowOnboardingUi_WithoutPhone) {
GetFeatureStatusProvider()->SetStatus(FeatureStatus::kDisabled);
EXPECT_TRUE(ui_state_changed_);
ui_state_changed_ = false;
GetOnboardingUiTracker()->SetShouldShowOnboardingUi(true);
EXPECT_TRUE(ui_state_changed_);
EXPECT_EQ(PhoneHubUiController::UiState::kOnboardingWithoutPhone,
controller_.ui_state());
auto content_view = controller_.CreateContentView(/*bubble_view=*/nullptr);
// TODO(tengs): Test the actual view id.
EXPECT_EQ(0, content_view->GetID());
}
TEST_F(PhoneHubUiControllerTest, ShowOnboardingUi_WithPhone) {
GetFeatureStatusProvider()->SetStatus(
FeatureStatus::kEligiblePhoneButNotSetUp);
EXPECT_TRUE(ui_state_changed_);
ui_state_changed_ = false;
GetOnboardingUiTracker()->SetShouldShowOnboardingUi(true);
EXPECT_TRUE(ui_state_changed_);
EXPECT_EQ(PhoneHubUiController::UiState::kOnboardingWithPhone,
controller_.ui_state());
auto content_view = controller_.CreateContentView(/*bubble_view=*/nullptr);
// TODO(tengs): Test the actual view id.
EXPECT_EQ(0, content_view->GetID());
}
TEST_F(PhoneHubUiControllerTest, PhoneConnectingForOnboarding) {
GetFeatureStatusProvider()->SetStatus(
FeatureStatus::kPhoneSelectedAndPendingSetup);
EXPECT_EQ(PhoneHubUiController::UiState::kInitialConnecting,
controller_.ui_state());
auto content_view = controller_.CreateContentView(/*bubble_view=*/nullptr);
// TODO(tengs): Test the actual view id.
EXPECT_EQ(0, content_view->GetID());
}
TEST_F(PhoneHubUiControllerTest, BluetoothOff) {
GetFeatureStatusProvider()->SetStatus(
FeatureStatus::kUnavailableBluetoothOff);
EXPECT_EQ(PhoneHubUiController::UiState::kBluetoothDisabled,
controller_.ui_state());
auto content_view = controller_.CreateContentView(/*bubble_view=*/nullptr);
// TODO(tengs): Test the actual view id.
EXPECT_EQ(0, content_view->GetID());
}
TEST_F(PhoneHubUiControllerTest, PhoneDisconnected) {
GetFeatureStatusProvider()->SetStatus(FeatureStatus::kEnabledButDisconnected);
EXPECT_EQ(PhoneHubUiController::UiState::kConnectionError,
controller_.ui_state());
auto content_view = controller_.CreateContentView(/*bubble_view=*/nullptr);
// TODO(tengs): Test the actual view id.
EXPECT_EQ(0, content_view->GetID());
}
TEST_F(PhoneHubUiControllerTest, PhoneConnecting) {
GetFeatureStatusProvider()->SetStatus(FeatureStatus::kEnabledAndConnecting);
EXPECT_EQ(PhoneHubUiController::UiState::kPhoneConnecting,
controller_.ui_state());
auto content_view = controller_.CreateContentView(/*bubble_view=*/nullptr);
// TODO(tengs): Test the actual view id.
EXPECT_EQ(0, content_view->GetID());
}
TEST_F(PhoneHubUiControllerTest, PhoneConnected) {
GetFeatureStatusProvider()->SetStatus(FeatureStatus::kEnabledAndConnected);
EXPECT_EQ(PhoneHubUiController::UiState::kPhoneConnected,
controller_.ui_state());
auto content_view = controller_.CreateContentView(/*bubble_view=*/nullptr);
// TODO(tengs): Test the actual view id.
EXPECT_EQ(0, content_view->GetID());
}
} // namespace ash
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