search_result_tile_item_list_view.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. // Copyright 2014 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/app_list/views/search_result_tile_item_list_view.h"
  5. #include <stddef.h>
  6. #include <algorithm>
  7. #include <memory>
  8. #include <set>
  9. #include <string>
  10. #include "ash/app_list/app_list_model_provider.h"
  11. #include "ash/app_list/app_list_util.h"
  12. #include "ash/app_list/app_list_view_delegate.h"
  13. #include "ash/app_list/model/search/search_result.h"
  14. #include "ash/app_list/views/search_result_page_view.h"
  15. #include "ash/public/cpp/app_list/app_list_color_provider.h"
  16. #include "ash/public/cpp/app_list/app_list_config.h"
  17. #include "ash/public/cpp/app_list/app_list_features.h"
  18. #include "ash/public/cpp/app_list/app_list_notifier.h"
  19. #include "ash/public/cpp/app_list/app_list_types.h"
  20. #include "ash/public/cpp/app_list/internal_app_id_constants.h"
  21. #include "base/bind.h"
  22. #include "base/callback.h"
  23. #include "base/i18n/rtl.h"
  24. #include "base/metrics/histogram_macros.h"
  25. #include "base/metrics/user_metrics.h"
  26. #include "base/stl_util.h"
  27. #include "ui/gfx/color_palette.h"
  28. #include "ui/gfx/geometry/insets.h"
  29. #include "ui/views/background.h"
  30. #include "ui/views/border.h"
  31. #include "ui/views/controls/separator.h"
  32. #include "ui/views/controls/textfield/textfield.h"
  33. #include "ui/views/focus/focus_manager.h"
  34. #include "ui/views/layout/box_layout.h"
  35. #include "ui/views/widget/widget.h"
  36. namespace ash {
  37. namespace {
  38. // Layout constants used when fullscreen app list feature is enabled.
  39. constexpr int kItemListVerticalSpacing = 16;
  40. constexpr int kItemListHorizontalSpacing = 12;
  41. constexpr int kBetweenItemSpacing = 8;
  42. constexpr int kSeparatorLeftRightPadding = 4;
  43. constexpr int kSeparatorHeight = 46;
  44. constexpr int kSeparatorTopPadding = 10;
  45. // The Delay before recording play store app results impression, i.e., if the
  46. // play store results are displayed less than the duration, we assume user
  47. // won't have chance to see them clearly and click on them, and wont' log
  48. // the impression.
  49. constexpr int kPlayStoreImpressionDelayInMs = 1000;
  50. // Returns true if the search result is an installable app.
  51. bool IsResultAnInstallableApp(SearchResult* result) {
  52. SearchResult::ResultType result_type = result->result_type();
  53. return result_type == AppListSearchResultType::kPlayStoreApp ||
  54. result_type == AppListSearchResultType::kPlayStoreReinstallApp ||
  55. result_type == AppListSearchResultType::kInstantApp;
  56. }
  57. bool IsPlayStoreApp(SearchResult* result) {
  58. return result->result_type() == AppListSearchResultType::kPlayStoreApp;
  59. }
  60. } // namespace
  61. SearchResultTileItemListView::SearchResultTileItemListView(
  62. views::Textfield* search_box,
  63. AppListViewDelegate* view_delegate)
  64. : SearchResultContainerView(view_delegate),
  65. search_box_(search_box),
  66. is_app_reinstall_recommendation_enabled_(
  67. app_list_features::IsAppReinstallZeroStateEnabled()),
  68. max_search_result_tiles_(
  69. SharedAppListConfig::instance().max_search_result_tiles()) {
  70. layout_ = SetLayoutManager(std::make_unique<views::BoxLayout>(
  71. views::BoxLayout::Orientation::kHorizontal,
  72. gfx::Insets::VH(kItemListVerticalSpacing, kItemListHorizontalSpacing),
  73. kBetweenItemSpacing));
  74. for (size_t i = 0; i < max_search_result_tiles_; ++i) {
  75. views::Separator* separator =
  76. AddChildView(std::make_unique<views::Separator>());
  77. separator->SetVisible(false);
  78. separator->SetBorder(views::CreateEmptyBorder(gfx::Insets::TLBR(
  79. kSeparatorTopPadding, kSeparatorLeftRightPadding,
  80. SharedAppListConfig::instance().search_tile_height() - kSeparatorHeight,
  81. kSeparatorLeftRightPadding)));
  82. separator->SetColorId(AppListColorProvider::Get()->GetSeparatorColorId());
  83. separator_views_.push_back(separator);
  84. layout_->SetFlexForView(separator, 0);
  85. SearchResultTileItemView* tile_item =
  86. AddChildView(std::make_unique<SearchResultTileItemView>(view_delegate));
  87. tile_item->set_index_in_container(i);
  88. tile_item->SetParentBackgroundColor(
  89. AppListColorProvider::Get()->GetSearchBoxCardBackgroundColor());
  90. tile_views_.push_back(tile_item);
  91. AddObservedResultView(tile_item);
  92. }
  93. // Tile items are shown horizontally.
  94. set_horizontally_traversable(true);
  95. }
  96. SearchResultTileItemListView::~SearchResultTileItemListView() = default;
  97. SearchResultTileItemView* SearchResultTileItemListView::GetResultViewAt(
  98. size_t index) {
  99. DCHECK(index >= 0 && index < tile_views_.size());
  100. return tile_views_[index];
  101. }
  102. int SearchResultTileItemListView::DoUpdate() {
  103. if (!GetWidget() || !GetWidget()->IsVisible() || !GetWidget()->IsActive()) {
  104. for (size_t i = 0; i < max_search_result_tiles_; ++i) {
  105. SearchResultBaseView* result_view = GetResultViewAt(i);
  106. result_view->SetResult(nullptr);
  107. result_view->SetVisible(false);
  108. }
  109. return 0;
  110. }
  111. std::vector<SearchResult*> display_results = GetDisplayResults();
  112. std::set<std::string> result_id_removed, result_id_added;
  113. bool is_result_an_installable_app = false;
  114. bool is_previous_result_installable_app = false;
  115. int installed_app_index = -1;
  116. int playstore_app_index = -1;
  117. int app_group_index = -1;
  118. bool found_playstore_results = false;
  119. for (size_t i = 0; i < max_search_result_tiles_; ++i) {
  120. // If the current result at i exists, wants to be notified and is a
  121. // different id, notify it that it is being hidden.
  122. SearchResult* current_result = tile_views_[i]->result();
  123. if (current_result != nullptr) {
  124. result_id_removed.insert(current_result->id());
  125. }
  126. if (i >= display_results.size()) {
  127. separator_views_[i]->SetVisible(false);
  128. GetResultViewAt(i)->SetResult(nullptr);
  129. continue;
  130. }
  131. SearchResult* item = display_results[i];
  132. if (IsPlayStoreApp(item)) {
  133. ++playstore_app_index;
  134. app_group_index = playstore_app_index;
  135. found_playstore_results = true;
  136. } else if (item->result_type() ==
  137. AppListSearchResultType::kPlayStoreReinstallApp) {
  138. app_group_index = playstore_app_index;
  139. } else {
  140. ++installed_app_index;
  141. app_group_index = installed_app_index;
  142. }
  143. GetResultViewAt(i)->SetResult(item);
  144. GetResultViewAt(i)->set_group_index_in_container_view(app_group_index);
  145. result_id_added.insert(item->id());
  146. is_result_an_installable_app = IsResultAnInstallableApp(item);
  147. if (i > 0 &&
  148. (is_result_an_installable_app != is_previous_result_installable_app)) {
  149. // Add a separator between installed apps and installable apps.
  150. // This assumes the search results are already separated in groups for
  151. // installed and installable apps.
  152. separator_views_[i]->SetVisible(true);
  153. } else {
  154. separator_views_[i]->SetVisible(false);
  155. }
  156. is_previous_result_installable_app = is_result_an_installable_app;
  157. }
  158. auto* notifier = view_delegate()->GetNotifier();
  159. if (notifier) {
  160. std::vector<AppListNotifier::Result> notifier_results;
  161. for (const auto* result : display_results)
  162. notifier_results.emplace_back(result->id(), result->metrics_type());
  163. notifier->NotifyResultsUpdated(SearchResultDisplayType::kTile,
  164. notifier_results);
  165. }
  166. // Track play store results and start the timer for recording their impression
  167. // UMA metrics.
  168. std::u16string user_typed_query = GetUserTypedQuery();
  169. if (found_playstore_results && user_typed_query != recent_playstore_query_) {
  170. recent_playstore_query_ = user_typed_query;
  171. playstore_impression_timer_.Stop();
  172. playstore_impression_timer_.Start(
  173. FROM_HERE, base::Milliseconds(kPlayStoreImpressionDelayInMs), this,
  174. &SearchResultTileItemListView::OnPlayStoreImpressionTimer);
  175. // Set the starting time in result view for play store results.
  176. base::TimeTicks result_display_start = base::TimeTicks::Now();
  177. for (size_t i = 0; i < max_search_result_tiles_; ++i) {
  178. SearchResult* result = GetResultViewAt(i)->result();
  179. if (result && IsPlayStoreApp(result)) {
  180. GetResultViewAt(i)->set_result_display_start_time(result_display_start);
  181. }
  182. }
  183. } else if (!found_playstore_results) {
  184. playstore_impression_timer_.Stop();
  185. }
  186. return display_results.size();
  187. }
  188. std::vector<SearchResult*> SearchResultTileItemListView::GetDisplayResults() {
  189. std::u16string raw_query = search_box_->GetText();
  190. std::u16string query;
  191. base::TrimWhitespace(raw_query, base::TRIM_ALL, &query);
  192. // We ask for |max_search_result_tiles_| policy tile results first,
  193. // then add them to their preferred position in the tile list if found.
  194. // Note: Policy tile provides a mechanism to display the result tile at the
  195. // preferred position recommended by display_index() property of the search
  196. // result. This is what policy referred to. It has nothing to do with
  197. // Enterprise policy.
  198. auto policy_tiles_filter =
  199. base::BindRepeating([](const SearchResult& r) -> bool {
  200. return r.display_index() != SearchResultDisplayIndex::kUndefined &&
  201. r.display_type() == SearchResultDisplayType::kTile &&
  202. r.is_recommendation();
  203. });
  204. std::vector<SearchResult*> policy_tiles_results =
  205. is_app_reinstall_recommendation_enabled_ && query.empty()
  206. ? SearchModel::FilterSearchResultsByFunction(
  207. results(), policy_tiles_filter, max_search_result_tiles_)
  208. : std::vector<SearchResult*>();
  209. SearchResult::DisplayType display_type = SearchResultDisplayType::kTile;
  210. size_t display_num = max_search_result_tiles_ - policy_tiles_results.size();
  211. // Do not display the repeat reinstall results or continue reading app in the
  212. // search result list.
  213. auto non_policy_tiles_filter = base::BindRepeating(
  214. [](const SearchResult::DisplayType& display_type,
  215. const SearchResult& r) -> bool {
  216. return r.display_type() == display_type &&
  217. r.result_type() !=
  218. AppListSearchResultType::kPlayStoreReinstallApp &&
  219. r.id() != kInternalAppIdContinueReading;
  220. },
  221. display_type);
  222. std::vector<SearchResult*> display_results =
  223. SearchModel::FilterSearchResultsByFunction(
  224. results(), non_policy_tiles_filter, display_num);
  225. // Policy tile results will be appended to the final tiles list
  226. // based on their specified index. If the requested index is out of
  227. // range of the current list, the result will be appended to the back.
  228. std::sort(policy_tiles_results.begin(), policy_tiles_results.end(),
  229. [](const SearchResult* r1, const SearchResult* r2) -> bool {
  230. return r1->display_index() < r2->display_index();
  231. });
  232. const SearchResultDisplayIndex display_results_last_index =
  233. static_cast<SearchResultDisplayIndex>(display_results.size() - 1);
  234. for (auto* result : policy_tiles_results) {
  235. const SearchResultDisplayIndex result_index = result->display_index();
  236. if (result_index > display_results_last_index) {
  237. display_results.emplace_back(result);
  238. } else {
  239. // TODO(newcomer): Remove this check once we determine the root cause for
  240. // https://crbug.com/992344.
  241. CHECK_GE(result_index, SearchResultDisplayIndex::kFirstIndex);
  242. display_results.emplace(display_results.begin() + result_index, result);
  243. }
  244. }
  245. return display_results;
  246. }
  247. std::u16string SearchResultTileItemListView::GetUserTypedQuery() {
  248. std::u16string search_box_text = search_box_->GetText();
  249. gfx::Range range = search_box_->GetSelectedRange();
  250. std::u16string raw_query = range.is_empty()
  251. ? search_box_text
  252. : search_box_text.substr(0, range.start());
  253. std::u16string query;
  254. base::TrimWhitespace(raw_query, base::TRIM_ALL, &query);
  255. return query;
  256. }
  257. void SearchResultTileItemListView::OnPlayStoreImpressionTimer() {
  258. size_t playstore_app_num = 0;
  259. for (const auto* tile_view : tile_views_) {
  260. SearchResult* result = tile_view->result();
  261. if (result == nullptr)
  262. continue;
  263. if (IsPlayStoreApp(result))
  264. ++playstore_app_num;
  265. }
  266. // Log the UMA metrics of play store impression.
  267. base::RecordAction(
  268. base::UserMetricsAction("AppList_ShowPlayStoreQueryResults"));
  269. DCHECK_LE(playstore_app_num, max_search_result_tiles_);
  270. UMA_HISTOGRAM_EXACT_LINEAR("Apps.AppListPlayStoreSearchAppsDisplayed",
  271. playstore_app_num, max_search_result_tiles_);
  272. }
  273. void SearchResultTileItemListView::CleanUpOnViewHide() {
  274. playstore_impression_timer_.Stop();
  275. recent_playstore_query_.clear();
  276. }
  277. const char* SearchResultTileItemListView::GetClassName() const {
  278. return "SearchResultTileItemListView";
  279. }
  280. void SearchResultTileItemListView::Layout() {
  281. const bool flex = GetContentsBounds().width() < GetPreferredSize().width();
  282. layout_->SetDefaultFlex(flex ? 1 : 0);
  283. layout_->set_between_child_spacing(flex ? 1 : kBetweenItemSpacing);
  284. views::View::Layout();
  285. }
  286. void SearchResultTileItemListView::OnShownChanged() {
  287. SearchResultContainerView::OnShownChanged();
  288. for (const auto* tile_view : tile_views_) {
  289. SearchResult* result = tile_view->result();
  290. if (result == nullptr) {
  291. continue;
  292. }
  293. }
  294. }
  295. void SearchResultTileItemListView::VisibilityChanged(View* starting_from,
  296. bool is_visible) {
  297. SearchResultContainerView::VisibilityChanged(starting_from, is_visible);
  298. // We only do this work when is_visible is false, since this is how we
  299. // receive the event. We filter and only run when shown.
  300. if (is_visible && shown()) {
  301. return;
  302. }
  303. CleanUpOnViewHide();
  304. for (const auto* tile_view : tile_views_) {
  305. SearchResult* result = tile_view->result();
  306. if (result == nullptr) {
  307. continue;
  308. }
  309. }
  310. }
  311. } // namespace ash