| // Copyright 2020 The Chromium Authors |
| // 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_tray.h" |
| #include <string> |
| |
| #include "ash/constants/ash_features.h" |
| #include "ash/focus_cycler.h" |
| #include "ash/public/cpp/test/test_new_window_delegate.h" |
| #include "ash/shell.h" |
| #include "ash/system/phonehub/multidevice_feature_opt_in_view.h" |
| #include "ash/system/phonehub/phone_hub_ui_controller.h" |
| #include "ash/system/phonehub/phone_hub_view_ids.h" |
| #include "ash/system/status_area_widget.h" |
| #include "ash/system/status_area_widget_test_helper.h" |
| #include "ash/system/toast/anchored_nudge.h" |
| #include "ash/system/toast/anchored_nudge_manager_impl.h" |
| #include "ash/test/ash_test_base.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/task_environment.h" |
| #include "base/time/time.h" |
| #include "chromeos/ash/components/phonehub/fake_connection_scheduler.h" |
| #include "chromeos/ash/components/phonehub/fake_icon_decoder.h" |
| #include "chromeos/ash/components/phonehub/fake_multidevice_feature_access_manager.h" |
| #include "chromeos/ash/components/phonehub/fake_phone_hub_manager.h" |
| #include "chromeos/ash/components/phonehub/phone_model_test_util.h" |
| #include "chromeos/ash/services/multidevice_setup/public/mojom/multidevice_setup.mojom-shared.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "ui/compositor/layer_animator.h" |
| #include "ui/compositor/scoped_animation_duration_scale_mode.h" |
| #include "ui/events/event.h" |
| #include "ui/events/keycodes/keyboard_codes_posix.h" |
| #include "ui/views/controls/button/button.h" |
| #include "ui/views/controls/menu/menu_controller.h" |
| #include "ui/views/controls/menu/menu_item_view.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| using multidevice_setup::mojom::Feature; |
| using AccessStatus = phonehub::MultideviceFeatureAccessManager::AccessStatus; |
| using AccessProhibitedReason = |
| phonehub::MultideviceFeatureAccessManager::AccessProhibitedReason; |
| |
| constexpr base::TimeDelta kConnectingViewGracePeriod = base::Seconds(40); |
| constexpr char kTrayBackgroundViewHistogramName[] = |
| "Ash.StatusArea.TrayBackgroundView.Pressed"; |
| const std::string kPhoneHubNudgeId = "PhoneHubNudge"; |
| |
| // A mock implementation of |NewWindowDelegate| for use in tests. |
| class MockNewWindowDelegate : public testing::NiceMock<TestNewWindowDelegate> { |
| public: |
| // TestNewWindowDelegate: |
| MOCK_METHOD(void, |
| OpenUrl, |
| (const GURL& url, OpenUrlFrom from, Disposition disposition), |
| (override)); |
| }; |
| |
| } // namespace |
| |
| class PhoneHubTrayTest : public AshTestBase { |
| public: |
| PhoneHubTrayTest() |
| : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {} |
| ~PhoneHubTrayTest() override = default; |
| |
| // AshTestBase: |
| void SetUp() override { |
| feature_list_.InitWithFeatures( |
| /*enabled_features=*/{features::kPhoneHub, |
| features::kPhoneHubCameraRoll, |
| features::kEcheLauncher, features::kEcheSWA, |
| features::kEcheNetworkConnectionState}, |
| /*disabled_features=*/{}); |
| auto delegate = std::make_unique<MockNewWindowDelegate>(); |
| new_window_delegate_ = delegate.get(); |
| delegate_provider_ = |
| std::make_unique<TestNewWindowDelegateProvider>(std::move(delegate)); |
| AshTestBase::SetUp(); |
| |
| phone_hub_tray_ = |
| StatusAreaWidgetTestHelper::GetStatusAreaWidget()->phone_hub_tray(); |
| // Disable pulse animation so the tests will not hang. |
| ui::ScopedAnimationDurationScaleMode duration_mode( |
| ui::ScopedAnimationDurationScaleMode::ZERO_DURATION); |
| |
| GetFeatureStatusProvider()->SetStatus( |
| phonehub::FeatureStatus::kEnabledAndConnected); |
| phone_hub_tray_->SetPhoneHubManager(&phone_hub_manager_); |
| |
| phone_hub_manager_.mutable_phone_model()->SetPhoneStatusModel( |
| phonehub::CreateFakePhoneStatusModel()); |
| phone_hub_manager_.fake_recent_apps_interaction_handler() |
| ->set_ui_state_for_testing(phonehub::RecentAppsInteractionHandler:: |
| RecentAppsUiState::ITEMS_VISIBLE); |
| } |
| |
| void TearDown() override { |
| delegate_provider_.reset(); |
| AshTestBase::TearDown(); |
| } |
| |
| phonehub::FakeFeatureStatusProvider* GetFeatureStatusProvider() { |
| return phone_hub_manager_.fake_feature_status_provider(); |
| } |
| |
| phonehub::FakeMultideviceFeatureAccessManager* |
| GetMultideviceFeatureAccessManager() { |
| return phone_hub_manager_.fake_multidevice_feature_access_manager(); |
| } |
| |
| phonehub::FakeConnectionScheduler* GetConnectionScheduler() { |
| return phone_hub_manager_.fake_connection_scheduler(); |
| } |
| |
| phonehub::FakeOnboardingUiTracker* GetOnboardingUiTracker() { |
| return phone_hub_manager_.fake_onboarding_ui_tracker(); |
| } |
| |
| phonehub::AppStreamLauncherDataModel* GetAppStreamLauncherDataModel() { |
| return phone_hub_manager_.fake_app_stream_launcher_data_model(); |
| } |
| |
| void ClickTrayButton() { LeftClickOn(phone_hub_tray_); } |
| |
| // When first connecting, the connecting view is shown for 30 seconds when |
| // disconnected, so in order to show the disconnecting view, we need to fast |
| // forward time. |
| void FastForwardByConnectingViewGracePeriod() { |
| task_environment()->FastForwardBy(kConnectingViewGracePeriod); |
| } |
| |
| MockNewWindowDelegate& new_window_delegate() { return *new_window_delegate_; } |
| |
| views::View* bubble_view() { return phone_hub_tray_->GetBubbleView(); } |
| |
| views::View* content_view() { |
| return phone_hub_tray_->content_view_for_testing(); |
| } |
| |
| PhoneHubTray* phone_hub_tray() { return phone_hub_tray_; } |
| |
| MultideviceFeatureOptInView* multidevice_feature_opt_in_view() { |
| return static_cast<MultideviceFeatureOptInView*>(bubble_view()->GetViewByID( |
| PhoneHubViewID::kMultideviceFeatureOptInView)); |
| } |
| |
| views::View* onboarding_main_view() { |
| return bubble_view()->GetViewByID(PhoneHubViewID::kOnboardingMainView); |
| } |
| |
| views::Button* onboarding_get_started_button() { |
| return static_cast<views::Button*>(bubble_view()->GetViewByID( |
| PhoneHubViewID::kOnboardingGetStartedButton)); |
| } |
| |
| views::Button* disconnected_refresh_button() { |
| return static_cast<views::Button*>( |
| bubble_view()->GetViewByID(PhoneHubViewID::kDisconnectedRefreshButton)); |
| } |
| |
| views::Button* disconnected_learn_more_button() { |
| return static_cast<views::Button*>(bubble_view()->GetViewByID( |
| PhoneHubViewID::kDisconnectedLearnMoreButton)); |
| } |
| |
| views::Button* bluetooth_disabled_learn_more_button() { |
| return static_cast<views::Button*>(bubble_view()->GetViewByID( |
| PhoneHubViewID::kBluetoothDisabledLearnMoreButton)); |
| } |
| |
| views::Button* notification_opt_in_set_up_button() { |
| return static_cast<views::Button*>(bubble_view()->GetViewByID( |
| PhoneHubViewID::kSubFeatureOptInConfirmButton)); |
| } |
| |
| views::Button* notification_opt_in_dismiss_button() { |
| return static_cast<views::Button*>(bubble_view()->GetViewByID( |
| PhoneHubViewID::kSubFeatureOptInDismissButton)); |
| } |
| |
| protected: |
| raw_ptr<PhoneHubTray, DanglingUntriaged> phone_hub_tray_ = nullptr; |
| phonehub::FakePhoneHubManager phone_hub_manager_; |
| base::test::ScopedFeatureList feature_list_; |
| raw_ptr<MockNewWindowDelegate, DanglingUntriaged> new_window_delegate_; |
| std::unique_ptr<TestNewWindowDelegateProvider> delegate_provider_; |
| }; |
| |
| TEST_F(PhoneHubTrayTest, SetPhoneHubManager) { |
| // Set a new manager. |
| phonehub::FakePhoneHubManager new_manager; |
| new_manager.fake_feature_status_provider()->SetStatus( |
| 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( |
| phonehub::FeatureStatus::kNotEligibleForFeature); |
| EXPECT_TRUE(phone_hub_tray_->GetVisible()); |
| |
| // Only the new manager should work. |
| new_manager.fake_feature_status_provider()->SetStatus( |
| phonehub::FeatureStatus::kNotEligibleForFeature); |
| EXPECT_FALSE(phone_hub_tray_->GetVisible()); |
| |
| // Set no manager. |
| phone_hub_tray_->SetPhoneHubManager(nullptr); |
| EXPECT_FALSE(phone_hub_tray_->GetVisible()); |
| } |
| |
| TEST_F(PhoneHubTrayTest, ClickTrayButton) { |
| EXPECT_TRUE(phone_hub_tray_->GetVisible()); |
| |
| EXPECT_FALSE(phone_hub_tray_->is_active()); |
| |
| ClickTrayButton(); |
| EXPECT_TRUE(phone_hub_tray_->is_active()); |
| // We only want to focus the bubble widget when it's opened by keyboard. |
| EXPECT_FALSE(phone_hub_tray_->GetBubbleView()->GetWidget()->IsActive()); |
| |
| ClickTrayButton(); |
| EXPECT_FALSE(phone_hub_tray_->is_active()); |
| } |
| |
| TEST_F(PhoneHubTrayTest, FocusBubbleWhenOpenedByKeyboard) { |
| EXPECT_TRUE(phone_hub_tray_->GetVisible()); |
| |
| Shell::Get()->focus_cycler()->FocusWidget(phone_hub_tray_->GetWidget()); |
| phone_hub_tray_->RequestFocus(); |
| PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN); |
| |
| // Generate a tab key press. |
| ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow()); |
| generator.PressKey(ui::KeyboardCode::VKEY_TAB, ui::EF_NONE); |
| |
| // The bubble widget should get focus when it's opened by keyboard and the tab |
| // key is pressed. |
| EXPECT_TRUE(phone_hub_tray_->is_active()); |
| EXPECT_TRUE(phone_hub_tray_->GetBubbleView()->GetWidget()->IsActive()); |
| } |
| |
| TEST_F(PhoneHubTrayTest, ShowOptInViewWhenNotificationAccessNotGranted) { |
| GetMultideviceFeatureAccessManager()->SetNotificationAccessStatusInternal( |
| AccessStatus::kAvailableButNotGranted, AccessProhibitedReason::kUnknown); |
| GetMultideviceFeatureAccessManager()->SetCameraRollAccessStatusInternal( |
| AccessStatus::kAccessGranted); |
| |
| ClickTrayButton(); |
| |
| EXPECT_TRUE(multidevice_feature_opt_in_view()); |
| EXPECT_TRUE(multidevice_feature_opt_in_view()->GetVisible()); |
| |
| // Simulate a click on "Dismiss" button. |
| LeftClickOn(notification_opt_in_dismiss_button()); |
| |
| // Clicking on "Dismiss" should hide the view and also disable the ability to |
| // show it again. |
| EXPECT_FALSE(multidevice_feature_opt_in_view()->GetVisible()); |
| EXPECT_TRUE(GetMultideviceFeatureAccessManager() |
| ->HasMultideviceFeatureSetupUiBeenDismissed()); |
| } |
| |
| TEST_F(PhoneHubTrayTest, ShowOptInViewWhenCameraRollAccessNotGranted) { |
| GetMultideviceFeatureAccessManager()->SetNotificationAccessStatusInternal( |
| AccessStatus::kAccessGranted, AccessProhibitedReason::kUnknown); |
| GetMultideviceFeatureAccessManager()->SetCameraRollAccessStatusInternal( |
| AccessStatus::kAvailableButNotGranted); |
| GetMultideviceFeatureAccessManager()->SetFeatureReadyForAccess( |
| Feature::kPhoneHubCameraRoll); |
| |
| ClickTrayButton(); |
| |
| EXPECT_TRUE(multidevice_feature_opt_in_view()); |
| EXPECT_TRUE(multidevice_feature_opt_in_view()->GetVisible()); |
| |
| // Simulate a click on "Dismiss" button. |
| LeftClickOn(notification_opt_in_dismiss_button()); |
| |
| // Clicking on "Dismiss" should hide the view and also disable the ability to |
| // show it again. |
| EXPECT_FALSE(multidevice_feature_opt_in_view()->GetVisible()); |
| EXPECT_TRUE(GetMultideviceFeatureAccessManager() |
| ->HasMultideviceFeatureSetupUiBeenDismissed()); |
| } |
| |
| TEST_F(PhoneHubTrayTest, HideOptInViewWhenAllFeatureAccessHasBeenGranted) { |
| GetMultideviceFeatureAccessManager()->SetNotificationAccessStatusInternal( |
| AccessStatus::kAccessGranted, AccessProhibitedReason::kUnknown); |
| GetMultideviceFeatureAccessManager()->SetCameraRollAccessStatusInternal( |
| AccessStatus::kAccessGranted); |
| |
| ClickTrayButton(); |
| |
| EXPECT_TRUE(multidevice_feature_opt_in_view()); |
| EXPECT_FALSE(multidevice_feature_opt_in_view()->GetVisible()); |
| } |
| |
| TEST_F( |
| PhoneHubTrayTest, |
| HideOptInViewWhenNotificationAccessIsProhibitedAndCameraRollAccessIsGranted) { |
| GetMultideviceFeatureAccessManager()->SetNotificationAccessStatusInternal( |
| AccessStatus::kProhibited, |
| AccessProhibitedReason::kDisabledByPhonePolicy); |
| GetMultideviceFeatureAccessManager()->SetCameraRollAccessStatusInternal( |
| AccessStatus::kAccessGranted); |
| |
| ClickTrayButton(); |
| |
| EXPECT_TRUE(multidevice_feature_opt_in_view()); |
| EXPECT_FALSE(multidevice_feature_opt_in_view()->GetVisible()); |
| } |
| |
| TEST_F(PhoneHubTrayTest, StartMultideviceFeatureSetUpFlow) { |
| GetMultideviceFeatureAccessManager()->SetNotificationAccessStatusInternal( |
| AccessStatus::kAvailableButNotGranted, AccessProhibitedReason::kUnknown); |
| GetMultideviceFeatureAccessManager()->SetCameraRollAccessStatusInternal( |
| AccessStatus::kAvailableButNotGranted); |
| GetMultideviceFeatureAccessManager()->SetFeatureReadyForAccess( |
| Feature::kPhoneHubCameraRoll); |
| |
| ClickTrayButton(); |
| EXPECT_TRUE(multidevice_feature_opt_in_view()); |
| EXPECT_TRUE(multidevice_feature_opt_in_view()->GetVisible()); |
| |
| // Clicking on the set up button should open the corresponding settings page |
| // for the notification set up flow. |
| EXPECT_CALL(new_window_delegate(), |
| OpenUrl(GURL("chrome://os-settings/multidevice/" |
| "features?showPhonePermissionSetupDialog&mode=5"), |
| NewWindowDelegate::OpenUrlFrom::kUserInteraction, |
| NewWindowDelegate::Disposition::kNewForegroundTab)); |
| |
| LeftClickOn(notification_opt_in_set_up_button()); |
| |
| // Simulate that notification access has been granted. |
| GetMultideviceFeatureAccessManager()->SetNotificationAccessStatusInternal( |
| AccessStatus::kAccessGranted, AccessProhibitedReason::kUnknown); |
| // Simulate that camera roll access has been granted. |
| GetMultideviceFeatureAccessManager()->SetCameraRollAccessStatusInternal( |
| AccessStatus::kAccessGranted); |
| |
| // Bubble has been dismissed, opening again. |
| ClickTrayButton(); |
| |
| // This view should be dismissed. |
| EXPECT_FALSE(multidevice_feature_opt_in_view()->GetVisible()); |
| |
| // Simulate that notification access has been revoked by the phone. |
| GetMultideviceFeatureAccessManager()->SetNotificationAccessStatusInternal( |
| AccessStatus::kAvailableButNotGranted, AccessProhibitedReason::kUnknown); |
| |
| // This view should show up again. |
| EXPECT_TRUE(multidevice_feature_opt_in_view()->GetVisible()); |
| } |
| |
| TEST_F(PhoneHubTrayTest, StartAllPermissionSetUpFlow) { |
| GetMultideviceFeatureAccessManager()->SetNotificationAccessStatusInternal( |
| AccessStatus::kAvailableButNotGranted, AccessProhibitedReason::kUnknown); |
| GetMultideviceFeatureAccessManager()->SetCameraRollAccessStatusInternal( |
| AccessStatus::kAvailableButNotGranted); |
| GetMultideviceFeatureAccessManager()->SetAppsAccessStatusInternal( |
| AccessStatus::kAvailableButNotGranted); |
| GetMultideviceFeatureAccessManager()->SetFeatureReadyForAccess( |
| Feature::kEche); |
| GetMultideviceFeatureAccessManager()->SetFeatureReadyForAccess( |
| Feature::kPhoneHubCameraRoll); |
| |
| ClickTrayButton(); |
| EXPECT_TRUE(multidevice_feature_opt_in_view()); |
| EXPECT_TRUE(multidevice_feature_opt_in_view()->GetVisible()); |
| |
| // Clicking on the set up button should open the corresponding settings page |
| // for the notification set up flow. |
| EXPECT_CALL(new_window_delegate(), |
| OpenUrl(GURL("chrome://os-settings/multidevice/" |
| "features?showPhonePermissionSetupDialog&mode=7"), |
| NewWindowDelegate::OpenUrlFrom::kUserInteraction, |
| NewWindowDelegate::Disposition::kNewForegroundTab)); |
| |
| LeftClickOn(notification_opt_in_set_up_button()); |
| } |
| |
| TEST_F(PhoneHubTrayTest, StartNotificationAndAppSetUpFlow) { |
| GetMultideviceFeatureAccessManager()->SetNotificationAccessStatusInternal( |
| AccessStatus::kAvailableButNotGranted, AccessProhibitedReason::kUnknown); |
| GetMultideviceFeatureAccessManager()->SetAppsAccessStatusInternal( |
| AccessStatus::kAvailableButNotGranted); |
| GetMultideviceFeatureAccessManager()->SetCameraRollAccessStatusInternal( |
| AccessStatus::kAccessGranted); |
| GetMultideviceFeatureAccessManager()->SetFeatureReadyForAccess( |
| Feature::kEche); |
| |
| ClickTrayButton(); |
| EXPECT_TRUE(multidevice_feature_opt_in_view()); |
| EXPECT_TRUE(multidevice_feature_opt_in_view()->GetVisible()); |
| |
| // Clicking on the set up button should open the corresponding settings page |
| // for the notification set up flow. |
| EXPECT_CALL(new_window_delegate(), |
| OpenUrl(GURL("chrome://os-settings/multidevice/" |
| "features?showPhonePermissionSetupDialog&mode=4"), |
| NewWindowDelegate::OpenUrlFrom::kUserInteraction, |
| NewWindowDelegate::Disposition::kNewForegroundTab)); |
| |
| LeftClickOn(notification_opt_in_set_up_button()); |
| } |
| |
| TEST_F(PhoneHubTrayTest, StartNotificationAccessOnlySetUpFlow) { |
| GetMultideviceFeatureAccessManager()->SetNotificationAccessStatusInternal( |
| AccessStatus::kAvailableButNotGranted, AccessProhibitedReason::kUnknown); |
| GetMultideviceFeatureAccessManager()->SetAppsAccessStatusInternal( |
| AccessStatus::kAccessGranted); |
| GetMultideviceFeatureAccessManager()->SetCameraRollAccessStatusInternal( |
| AccessStatus::kAccessGranted); |
| |
| ClickTrayButton(); |
| EXPECT_TRUE(multidevice_feature_opt_in_view()); |
| EXPECT_TRUE(multidevice_feature_opt_in_view()->GetVisible()); |
| |
| // Clicking on the set up button should open the corresponding settings page |
| // for the notification set up flow. |
| EXPECT_CALL(new_window_delegate(), |
| OpenUrl(GURL("chrome://os-settings/multidevice/" |
| "features?showPhonePermissionSetupDialog&mode=1"), |
| NewWindowDelegate::OpenUrlFrom::kUserInteraction, |
| NewWindowDelegate::Disposition::kNewForegroundTab)); |
| |
| LeftClickOn(notification_opt_in_set_up_button()); |
| } |
| |
| TEST_F(PhoneHubTrayTest, StartAppsAccessOnlySetUpFlow) { |
| GetMultideviceFeatureAccessManager()->SetNotificationAccessStatusInternal( |
| AccessStatus::kAccessGranted, AccessProhibitedReason::kUnknown); |
| GetMultideviceFeatureAccessManager()->SetCameraRollAccessStatusInternal( |
| AccessStatus::kAccessGranted); |
| GetMultideviceFeatureAccessManager()->SetAppsAccessStatusInternal( |
| AccessStatus::kAvailableButNotGranted); |
| GetMultideviceFeatureAccessManager()->SetFeatureReadyForAccess( |
| Feature::kEche); |
| |
| ClickTrayButton(); |
| EXPECT_TRUE(multidevice_feature_opt_in_view()); |
| EXPECT_TRUE(multidevice_feature_opt_in_view()->GetVisible()); |
| |
| // Clicking on the set up button should open the corresponding settings page |
| // for the notification set up flow. |
| EXPECT_CALL(new_window_delegate(), |
| OpenUrl(GURL("chrome://os-settings/multidevice/" |
| "features?showPhonePermissionSetupDialog&mode=2"), |
| NewWindowDelegate::OpenUrlFrom::kUserInteraction, |
| NewWindowDelegate::Disposition::kNewForegroundTab)); |
| |
| LeftClickOn(notification_opt_in_set_up_button()); |
| } |
| |
| TEST_F(PhoneHubTrayTest, DoNotShowAppsAccessSetUpFlowIfFeatureIsNotReady) { |
| GetMultideviceFeatureAccessManager()->SetNotificationAccessStatusInternal( |
| AccessStatus::kAccessGranted, AccessProhibitedReason::kUnknown); |
| GetMultideviceFeatureAccessManager()->SetCameraRollAccessStatusInternal( |
| AccessStatus::kAccessGranted); |
| GetMultideviceFeatureAccessManager()->SetAppsAccessStatusInternal( |
| AccessStatus::kAvailableButNotGranted); |
| |
| ClickTrayButton(); |
| EXPECT_TRUE(multidevice_feature_opt_in_view()); |
| EXPECT_FALSE(multidevice_feature_opt_in_view()->GetVisible()); |
| } |
| |
| TEST_F(PhoneHubTrayTest, StartCameraRollOnlySetUpFlow) { |
| GetMultideviceFeatureAccessManager()->SetNotificationAccessStatusInternal( |
| AccessStatus::kAccessGranted, AccessProhibitedReason::kUnknown); |
| GetMultideviceFeatureAccessManager()->SetCameraRollAccessStatusInternal( |
| AccessStatus::kAvailableButNotGranted); |
| GetMultideviceFeatureAccessManager()->SetAppsAccessStatusInternal( |
| AccessStatus::kAccessGranted); |
| GetMultideviceFeatureAccessManager()->SetFeatureReadyForAccess( |
| Feature::kPhoneHubCameraRoll); |
| |
| ClickTrayButton(); |
| EXPECT_TRUE(multidevice_feature_opt_in_view()); |
| EXPECT_TRUE(multidevice_feature_opt_in_view()->GetVisible()); |
| |
| // Clicking on the set up button should open the corresponding settings page |
| // for the notification set up flow. |
| EXPECT_CALL(new_window_delegate(), |
| OpenUrl(GURL("chrome://os-settings/multidevice/" |
| "features?showPhonePermissionSetupDialog&mode=3"), |
| NewWindowDelegate::OpenUrlFrom::kUserInteraction, |
| NewWindowDelegate::Disposition::kNewForegroundTab)); |
| |
| LeftClickOn(notification_opt_in_set_up_button()); |
| } |
| |
| TEST_F(PhoneHubTrayTest, HideTrayItemOnUiStateChange) { |
| ClickTrayButton(); |
| EXPECT_TRUE(phone_hub_tray_->is_active()); |
| |
| GetFeatureStatusProvider()->SetStatus( |
| 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()); |
| |
| EXPECT_TRUE(content_view()); |
| EXPECT_EQ(PhoneHubViewID::kPhoneConnectedView, content_view()->GetID()); |
| |
| GetFeatureStatusProvider()->SetStatus( |
| phonehub::FeatureStatus::kEnabledButDisconnected); |
| FastForwardByConnectingViewGracePeriod(); |
| |
| EXPECT_TRUE(content_view()); |
| EXPECT_EQ(PhoneHubViewID::kDisconnectedView, content_view()->GetID()); |
| } |
| |
| TEST_F(PhoneHubTrayTest, StartOnboardingFlow) { |
| // Simulate a pending setup state to show the onboarding screen. |
| GetFeatureStatusProvider()->SetStatus( |
| phonehub::FeatureStatus::kEligiblePhoneButNotSetUp); |
| GetOnboardingUiTracker()->SetShouldShowOnboardingUi(true); |
| |
| ClickTrayButton(); |
| EXPECT_TRUE(phone_hub_tray_->is_active()); |
| EXPECT_EQ(PhoneHubViewID::kOnboardingView, content_view()->GetID()); |
| // It should display the onboarding main view. |
| EXPECT_TRUE(onboarding_main_view()); |
| EXPECT_TRUE(onboarding_main_view()->GetVisible()); |
| EXPECT_EQ(0u, GetOnboardingUiTracker()->handle_get_started_call_count()); |
| |
| // Simulate a click on the "Get started" button. |
| LeftClickOn(onboarding_get_started_button()); |
| // It should invoke the |HandleGetStarted| call. |
| EXPECT_EQ(1u, GetOnboardingUiTracker()->handle_get_started_call_count()); |
| } |
| |
| TEST_F(PhoneHubTrayTest, DismissOnboardingFlowByRightClickIcon) { |
| // Simulate a pending setup state to show the onboarding screen. |
| GetFeatureStatusProvider()->SetStatus( |
| phonehub::FeatureStatus::kEligiblePhoneButNotSetUp); |
| GetOnboardingUiTracker()->SetShouldShowOnboardingUi(true); |
| |
| RightClickOn(phone_hub_tray_); |
| EXPECT_TRUE(views::MenuController::GetActiveInstance()); |
| views::MenuItemView* menu_item_view = |
| views::MenuController::GetActiveInstance() |
| ->GetSelectedMenuItem() |
| ->GetMenuItemByID(/*kHidePhoneHubIconCommandId*/ 1); |
| LeftClickOn(menu_item_view); |
| |
| // Clicking "Ok, got it" button should dismiss the bubble, hide the tray icon, |
| // and disable the ability to show onboarding UI again. |
| EXPECT_FALSE(phone_hub_tray_->GetBubbleView()); |
| EXPECT_FALSE(phone_hub_tray_->GetVisible()); |
| EXPECT_FALSE(GetOnboardingUiTracker()->ShouldShowOnboardingUi()); |
| } |
| |
| TEST_F(PhoneHubTrayTest, ShouldNotShowMiniLauncherOnCloseBubble) { |
| GetFeatureStatusProvider()->SetStatus( |
| phonehub::FeatureStatus::kEnabledAndConnected); |
| |
| ClickTrayButton(); |
| EXPECT_TRUE(phone_hub_tray_->is_active()); |
| |
| // Simulate showing the app stream mini launcher |
| GetAppStreamLauncherDataModel()->SetShouldShowMiniLauncher(true); |
| EXPECT_TRUE(GetAppStreamLauncherDataModel()->GetShouldShowMiniLauncher()); |
| |
| // Simulate a click outside the bubble. |
| phone_hub_tray_->ClickedOutsideBubble(); |
| |
| // Clicking outside should dismiss the bubble and should not show the app |
| // stream mini launcher. |
| EXPECT_FALSE(phone_hub_tray_->GetBubbleView()); |
| EXPECT_TRUE(phone_hub_tray_->GetVisible()); |
| EXPECT_FALSE(GetAppStreamLauncherDataModel()->GetShouldShowMiniLauncher()); |
| |
| // Opening the bubble again should still have the app stream mini launcher |
| // not shown. |
| ClickTrayButton(); |
| EXPECT_TRUE(phone_hub_tray_->GetBubbleView()); |
| EXPECT_TRUE(phone_hub_tray_->GetVisible()); |
| EXPECT_FALSE(GetAppStreamLauncherDataModel()->GetShouldShowMiniLauncher()); |
| } |
| |
| TEST_F(PhoneHubTrayTest, ClickButtonsOnDisconnectedView) { |
| // Simulates a phone disconnected error state to show the disconnected view. |
| GetFeatureStatusProvider()->SetStatus( |
| phonehub::FeatureStatus::kEnabledButDisconnected); |
| FastForwardByConnectingViewGracePeriod(); |
| |
| EXPECT_EQ(0u, GetConnectionScheduler()->num_schedule_connection_now_calls()); |
| |
| // In error state, clicking the tray button should schedule a connection |
| // attempt. |
| ClickTrayButton(); |
| EXPECT_TRUE(phone_hub_tray_->is_active()); |
| EXPECT_EQ(1u, GetConnectionScheduler()->num_schedule_connection_now_calls()); |
| |
| // Make sure the testing environment is in disconnected view. |
| EXPECT_TRUE(content_view()); |
| EXPECT_EQ(PhoneHubViewID::kDisconnectedView, content_view()->GetID()); |
| |
| // Simulates a click on the "Refresh" button. |
| LeftClickOn(disconnected_refresh_button()); |
| |
| // Clicking "Refresh" button should schedule a connection attempt. |
| EXPECT_EQ(2u, GetConnectionScheduler()->num_schedule_connection_now_calls()); |
| |
| // Clicking "Learn More" button should open the corresponding help center |
| // article in a browser tab. |
| EXPECT_CALL(new_window_delegate(), |
| OpenUrl(GURL("https://support.google.com/chromebook?p=phone_hub"), |
| NewWindowDelegate::OpenUrlFrom::kUserInteraction, |
| NewWindowDelegate::Disposition::kNewForegroundTab)); |
| |
| // Simulates a click on the "Learn more" button. |
| LeftClickOn(disconnected_learn_more_button()); |
| } |
| |
| TEST_F(PhoneHubTrayTest, ClickButtonOnBluetoothDisabledView) { |
| // Simulate a Bluetooth unavailable state. |
| GetFeatureStatusProvider()->SetStatus( |
| phonehub::FeatureStatus::kUnavailableBluetoothOff); |
| ClickTrayButton(); |
| EXPECT_TRUE(phone_hub_tray_->is_active()); |
| EXPECT_EQ(PhoneHubViewID::kBluetoothDisabledView, content_view()->GetID()); |
| |
| // Clicking "Learn more" button should open the corresponding help center |
| // article in a browser tab. |
| EXPECT_CALL(new_window_delegate(), |
| OpenUrl(GURL("https://support.google.com/chromebook?p=phone_hub"), |
| NewWindowDelegate::OpenUrlFrom::kUserInteraction, |
| NewWindowDelegate::Disposition::kNewForegroundTab)); |
| // Simulate a click on "Learn more" button. |
| LeftClickOn(bluetooth_disabled_learn_more_button()); |
| } |
| |
| TEST_F(PhoneHubTrayTest, CloseBubbleWhileShowingSameView) { |
| // Simulate the views returned to PhoneHubTray are the same and open and |
| // close tray. |
| GetFeatureStatusProvider()->SetStatus( |
| phonehub::FeatureStatus::kEnabledAndConnecting); |
| ClickTrayButton(); |
| EXPECT_TRUE(phone_hub_tray_->is_active()); |
| EXPECT_EQ(PhoneHubViewID::kPhoneConnectingView, content_view()->GetID()); |
| ClickTrayButton(); |
| EXPECT_FALSE(phone_hub_tray_->is_active()); |
| GetFeatureStatusProvider()->SetStatus( |
| phonehub::FeatureStatus::kEnabledButDisconnected); |
| EXPECT_FALSE(content_view()); |
| } |
| |
| TEST_F(PhoneHubTrayTest, OnSessionChanged) { |
| ui::ScopedAnimationDurationScaleMode test_duration_mode( |
| ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION); |
| |
| // Disable the tray first. |
| GetFeatureStatusProvider()->SetStatus( |
| phonehub::FeatureStatus::kNotEligibleForFeature); |
| FastForwardByConnectingViewGracePeriod(); |
| EXPECT_FALSE(phone_hub_tray_->GetVisible()); |
| |
| // Enable it to let it visible. |
| GetFeatureStatusProvider()->SetStatus( |
| phonehub::FeatureStatus::kEnabledAndConnected); |
| task_environment()->FastForwardBy(base::Seconds(3)); |
| EXPECT_TRUE(phone_hub_tray_->GetVisible()); |
| GetSessionControllerClient()->SetSessionState( |
| session_manager::SessionState::LOGIN_PRIMARY); |
| |
| EXPECT_TRUE(phone_hub_tray_->GetVisible()); |
| |
| // Animation is disabled for 5 seconds after the session state get changed. |
| GetSessionControllerClient()->SetSessionState( |
| session_manager::SessionState::ACTIVE); |
| // Gives it a small duration to let the session get changed. This duration is |
| // way smaller than the animation duration, so that the animation will not |
| // finish when this duration ends. The same for the other places below. |
| task_environment()->FastForwardBy(base::Milliseconds(20)); |
| for (int i = 0; i < 3; i++) { |
| SCOPED_TRACE(::testing::Message() << "iteration=" << i); |
| EXPECT_FALSE(phone_hub_tray_->layer()->GetAnimator()->is_animating()); |
| EXPECT_TRUE(phone_hub_tray_->GetVisible()); |
| GetFeatureStatusProvider()->SetStatus( |
| phonehub::FeatureStatus::kNotEligibleForFeature); |
| FastForwardByConnectingViewGracePeriod(); |
| EXPECT_FALSE(phone_hub_tray_->GetVisible()); |
| GetFeatureStatusProvider()->SetStatus( |
| phonehub::FeatureStatus::kEnabledAndConnected); |
| task_environment()->FastForwardBy(base::Seconds(3)); |
| } |
| EXPECT_FALSE(phone_hub_tray_->layer()->GetAnimator()->is_animating()); |
| EXPECT_TRUE(phone_hub_tray_->GetVisible()); |
| |
| GetFeatureStatusProvider()->SetStatus( |
| phonehub::FeatureStatus::kNotEligibleForFeature); |
| GetFeatureStatusProvider()->SetStatus( |
| phonehub::FeatureStatus::kEnabledAndConnected); |
| EXPECT_TRUE(phone_hub_tray_->layer()->GetAnimator()->is_animating()); |
| EXPECT_TRUE(phone_hub_tray_->GetVisible()); |
| } |
| |
| // This is a test to check for use-after-free error on accessing |
| // a possible dangling reference to `phone_status_view`. |
| TEST_F(PhoneHubTrayTest, SafeAccessToHeaderView) { |
| phone_hub_tray_->ShowBubble(); |
| |
| // Bubble is closed w/o calling `phone_hub_tray_->CloseBubble()` |
| phone_hub_tray_->GetBubbleWidget()->CloseNow(); |
| |
| // Make sure it does not cause a UAF error.This is caught by ASAN (go/asan) |
| phone_hub_tray_->UpdateHeaderVisibility(); |
| } |
| |
| TEST_F(PhoneHubTrayTest, MultiDisplay) { |
| // Connect a second display, make sure the phone hub tray is shown still. |
| UpdateDisplay("500x400,500x400"); |
| aura::Window::Windows root_windows = Shell::GetAllRootWindows(); |
| EXPECT_EQ(2U, root_windows.size()); |
| |
| auto* secondary_phone_hub_tray = |
| StatusAreaWidgetTestHelper::GetSecondaryStatusAreaWidget() |
| ->phone_hub_tray(); |
| secondary_phone_hub_tray->SetPhoneHubManager(&phone_hub_manager_); |
| |
| EXPECT_TRUE(phone_hub_tray_->GetVisible()); |
| EXPECT_TRUE(secondary_phone_hub_tray->GetVisible()); |
| } |
| |
| TEST_F(PhoneHubTrayTest, |
| PhoneHubNotShownOnMoreThanFiveMinutesAfterSessionStartTime) { |
| GetSessionControllerClient()->SetSessionState( |
| session_manager::SessionState::ACTIVE); |
| GetFeatureStatusProvider()->SetStatus( |
| phonehub::FeatureStatus::kNotEligibleForFeature); |
| |
| // Set time to fifteen minutes after session start time. |
| task_environment()->AdvanceClock(base::TimeDelta(base::Minutes(15))); |
| GetFeatureStatusProvider()->SetStatus( |
| phonehub::FeatureStatus::kEligiblePhoneButNotSetUp); |
| GetOnboardingUiTracker()->SetShouldShowOnboardingUi(true); |
| EXPECT_FALSE(phone_hub_tray_->GetVisible()); |
| |
| GetSessionControllerClient()->SetSessionState( |
| session_manager::SessionState::LOCKED); |
| EXPECT_FALSE(phone_hub_tray_->GetVisible()); |
| |
| GetSessionControllerClient()->SetSessionState( |
| session_manager::SessionState::ACTIVE); |
| EXPECT_TRUE(phone_hub_tray_->GetVisible()); |
| } |
| |
| TEST_F(PhoneHubTrayTest, ShowPhoneHubOnlyUpToFiveMinutesAfterSessionStartTime) { |
| // Reset session start time. |
| GetFeatureStatusProvider()->SetStatus( |
| phonehub::FeatureStatus::kNotEligibleForFeature); |
| GetSessionControllerClient()->SetSessionState( |
| session_manager::SessionState::ACTIVE); |
| |
| // Set time to three minutes after session start time. |
| task_environment()->AdvanceClock(base::TimeDelta(base::Minutes(3))); |
| GetFeatureStatusProvider()->SetStatus( |
| phonehub::FeatureStatus::kEligiblePhoneButNotSetUp); |
| GetOnboardingUiTracker()->SetShouldShowOnboardingUi(true); |
| EXPECT_TRUE(phone_hub_tray_->GetVisible()); |
| } |
| |
| TEST_F(PhoneHubTrayTest, ShowAndHideNudge) { |
| GetFeatureStatusProvider()->SetStatus( |
| phonehub::FeatureStatus::kEligiblePhoneButNotSetUp); |
| GetOnboardingUiTracker()->SetShouldShowOnboardingUi(true); |
| GetSessionControllerClient()->SetSessionState( |
| session_manager::SessionState::ACTIVE); |
| |
| EXPECT_TRUE( |
| Shell::Get()->anchored_nudge_manager()->IsNudgeShown(kPhoneHubNudgeId)); |
| |
| ClickTrayButton(); |
| EXPECT_TRUE(phone_hub_tray_->is_active()); |
| EXPECT_EQ(PhoneHubViewID::kOnboardingView, content_view()->GetID()); |
| // It should display the onboarding main view. |
| EXPECT_TRUE(onboarding_main_view()); |
| EXPECT_TRUE(onboarding_main_view()->GetVisible()); |
| EXPECT_EQ(0u, GetOnboardingUiTracker()->handle_get_started_call_count()); |
| |
| // Simulate a click on the "Get started" button. |
| LeftClickOn(onboarding_get_started_button()); |
| // It should invoke the |HandleGetStarted| call. |
| EXPECT_EQ(1u, GetOnboardingUiTracker()->handle_get_started_call_count()); |
| EXPECT_TRUE(GetOnboardingUiTracker()->is_icon_clicked_when_nudge_visible()); |
| EXPECT_FALSE( |
| Shell::Get()->anchored_nudge_manager()->IsNudgeShown(kPhoneHubNudgeId)); |
| } |
| |
| TEST_F(PhoneHubTrayTest, EcheIconActivatesCallback) { |
| bool launched_app_window = false; |
| phone_hub_tray_->SetEcheIconActivationCallback( |
| base::BindLambdaForTesting([&]() { launched_app_window = true; })); |
| phone_hub_tray_->OnAppStreamUpdate(phonehub::proto::AppStreamUpdate()); |
| phone_hub_manager_.fake_icon_decoder()->FinishLastCall(); |
| |
| LeftClickOn(phone_hub_tray_->eche_icon_); |
| |
| EXPECT_TRUE(launched_app_window); |
| } |
| |
| // Makes sure metrics are recorded for the phone hub tray or any nested button |
| // being pressed. |
| TEST_F(PhoneHubTrayTest, TrayPressedMetrics) { |
| base::HistogramTester histogram_tester; |
| |
| LeftClickOn(phone_hub_tray()); |
| histogram_tester.ExpectTotalCount(kTrayBackgroundViewHistogramName, 1); |
| |
| LeftClickOn(phone_hub_tray()->icon_); |
| histogram_tester.ExpectTotalCount(kTrayBackgroundViewHistogramName, 2); |
| |
| LeftClickOn(phone_hub_tray()->eche_icon_); |
| histogram_tester.ExpectTotalCount(kTrayBackgroundViewHistogramName, 3); |
| } |
| |
| } // namespace ash |