123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360 |
- // Copyright 2014 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/app_list/views/search_result_tile_item_list_view.h"
- #include <stddef.h>
- #include <algorithm>
- #include <memory>
- #include <set>
- #include <string>
- #include "ash/app_list/app_list_model_provider.h"
- #include "ash/app_list/app_list_util.h"
- #include "ash/app_list/app_list_view_delegate.h"
- #include "ash/app_list/model/search/search_result.h"
- #include "ash/app_list/views/search_result_page_view.h"
- #include "ash/public/cpp/app_list/app_list_color_provider.h"
- #include "ash/public/cpp/app_list/app_list_config.h"
- #include "ash/public/cpp/app_list/app_list_features.h"
- #include "ash/public/cpp/app_list/app_list_notifier.h"
- #include "ash/public/cpp/app_list/app_list_types.h"
- #include "ash/public/cpp/app_list/internal_app_id_constants.h"
- #include "base/bind.h"
- #include "base/callback.h"
- #include "base/i18n/rtl.h"
- #include "base/metrics/histogram_macros.h"
- #include "base/metrics/user_metrics.h"
- #include "base/stl_util.h"
- #include "ui/gfx/color_palette.h"
- #include "ui/gfx/geometry/insets.h"
- #include "ui/views/background.h"
- #include "ui/views/border.h"
- #include "ui/views/controls/separator.h"
- #include "ui/views/controls/textfield/textfield.h"
- #include "ui/views/focus/focus_manager.h"
- #include "ui/views/layout/box_layout.h"
- #include "ui/views/widget/widget.h"
- namespace ash {
- namespace {
- // Layout constants used when fullscreen app list feature is enabled.
- constexpr int kItemListVerticalSpacing = 16;
- constexpr int kItemListHorizontalSpacing = 12;
- constexpr int kBetweenItemSpacing = 8;
- constexpr int kSeparatorLeftRightPadding = 4;
- constexpr int kSeparatorHeight = 46;
- constexpr int kSeparatorTopPadding = 10;
- // The Delay before recording play store app results impression, i.e., if the
- // play store results are displayed less than the duration, we assume user
- // won't have chance to see them clearly and click on them, and wont' log
- // the impression.
- constexpr int kPlayStoreImpressionDelayInMs = 1000;
- // Returns true if the search result is an installable app.
- bool IsResultAnInstallableApp(SearchResult* result) {
- SearchResult::ResultType result_type = result->result_type();
- return result_type == AppListSearchResultType::kPlayStoreApp ||
- result_type == AppListSearchResultType::kPlayStoreReinstallApp ||
- result_type == AppListSearchResultType::kInstantApp;
- }
- bool IsPlayStoreApp(SearchResult* result) {
- return result->result_type() == AppListSearchResultType::kPlayStoreApp;
- }
- } // namespace
- SearchResultTileItemListView::SearchResultTileItemListView(
- views::Textfield* search_box,
- AppListViewDelegate* view_delegate)
- : SearchResultContainerView(view_delegate),
- search_box_(search_box),
- is_app_reinstall_recommendation_enabled_(
- app_list_features::IsAppReinstallZeroStateEnabled()),
- max_search_result_tiles_(
- SharedAppListConfig::instance().max_search_result_tiles()) {
- layout_ = SetLayoutManager(std::make_unique<views::BoxLayout>(
- views::BoxLayout::Orientation::kHorizontal,
- gfx::Insets::VH(kItemListVerticalSpacing, kItemListHorizontalSpacing),
- kBetweenItemSpacing));
- for (size_t i = 0; i < max_search_result_tiles_; ++i) {
- views::Separator* separator =
- AddChildView(std::make_unique<views::Separator>());
- separator->SetVisible(false);
- separator->SetBorder(views::CreateEmptyBorder(gfx::Insets::TLBR(
- kSeparatorTopPadding, kSeparatorLeftRightPadding,
- SharedAppListConfig::instance().search_tile_height() - kSeparatorHeight,
- kSeparatorLeftRightPadding)));
- separator->SetColorId(AppListColorProvider::Get()->GetSeparatorColorId());
- separator_views_.push_back(separator);
- layout_->SetFlexForView(separator, 0);
- SearchResultTileItemView* tile_item =
- AddChildView(std::make_unique<SearchResultTileItemView>(view_delegate));
- tile_item->set_index_in_container(i);
- tile_item->SetParentBackgroundColor(
- AppListColorProvider::Get()->GetSearchBoxCardBackgroundColor());
- tile_views_.push_back(tile_item);
- AddObservedResultView(tile_item);
- }
- // Tile items are shown horizontally.
- set_horizontally_traversable(true);
- }
- SearchResultTileItemListView::~SearchResultTileItemListView() = default;
- SearchResultTileItemView* SearchResultTileItemListView::GetResultViewAt(
- size_t index) {
- DCHECK(index >= 0 && index < tile_views_.size());
- return tile_views_[index];
- }
- int SearchResultTileItemListView::DoUpdate() {
- if (!GetWidget() || !GetWidget()->IsVisible() || !GetWidget()->IsActive()) {
- for (size_t i = 0; i < max_search_result_tiles_; ++i) {
- SearchResultBaseView* result_view = GetResultViewAt(i);
- result_view->SetResult(nullptr);
- result_view->SetVisible(false);
- }
- return 0;
- }
- std::vector<SearchResult*> display_results = GetDisplayResults();
- std::set<std::string> result_id_removed, result_id_added;
- bool is_result_an_installable_app = false;
- bool is_previous_result_installable_app = false;
- int installed_app_index = -1;
- int playstore_app_index = -1;
- int app_group_index = -1;
- bool found_playstore_results = false;
- for (size_t i = 0; i < max_search_result_tiles_; ++i) {
- // If the current result at i exists, wants to be notified and is a
- // different id, notify it that it is being hidden.
- SearchResult* current_result = tile_views_[i]->result();
- if (current_result != nullptr) {
- result_id_removed.insert(current_result->id());
- }
- if (i >= display_results.size()) {
- separator_views_[i]->SetVisible(false);
- GetResultViewAt(i)->SetResult(nullptr);
- continue;
- }
- SearchResult* item = display_results[i];
- if (IsPlayStoreApp(item)) {
- ++playstore_app_index;
- app_group_index = playstore_app_index;
- found_playstore_results = true;
- } else if (item->result_type() ==
- AppListSearchResultType::kPlayStoreReinstallApp) {
- app_group_index = playstore_app_index;
- } else {
- ++installed_app_index;
- app_group_index = installed_app_index;
- }
- GetResultViewAt(i)->SetResult(item);
- GetResultViewAt(i)->set_group_index_in_container_view(app_group_index);
- result_id_added.insert(item->id());
- is_result_an_installable_app = IsResultAnInstallableApp(item);
- if (i > 0 &&
- (is_result_an_installable_app != is_previous_result_installable_app)) {
- // Add a separator between installed apps and installable apps.
- // This assumes the search results are already separated in groups for
- // installed and installable apps.
- separator_views_[i]->SetVisible(true);
- } else {
- separator_views_[i]->SetVisible(false);
- }
- is_previous_result_installable_app = is_result_an_installable_app;
- }
- auto* notifier = view_delegate()->GetNotifier();
- if (notifier) {
- std::vector<AppListNotifier::Result> notifier_results;
- for (const auto* result : display_results)
- notifier_results.emplace_back(result->id(), result->metrics_type());
- notifier->NotifyResultsUpdated(SearchResultDisplayType::kTile,
- notifier_results);
- }
- // Track play store results and start the timer for recording their impression
- // UMA metrics.
- std::u16string user_typed_query = GetUserTypedQuery();
- if (found_playstore_results && user_typed_query != recent_playstore_query_) {
- recent_playstore_query_ = user_typed_query;
- playstore_impression_timer_.Stop();
- playstore_impression_timer_.Start(
- FROM_HERE, base::Milliseconds(kPlayStoreImpressionDelayInMs), this,
- &SearchResultTileItemListView::OnPlayStoreImpressionTimer);
- // Set the starting time in result view for play store results.
- base::TimeTicks result_display_start = base::TimeTicks::Now();
- for (size_t i = 0; i < max_search_result_tiles_; ++i) {
- SearchResult* result = GetResultViewAt(i)->result();
- if (result && IsPlayStoreApp(result)) {
- GetResultViewAt(i)->set_result_display_start_time(result_display_start);
- }
- }
- } else if (!found_playstore_results) {
- playstore_impression_timer_.Stop();
- }
- return display_results.size();
- }
- std::vector<SearchResult*> SearchResultTileItemListView::GetDisplayResults() {
- std::u16string raw_query = search_box_->GetText();
- std::u16string query;
- base::TrimWhitespace(raw_query, base::TRIM_ALL, &query);
- // We ask for |max_search_result_tiles_| policy tile results first,
- // then add them to their preferred position in the tile list if found.
- // Note: Policy tile provides a mechanism to display the result tile at the
- // preferred position recommended by display_index() property of the search
- // result. This is what policy referred to. It has nothing to do with
- // Enterprise policy.
- auto policy_tiles_filter =
- base::BindRepeating([](const SearchResult& r) -> bool {
- return r.display_index() != SearchResultDisplayIndex::kUndefined &&
- r.display_type() == SearchResultDisplayType::kTile &&
- r.is_recommendation();
- });
- std::vector<SearchResult*> policy_tiles_results =
- is_app_reinstall_recommendation_enabled_ && query.empty()
- ? SearchModel::FilterSearchResultsByFunction(
- results(), policy_tiles_filter, max_search_result_tiles_)
- : std::vector<SearchResult*>();
- SearchResult::DisplayType display_type = SearchResultDisplayType::kTile;
- size_t display_num = max_search_result_tiles_ - policy_tiles_results.size();
- // Do not display the repeat reinstall results or continue reading app in the
- // search result list.
- auto non_policy_tiles_filter = base::BindRepeating(
- [](const SearchResult::DisplayType& display_type,
- const SearchResult& r) -> bool {
- return r.display_type() == display_type &&
- r.result_type() !=
- AppListSearchResultType::kPlayStoreReinstallApp &&
- r.id() != kInternalAppIdContinueReading;
- },
- display_type);
- std::vector<SearchResult*> display_results =
- SearchModel::FilterSearchResultsByFunction(
- results(), non_policy_tiles_filter, display_num);
- // Policy tile results will be appended to the final tiles list
- // based on their specified index. If the requested index is out of
- // range of the current list, the result will be appended to the back.
- std::sort(policy_tiles_results.begin(), policy_tiles_results.end(),
- [](const SearchResult* r1, const SearchResult* r2) -> bool {
- return r1->display_index() < r2->display_index();
- });
- const SearchResultDisplayIndex display_results_last_index =
- static_cast<SearchResultDisplayIndex>(display_results.size() - 1);
- for (auto* result : policy_tiles_results) {
- const SearchResultDisplayIndex result_index = result->display_index();
- if (result_index > display_results_last_index) {
- display_results.emplace_back(result);
- } else {
- // TODO(newcomer): Remove this check once we determine the root cause for
- // https://crbug.com/992344.
- CHECK_GE(result_index, SearchResultDisplayIndex::kFirstIndex);
- display_results.emplace(display_results.begin() + result_index, result);
- }
- }
- return display_results;
- }
- std::u16string SearchResultTileItemListView::GetUserTypedQuery() {
- std::u16string search_box_text = search_box_->GetText();
- gfx::Range range = search_box_->GetSelectedRange();
- std::u16string raw_query = range.is_empty()
- ? search_box_text
- : search_box_text.substr(0, range.start());
- std::u16string query;
- base::TrimWhitespace(raw_query, base::TRIM_ALL, &query);
- return query;
- }
- void SearchResultTileItemListView::OnPlayStoreImpressionTimer() {
- size_t playstore_app_num = 0;
- for (const auto* tile_view : tile_views_) {
- SearchResult* result = tile_view->result();
- if (result == nullptr)
- continue;
- if (IsPlayStoreApp(result))
- ++playstore_app_num;
- }
- // Log the UMA metrics of play store impression.
- base::RecordAction(
- base::UserMetricsAction("AppList_ShowPlayStoreQueryResults"));
- DCHECK_LE(playstore_app_num, max_search_result_tiles_);
- UMA_HISTOGRAM_EXACT_LINEAR("Apps.AppListPlayStoreSearchAppsDisplayed",
- playstore_app_num, max_search_result_tiles_);
- }
- void SearchResultTileItemListView::CleanUpOnViewHide() {
- playstore_impression_timer_.Stop();
- recent_playstore_query_.clear();
- }
- const char* SearchResultTileItemListView::GetClassName() const {
- return "SearchResultTileItemListView";
- }
- void SearchResultTileItemListView::Layout() {
- const bool flex = GetContentsBounds().width() < GetPreferredSize().width();
- layout_->SetDefaultFlex(flex ? 1 : 0);
- layout_->set_between_child_spacing(flex ? 1 : kBetweenItemSpacing);
- views::View::Layout();
- }
- void SearchResultTileItemListView::OnShownChanged() {
- SearchResultContainerView::OnShownChanged();
- for (const auto* tile_view : tile_views_) {
- SearchResult* result = tile_view->result();
- if (result == nullptr) {
- continue;
- }
- }
- }
- void SearchResultTileItemListView::VisibilityChanged(View* starting_from,
- bool is_visible) {
- SearchResultContainerView::VisibilityChanged(starting_from, is_visible);
- // We only do this work when is_visible is false, since this is how we
- // receive the event. We filter and only run when shown.
- if (is_visible && shown()) {
- return;
- }
- CleanUpOnViewHide();
- for (const auto* tile_view : tile_views_) {
- SearchResult* result = tile_view->result();
- if (result == nullptr) {
- continue;
- }
- }
- }
- } // namespace ash
|