0

Implement functionality of LauncherSearchIphView

- A follow-up CL is going to implement more detailed styles.

Bug: b:272370530
Test: browser_tests --gtest_filter=*AppListIphBrowser*
Test: ash_pixeltests --gtest_filter=*LauncherSearchIph*
Change-Id: If7e16d5a21764c4042197e5f1866a7d22d8c14f2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4354790
Reviewed-by: Toni Barzic <tbarzic@chromium.org>
Commit-Queue: Yuki Awano <yawano@google.com>
Quick-Run: Yuki Awano <yawano@google.com>
Cr-Commit-Position: refs/heads/main@{#1122800}
This commit is contained in:
Yuki Awano
2023-03-28 02:15:40 +00:00
committed by Chromium LUCI CQ
parent 9538a22878
commit d56e17a282
7 changed files with 242 additions and 62 deletions

@ -9,6 +9,7 @@
#include "ash/app_list/app_list_model_provider.h"
#include "ash/app_list/model/app_list_item.h"
#include "ash/public/cpp/app_list/app_list_controller.h"
#include "ash/public/cpp/app_list/app_list_types.h"
#include "base/functional/bind.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
@ -87,7 +88,7 @@ void TestAppListClient::QueryWouldTriggerLauncherSearchIph() {}
std::unique_ptr<ScopedIphSession>
TestAppListClient::CreateLauncherSearchIphSession() {
return nullptr;
return std::make_unique<ScopedIphSession>();
}
std::vector<TestAppListClient::SearchResultActionId>

@ -17,6 +17,8 @@
#include "ash/test/pixel/ash_pixel_differ.h"
#include "ash/test/pixel/ash_pixel_test_init_params.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "base/run_loop.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/types/event_type.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/textfield/textfield_test_api.h"
@ -184,6 +186,41 @@ TEST_P(AppListViewPixelRTLTest, GradientZone) {
GetPrimaryShelf()->navigation_widget()));
}
class AppListViewLauncherSearchIphTest
: public AshTestBase,
public testing::WithParamInterface</*rtl=*/bool> {
public:
absl::optional<pixel_test::InitParams> CreatePixelTestInitParams()
const override {
pixel_test::InitParams init_params;
init_params.under_rtl = GetParam();
return init_params;
}
void SetUp() override {
AshTestBase::SetUp();
AppListTestHelper* test_helper = GetAppListTestHelper();
test_helper->ShowAppList();
GetAppListTestHelper()->search_model()->SetWouldTriggerLauncherSearchIph(
true);
GetAppListTestHelper()->GetSearchBoxView()->SetIsIphAllowed(true);
}
};
INSTANTIATE_TEST_SUITE_P(RTL,
AppListViewLauncherSearchIphTest,
testing::Bool());
TEST_P(AppListViewLauncherSearchIphTest, Basic) {
// Wait re-layout for adding IPH view.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
"launcher_search_iph", /*revision_number=*/0,
GetAppListTestHelper()->GetSearchBoxView()));
}
class AppListViewTabletPixelTest
: public AshTestBase,
public testing::WithParamInterface</*tablet_mode=*/bool> {

@ -5,13 +5,18 @@
#include "ash/app_list/views/launcher_search_iph_view.h"
#include <memory>
#include <string>
#include <utility>
#include "ash/public/cpp/app_list/app_list_client.h"
#include "ash/style/pill_button.h"
#include "base/functional/bind.h"
#include "ui/gfx/font_list.h"
#include "ui/gfx/text_constants.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/box_layout_view.h"
namespace ash {
namespace {
@ -21,35 +26,97 @@ constexpr int kHorizontalInset = 24;
constexpr int kTitleTextFontSize = 20;
constexpr int kDescriptionTextFontSize = 16;
constexpr int kMainLayoutBetweenChildSpacing = 16;
constexpr int kActionContainerBetweenChildSpacing = 8;
constexpr char16_t kTitleTextPlaceholder[] = u"Title text";
constexpr char16_t kDescriptionTextPlaceholder[] = u"Description text";
constexpr char16_t kChipOneQueryPlaceholder[] = u"Weather";
constexpr char16_t kChipTwoQueryPlaceholder[] = u"1+1";
constexpr char16_t kChipThreeQueryPlaceholder[] = u"5 cm in inches";
constexpr char16_t kAssistantButtonPlaceholder[] = u"Assistant";
} // namespace
LauncherSearchIphView::LauncherSearchIphView(
std::unique_ptr<ScopedIphSession> scoped_iph_session)
: scoped_iph_session_(std::move(scoped_iph_session)) {
SetID(kViewId);
std::unique_ptr<ScopedIphSession> scoped_iph_session,
raw_ptr<Delegate> delegate)
: scoped_iph_session_(std::move(scoped_iph_session)), delegate_(delegate) {
SetID(ViewId::kSelf);
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical,
gfx::Insets::VH(kVerticalInset, kHorizontalInset)));
raw_ptr<views::BoxLayout> box_layout =
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical,
gfx::Insets::VH(kVerticalInset, kHorizontalInset)));
box_layout->set_between_child_spacing(kMainLayoutBetweenChildSpacing);
// Use `kStretch` for `actions_container` to get stretched.
box_layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kStretch);
raw_ptr<views::Label> title_label =
AddChildView(std::make_unique<views::Label>(kTitleTextPlaceholder));
// Add texts into a container to avoid stretching `views::Label`s.
raw_ptr<views::BoxLayoutView> text_container =
AddChildView(std::make_unique<views::BoxLayoutView>());
text_container->SetOrientation(views::BoxLayout::Orientation::kVertical);
text_container->SetCrossAxisAlignment(
views::BoxLayout::CrossAxisAlignment::kStart);
text_container->SetBetweenChildSpacing(kMainLayoutBetweenChildSpacing);
raw_ptr<views::Label> title_label = text_container->AddChildView(
std::make_unique<views::Label>(kTitleTextPlaceholder));
title_label->SetFontList(gfx::FontList().DeriveWithSizeDelta(
kTitleTextFontSize - gfx::FontList().GetFontSize()));
title_label->SetLineHeight(kTitleTextFontSize);
title_label->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_TO_HEAD);
raw_ptr<views::Label> description_label =
AddChildView(std::make_unique<views::Label>(kDescriptionTextPlaceholder));
raw_ptr<views::Label> description_label = text_container->AddChildView(
std::make_unique<views::Label>(kDescriptionTextPlaceholder));
description_label->SetFontList(gfx::FontList().DeriveWithSizeDelta(
kDescriptionTextFontSize - gfx::FontList().GetFontSize()));
description_label->SetLineHeight(kDescriptionTextFontSize);
description_label->SetHorizontalAlignment(
gfx::HorizontalAlignment::ALIGN_TO_HEAD);
raw_ptr<views::BoxLayoutView> actions_container =
AddChildView(std::make_unique<views::BoxLayoutView>());
actions_container->SetOrientation(views::BoxLayout::Orientation::kHorizontal);
actions_container->SetBetweenChildSpacing(
kActionContainerBetweenChildSpacing);
int query_chip_view_id = ViewId::kChipStart;
for (const std::u16string& query :
{kChipOneQueryPlaceholder, kChipTwoQueryPlaceholder,
kChipThreeQueryPlaceholder}) {
raw_ptr<ash::PillButton> chip =
actions_container->AddChildView(std::make_unique<ash::PillButton>(
base::BindRepeating(&LauncherSearchIphView::RunLauncherSearchQuery,
weak_ptr_factory_.GetWeakPtr(), query),
query));
chip->SetID(query_chip_view_id);
query_chip_view_id++;
}
raw_ptr<views::View> spacer =
actions_container->AddChildView(std::make_unique<views::View>());
actions_container->SetFlexForView(spacer, 1);
raw_ptr<ash::PillButton> assistant_button =
actions_container->AddChildView(std::make_unique<ash::PillButton>(
base::BindRepeating(&LauncherSearchIphView::OpenAssistantPage,
weak_ptr_factory_.GetWeakPtr()),
kAssistantButtonPlaceholder));
assistant_button->SetID(ViewId::kAssistant);
}
LauncherSearchIphView::~LauncherSearchIphView() = default;
void LauncherSearchIphView::RunLauncherSearchQuery(
const std::u16string& query) {
delegate_->RunLauncherSearchQuery(query);
}
void LauncherSearchIphView::OpenAssistantPage() {
delegate_->OpenAssistantPage();
}
} // namespace ash

@ -6,6 +6,7 @@
#define ASH_APP_LIST_VIEWS_LAUNCHER_SEARCH_IPH_VIEW_H_
#include <memory>
#include <string>
#include "ash/public/cpp/app_list/app_list_client.h"
#include "ui/views/view.h"
@ -14,14 +15,36 @@ namespace ash {
class LauncherSearchIphView : public views::View {
public:
static constexpr int kViewId = 1;
// Delegate for handling actions of `LauncherSearchIphView`.
class Delegate {
public:
virtual ~Delegate() = default;
// Run `query` as a launcher search. `query` is localized.
virtual void RunLauncherSearchQuery(const std::u16string& query) = 0;
// Opens Assistant page in the launcher.
virtual void OpenAssistantPage() = 0;
};
explicit LauncherSearchIphView(
std::unique_ptr<ScopedIphSession> scoped_iph_session);
enum ViewId {
kSelf = 1,
kAssistant,
// Do not put a new id after `kChipStart`. Numbers after `kChipStart`
// will be used for chips.
kChipStart
};
LauncherSearchIphView(std::unique_ptr<ScopedIphSession> scoped_iph_session,
raw_ptr<Delegate> delegate);
~LauncherSearchIphView() override;
private:
// TODO(b/272370530): Use string id for internationalization.
void RunLauncherSearchQuery(const std::u16string& query);
void OpenAssistantPage();
std::unique_ptr<ScopedIphSession> scoped_iph_session_;
raw_ptr<Delegate> delegate_;
base::WeakPtrFactory<LauncherSearchIphView> weak_ptr_factory_{this};
};

@ -494,6 +494,14 @@ void SearchBoxView::AddedToWidget() {
}
}
void SearchBoxView::RunLauncherSearchQuery(const std::u16string& query) {
UpdateQuery(query);
}
void SearchBoxView::OpenAssistantPage() {
delegate_->AssistantButtonPressed();
}
// static
int SearchBoxView::GetFocusRingSpacing() {
return kSearchBoxFocusRingWidth + kSearchBoxFocusRingPadding;
@ -1236,8 +1244,8 @@ void SearchBoxView::UpdateIphViewVisibility() {
return;
}
SetIphView(
std::make_unique<LauncherSearchIphView>(std::move(scoped_iph_session)));
SetIphView(std::make_unique<LauncherSearchIphView>(
std::move(scoped_iph_session), /*delegate=*/this));
} else {
DeleteIphView();
}

@ -14,6 +14,7 @@
#include "ash/app_list/app_list_view_delegate.h"
#include "ash/app_list/model/search/search_box_model.h"
#include "ash/app_list/model/search/search_box_model_observer.h"
#include "ash/app_list/views/launcher_search_iph_view.h"
#include "ash/ash_export.h"
#include "ash/public/cpp/app_list/app_list_types.h"
#include "ash/search_box/search_box_view_base.h"
@ -39,7 +40,8 @@ class SearchResultBaseView;
// contents and selection model of the Textfield.
class ASH_EXPORT SearchBoxView : public SearchBoxViewBase,
public AppListModelProvider::Observer,
public SearchBoxModelObserver {
public SearchBoxModelObserver,
public LauncherSearchIphView::Delegate {
public:
enum class PlaceholderTextType {
kShortcuts = 0,
@ -102,6 +104,10 @@ class ASH_EXPORT SearchBoxView : public SearchBoxViewBase,
void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
void AddedToWidget() override;
// LauncherSearchIphView::Delegate:
void RunLauncherSearchQuery(const std::u16string& query) override;
void OpenAssistantPage() override;
// Updates the search box's background corner radius and color based on the
// state of AppListModel.
void UpdateBackground(AppListState target_state);

@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "ash/app_list/app_list_public_test_util.h"
#include "ash/app_list/views/app_list_bubble_view.h"
#include "ash/app_list/views/assistant/assistant_test_api_impl.h"
#include "ash/app_list/views/launcher_search_iph_view.h"
#include "ash/app_list/views/search_box_view.h"
@ -14,6 +15,7 @@
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "chrome/browser/ash/app_list/app_list_client_impl.h"
#include "chrome/browser/ash/app_list/search/search_controller.h"
#include "chrome/browser/ui/ash/assistant/assistant_test_mixin.h"
#include "chrome/test/base/mixin_based_in_process_browser_test.h"
#include "components/feature_engagement/public/feature_constants.h"
@ -62,13 +64,23 @@ bool IsLauncherSearchIphViewVisible() {
}
raw_ptr<views::View> launcher_search_iph_view =
search_box_view->GetViewByID(ash::LauncherSearchIphView::kViewId);
search_box_view->GetViewByID(ash::LauncherSearchIphView::ViewId::kSelf);
if (!launcher_search_iph_view) {
return false;
}
return launcher_search_iph_view->GetVisible();
}
void Click(raw_ptr<views::View> view) {
ASSERT_TRUE(view);
ui::test::EventGenerator event_generator(
view->GetWidget()->GetNativeWindow()->GetRootWindow());
event_generator.MoveMouseToInHost(view->GetBoundsInScreen().CenterPoint());
event_generator.ClickLeftButton();
}
} // namespace
class AppListIphBrowserTest : public MixinBasedInProcessBrowserTest {
@ -88,7 +100,7 @@ class AppListIphBrowserTest : public MixinBasedInProcessBrowserTest {
ash::AssistantTestApiImpl test_api_impl_;
};
class AppListIphBrowerTestWithDemoMode : public AppListIphBrowserTest {
class AppListIphBrowserTestWithDemoMode : public AppListIphBrowserTest {
public:
void SetUpCommandLine(base::CommandLine* command_line) override {
scoped_iph_feature_list_ =
@ -99,9 +111,39 @@ class AppListIphBrowerTestWithDemoMode : public AppListIphBrowserTest {
MixinBasedInProcessBrowserTest::SetUpCommandLine(command_line);
}
protected:
void OpenAppListWithIph() {
ASSERT_TRUE(!app_list_client_impl_);
ASSERT_TRUE(!search_box_view_);
app_list_client_impl_ = AppListClientImpl::GetInstance();
app_list_client_impl_->UpdateProfile();
app_list_client_impl_->ShowAppList(ash::AppListShowSource::kSearchKey);
// We dispatch mouse events to interact with UI. Wait animation completion
// to reliably dispatch those events.
ash::AppListTestApi().WaitForBubbleWindow(
/*wait_for_opening_animation=*/true);
search_box_view_ = ash::GetSearchBoxView();
ASSERT_TRUE(search_box_view_);
// There is an async call for checking IPH trigger condition.
ViewWaiter(search_box_view_, ash::LauncherSearchIphView::ViewId::kSelf)
.Run();
ASSERT_TRUE(IsLauncherSearchIphViewVisible());
}
raw_ptr<AppListClientImpl> app_list_client_impl() {
return app_list_client_impl_;
}
raw_ptr<ash::SearchBoxView> search_box_view() { return search_box_view_; }
private:
std::unique_ptr<feature_engagement::test::ScopedIphFeatureList>
scoped_iph_feature_list_;
raw_ptr<AppListClientImpl> app_list_client_impl_ = nullptr;
raw_ptr<ash::SearchBoxView> search_box_view_ = nullptr;
};
IN_PROC_BROWSER_TEST_F(AppListIphBrowserTest,
@ -115,43 +157,25 @@ IN_PROC_BROWSER_TEST_F(AppListIphBrowserTest,
EXPECT_FALSE(IsLauncherSearchIphViewVisible());
}
IN_PROC_BROWSER_TEST_F(AppListIphBrowerTestWithDemoMode, LauncherSearchIph) {
raw_ptr<AppListClientImpl> client_impl = AppListClientImpl::GetInstance();
client_impl->UpdateProfile();
client_impl->ShowAppList(ash::AppListShowSource::kSearchKey);
ash::AppListTestApi().WaitForBubbleWindow(
/*wait_for_opening_animation=*/false);
raw_ptr<ash::SearchBoxView> search_box_view = ash::GetSearchBoxView();
ASSERT_TRUE(search_box_view);
// There is an async call for checking IPH trigger condition.
ViewWaiter(search_box_view, ash::LauncherSearchIphView::kViewId).Run();
IN_PROC_BROWSER_TEST_F(AppListIphBrowserTestWithDemoMode, LauncherSearchIph) {
OpenAppListWithIph();
EXPECT_TRUE(IsLauncherSearchIphViewVisible());
// Dismiss the app list and show it again. IPH won't be shown this time. Note
// that this is IPH demo mode behavior.
client_impl->DismissView();
ASSERT_FALSE(client_impl->GetAppListWindow());
app_list_client_impl()->DismissView();
ASSERT_FALSE(app_list_client_impl()->GetAppListWindow());
client_impl->ShowAppList(ash::AppListShowSource::kSearchKey);
app_list_client_impl()->ShowAppList(ash::AppListShowSource::kSearchKey);
ash::AppListTestApi().WaitForBubbleWindow(
/*wait_for_opening_animation=*/false);
EXPECT_FALSE(IsLauncherSearchIphViewVisible());
}
IN_PROC_BROWSER_TEST_F(AppListIphBrowerTestWithDemoMode,
IN_PROC_BROWSER_TEST_F(AppListIphBrowserTestWithDemoMode,
LauncherSearchIphSearch) {
raw_ptr<AppListClientImpl> client_impl = AppListClientImpl::GetInstance();
client_impl->UpdateProfile();
client_impl->ShowAppList(ash::AppListShowSource::kSearchKey);
ash::AppListTestApi().WaitForBubbleWindow(
/*wait_for_opening_animation=*/false);
raw_ptr<ash::SearchBoxView> search_box_view = ash::GetSearchBoxView();
ASSERT_TRUE(search_box_view);
// There is an async call for checking IPH trigger condition.
ViewWaiter(search_box_view, ash::LauncherSearchIphView::kViewId).Run();
OpenAppListWithIph();
EXPECT_TRUE(IsLauncherSearchIphViewVisible());
// Do search and confirm that the IPH gets dismissed.
@ -159,30 +183,44 @@ IN_PROC_BROWSER_TEST_F(AppListIphBrowerTestWithDemoMode,
EXPECT_FALSE(IsLauncherSearchIphViewVisible());
}
IN_PROC_BROWSER_TEST_F(AppListIphBrowerTestWithDemoMode,
IN_PROC_BROWSER_TEST_F(AppListIphBrowserTestWithDemoMode,
LauncherSearchIphAssistant) {
raw_ptr<AppListClientImpl> client_impl = AppListClientImpl::GetInstance();
client_impl->UpdateProfile();
client_impl->ShowAppList(ash::AppListShowSource::kSearchKey);
ash::AppListTestApi().WaitForBubbleWindow(
/*wait_for_opening_animation=*/false);
raw_ptr<ash::SearchBoxView> search_box_view = ash::GetSearchBoxView();
ASSERT_TRUE(search_box_view);
// There is an async call for checking IPH trigger condition.
ViewWaiter(search_box_view, ash::LauncherSearchIphView::kViewId).Run();
EXPECT_TRUE(IsLauncherSearchIphViewVisible());
OpenAppListWithIph();
// Clicks Assistant button to open Assistant UI and confirm that IPH gets
// dismissed.
raw_ptr<views::ImageButton> assistant_button =
search_box_view->assistant_button();
search_box_view()->assistant_button();
ASSERT_TRUE(assistant_button);
ui::test::EventGenerator event_generator(
assistant_button->GetWidget()->GetNativeWindow()->GetRootWindow());
event_generator.MoveMouseToInHost(
assistant_button->GetBoundsInScreen().CenterPoint());
event_generator.ClickLeftButton();
Click(assistant_button);
EXPECT_FALSE(IsLauncherSearchIphViewVisible());
}
IN_PROC_BROWSER_TEST_F(AppListIphBrowserTestWithDemoMode, ClickChip) {
OpenAppListWithIph();
raw_ptr<views::View> chip = search_box_view()->GetViewByID(
ash::LauncherSearchIphView::ViewId::kChipStart);
ASSERT_TRUE(chip);
Click(chip);
EXPECT_EQ(u"Weather",
app_list_client_impl()->search_controller()->get_query());
EXPECT_EQ(ash::AppListBubblePage::kSearch,
ash::GetAppListBubbleView()->current_page_for_test());
EXPECT_FALSE(IsLauncherSearchIphViewVisible());
}
IN_PROC_BROWSER_TEST_F(AppListIphBrowserTestWithDemoMode, ClickAssistant) {
OpenAppListWithIph();
raw_ptr<views::View> assistant_button = search_box_view()->GetViewByID(
ash::LauncherSearchIphView::ViewId::kAssistant);
ASSERT_TRUE(assistant_button);
Click(assistant_button);
EXPECT_EQ(ash::AppListBubblePage::kAssistant,
ash::GetAppListBubbleView()->current_page_for_test());
EXPECT_FALSE(IsLauncherSearchIphViewVisible());
}