recent_apps_view.cc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. // Copyright 2021 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/recent_apps_view.h"
  5. #include <algorithm>
  6. #include <memory>
  7. #include <string>
  8. #include <vector>
  9. #include "ash/app_list/app_list_util.h"
  10. #include "ash/app_list/app_list_view_delegate.h"
  11. #include "ash/app_list/model/app_list_item.h"
  12. #include "ash/app_list/model/app_list_model.h"
  13. #include "ash/app_list/model/search/search_model.h"
  14. #include "ash/app_list/model/search/search_result.h"
  15. #include "ash/app_list/views/app_list_item_view.h"
  16. #include "ash/app_list/views/app_list_keyboard_controller.h"
  17. #include "ash/public/cpp/app_list/app_list_config.h"
  18. #include "ash/public/cpp/app_list/app_list_config_provider.h"
  19. #include "ash/public/cpp/app_list/app_list_notifier.h"
  20. #include "ash/public/cpp/app_list/app_list_types.h"
  21. #include "ash/strings/grit/ash_strings.h"
  22. #include "base/bind.h"
  23. #include "base/check.h"
  24. #include "base/strings/string_util.h"
  25. #include "extensions/common/constants.h"
  26. #include "ui/base/l10n/l10n_util.h"
  27. #include "ui/base/metadata/metadata_impl_macros.h"
  28. #include "ui/views/accessibility/view_accessibility.h"
  29. #include "ui/views/focus/focus_manager.h"
  30. #include "ui/views/layout/box_layout.h"
  31. #include "ui/views/view_utils.h"
  32. #include "url/gurl.h"
  33. namespace ash {
  34. namespace {
  35. constexpr size_t kMinRecommendedApps = 4;
  36. constexpr size_t kMaxRecommendedApps = 5;
  37. // Sorts increasing by display index, then decreasing by position priority.
  38. struct CompareByDisplayIndexAndPositionPriority {
  39. bool operator()(const SearchResult* result1,
  40. const SearchResult* result2) const {
  41. SearchResultDisplayIndex index1 = result1->display_index();
  42. SearchResultDisplayIndex index2 = result2->display_index();
  43. if (index1 != index2)
  44. return index1 < index2;
  45. return result1->position_priority() > result2->position_priority();
  46. }
  47. };
  48. // Converts a search result app ID to an app list item ID.
  49. std::string ItemIdFromAppId(const std::string& app_id) {
  50. // Convert chrome-extension://<id> to just <id>.
  51. if (base::StartsWith(app_id, extensions::kExtensionScheme)) {
  52. GURL url(app_id);
  53. return url.host();
  54. }
  55. return app_id;
  56. }
  57. // Returns a list of recent apps by filtering zero-state suggestion data.
  58. std::vector<SearchResult*> GetRecentApps(
  59. SearchModel* search_model,
  60. const std::vector<std::string>& ids_to_ignore) {
  61. SearchModel::SearchResults* results = search_model->results();
  62. auto filter_function = base::BindRepeating(
  63. [](const std::vector<std::string>& ids_to_ignore,
  64. const SearchResult& r) -> bool {
  65. if (r.display_type() != SearchResultDisplayType::kRecentApps)
  66. return false;
  67. for (std::string id : ids_to_ignore) {
  68. if (base::EndsWith(r.id(), id))
  69. return false;
  70. }
  71. return true;
  72. },
  73. ids_to_ignore);
  74. std::vector<SearchResult*> app_suggestion_results =
  75. SearchModel::FilterSearchResultsByFunction(
  76. results, filter_function,
  77. /*max_results=*/kMaxRecommendedApps);
  78. std::sort(app_suggestion_results.begin(), app_suggestion_results.end(),
  79. CompareByDisplayIndexAndPositionPriority());
  80. return app_suggestion_results;
  81. }
  82. } // namespace
  83. // The grid delegate for each AppListItemView. Recent app icons cannot be
  84. // dragged, so this implementation is mostly a stub.
  85. class RecentAppsView::GridDelegateImpl : public AppListItemView::GridDelegate {
  86. public:
  87. explicit GridDelegateImpl(AppListViewDelegate* view_delegate)
  88. : view_delegate_(view_delegate) {}
  89. GridDelegateImpl(const GridDelegateImpl&) = delete;
  90. GridDelegateImpl& operator=(const GridDelegateImpl&) = delete;
  91. ~GridDelegateImpl() override = default;
  92. // AppListItemView::GridDelegate:
  93. bool IsInFolder() const override { return false; }
  94. void SetSelectedView(AppListItemView* view) override {
  95. DCHECK(view);
  96. if (view == selected_view_)
  97. return;
  98. // Ensure the translucent background of the previous selection goes away.
  99. if (selected_view_)
  100. selected_view_->SchedulePaint();
  101. selected_view_ = view;
  102. // Ensure the translucent background of this selection is painted.
  103. selected_view_->SchedulePaint();
  104. }
  105. void ClearSelectedView() override { selected_view_ = nullptr; }
  106. bool IsSelectedView(const AppListItemView* view) const override {
  107. return view == selected_view_;
  108. }
  109. bool InitiateDrag(AppListItemView* view,
  110. const gfx::Point& location,
  111. const gfx::Point& root_location,
  112. base::OnceClosure drag_start_callback,
  113. base::OnceClosure drag_end_callback) override {
  114. return false;
  115. }
  116. void StartDragAndDropHostDragAfterLongPress() override {}
  117. bool UpdateDragFromItem(bool is_touch,
  118. const ui::LocatedEvent& event) override {
  119. return false;
  120. }
  121. void EndDrag(bool cancel) override {}
  122. void OnAppListItemViewActivated(AppListItemView* pressed_item_view,
  123. const ui::Event& event) override {
  124. // NOTE: Avoid using |item->id()| as the parameter. In some rare situations,
  125. // activating the item may destruct it. Using the reference to an object
  126. // which may be destroyed during the procedure as the function parameter
  127. // may bring the crash like https://crbug.com/990282.
  128. const std::string id = pressed_item_view->item()->id();
  129. view_delegate_->ActivateItem(id, event.flags(),
  130. AppListLaunchedFrom::kLaunchedFromRecentApps);
  131. // `this` may be deleted.
  132. }
  133. private:
  134. AppListViewDelegate* const view_delegate_;
  135. AppListItemView* selected_view_ = nullptr;
  136. };
  137. RecentAppsView::RecentAppsView(AppListKeyboardController* keyboard_controller,
  138. AppListViewDelegate* view_delegate)
  139. : keyboard_controller_(keyboard_controller),
  140. view_delegate_(view_delegate),
  141. grid_delegate_(std::make_unique<GridDelegateImpl>(view_delegate_)) {
  142. DCHECK(keyboard_controller_);
  143. DCHECK(view_delegate_);
  144. layout_ = SetLayoutManager(std::make_unique<views::BoxLayout>(
  145. views::BoxLayout::Orientation::kHorizontal));
  146. layout_->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
  147. layout_->set_cross_axis_alignment(
  148. views::BoxLayout::CrossAxisAlignment::kStart);
  149. GetViewAccessibility().OverrideRole(ax::mojom::Role::kGroup);
  150. // TODO(https://crbug.com/1298211): This needs a designated string resource.
  151. GetViewAccessibility().OverrideName(
  152. l10n_util::GetStringUTF16(IDS_ASH_LAUNCHER_RECENT_APPS_A11Y_NAME));
  153. SetVisible(false);
  154. }
  155. RecentAppsView::~RecentAppsView() {
  156. if (model_)
  157. model_->RemoveObserver(this);
  158. }
  159. void RecentAppsView::OnAppListItemWillBeDeleted(AppListItem* item) {
  160. std::vector<std::string> ids_to_remove;
  161. for (AppListItemView* view : item_views_) {
  162. if (view->item() && view->item() == item)
  163. ids_to_remove.push_back(view->item()->id());
  164. }
  165. if (!ids_to_remove.empty()) {
  166. UpdateResults(ids_to_remove);
  167. UpdateVisibility();
  168. }
  169. }
  170. void RecentAppsView::UpdateAppListConfig(const AppListConfig* app_list_config) {
  171. app_list_config_ = app_list_config;
  172. for (auto* item_view : item_views_)
  173. item_view->UpdateAppListConfig(app_list_config);
  174. }
  175. void RecentAppsView::UpdateResults(
  176. const std::vector<std::string>& ids_to_ignore) {
  177. if (!search_model_ || !model_)
  178. return;
  179. DCHECK(app_list_config_);
  180. item_views_.clear();
  181. RemoveAllChildViews();
  182. std::vector<SearchResult*> apps = GetRecentApps(search_model_, ids_to_ignore);
  183. std::vector<AppListItem*> items;
  184. for (SearchResult* app : apps) {
  185. std::string item_id = ItemIdFromAppId(app->id());
  186. AppListItem* item = model_->FindItem(item_id);
  187. if (item)
  188. items.push_back(item);
  189. }
  190. if (items.size() < kMinRecommendedApps) {
  191. if (auto* notifier = view_delegate_->GetNotifier()) {
  192. notifier->NotifyResultsUpdated(SearchResultDisplayType::kRecentApps, {});
  193. }
  194. return;
  195. }
  196. if (auto* notifier = view_delegate_->GetNotifier()) {
  197. std::vector<AppListNotifier::Result> notifier_results;
  198. for (const SearchResult* app : apps)
  199. notifier_results.emplace_back(app->id(), app->metrics_type());
  200. notifier->NotifyResultsUpdated(SearchResultDisplayType::kRecentApps,
  201. notifier_results);
  202. }
  203. for (AppListItem* item : items) {
  204. auto* item_view = AddChildView(std::make_unique<AppListItemView>(
  205. app_list_config_, grid_delegate_.get(), item, view_delegate_,
  206. AppListItemView::Context::kRecentAppsView));
  207. item_view->UpdateAppListConfig(app_list_config_);
  208. item_views_.push_back(item_view);
  209. item_view->InitializeIconLoader();
  210. }
  211. NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged,
  212. /*send_native_event=*/true);
  213. }
  214. void RecentAppsView::SetModels(SearchModel* search_model, AppListModel* model) {
  215. if (model_ != model) {
  216. if (model_)
  217. model_->RemoveObserver(this);
  218. model_ = model;
  219. if (model_)
  220. model_->AddObserver(this);
  221. }
  222. search_model_ = search_model;
  223. UpdateResults(/*ids_to_ignore=*/{});
  224. UpdateVisibility();
  225. }
  226. void RecentAppsView::UpdateVisibility() {
  227. const bool has_enough_apps = item_views_.size() >= kMinRecommendedApps;
  228. const bool hidden_by_user = view_delegate_->ShouldHideContinueSection();
  229. const bool visible = has_enough_apps && !hidden_by_user;
  230. SetVisible(visible);
  231. if (auto* notifier = view_delegate_->GetNotifier()) {
  232. notifier->NotifyContinueSectionVisibilityChanged(
  233. SearchResultDisplayType::kRecentApps, visible);
  234. }
  235. }
  236. int RecentAppsView::GetItemViewCount() const {
  237. return item_views_.size();
  238. }
  239. AppListItemView* RecentAppsView::GetItemViewAt(int index) const {
  240. if (static_cast<int>(item_views_.size()) <= index)
  241. return nullptr;
  242. return item_views_[index];
  243. }
  244. void RecentAppsView::DisableFocusForShowingActiveFolder(bool disabled) {
  245. for (views::View* child : children())
  246. child->SetEnabled(!disabled);
  247. // Prevent items from being accessed by ChromeVox.
  248. SetViewIgnoredForAccessibility(this, disabled);
  249. }
  250. bool RecentAppsView::OnKeyPressed(const ui::KeyEvent& event) {
  251. if (event.key_code() == ui::VKEY_UP) {
  252. MoveFocusUp();
  253. return true;
  254. }
  255. if (event.key_code() == ui::VKEY_DOWN) {
  256. MoveFocusDown();
  257. return true;
  258. }
  259. return false;
  260. }
  261. void RecentAppsView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
  262. // The AppsGridView's space between items is the sum of the padding on left
  263. // and on right of the individual tiles. Because of rounding errors, there can
  264. // be an actual difference of 1px over the actual distribution of space
  265. // needed, and because this is not compensated on the other columns, the grid
  266. // carries over the error making it progressively more significant for each
  267. // column. For the RecentAppsView tiles to match the grid we need to calculate
  268. // padding as the AppsGridView does to account for the rounding errors and
  269. // then double it, so it is exactly the same spacing as the AppsGridView.
  270. layout_->set_between_child_spacing(2 * CalculateTilePadding());
  271. }
  272. void RecentAppsView::MoveFocusUp() {
  273. DVLOG(1) << __FUNCTION__;
  274. // This function should only run when a child has focus.
  275. DCHECK(Contains(GetFocusManager()->GetFocusedView()));
  276. DCHECK(!children().empty());
  277. keyboard_controller_->MoveFocusUpFromRecents();
  278. }
  279. void RecentAppsView::MoveFocusDown() {
  280. DVLOG(1) << __FUNCTION__;
  281. // This function should only run when a child has focus.
  282. DCHECK(Contains(GetFocusManager()->GetFocusedView()));
  283. int column = GetColumnOfFocusedChild();
  284. DCHECK_GE(column, 0);
  285. keyboard_controller_->MoveFocusDownFromRecents(column);
  286. }
  287. int RecentAppsView::GetColumnOfFocusedChild() const {
  288. int column = 0;
  289. for (views::View* child : children()) {
  290. if (!views::IsViewClass<AppListItemView>(child))
  291. continue;
  292. if (child->HasFocus())
  293. return column;
  294. ++column;
  295. }
  296. return -1;
  297. }
  298. int RecentAppsView::CalculateTilePadding() const {
  299. int content_width = GetContentsBounds().width();
  300. int tile_width = app_list_config_->grid_tile_width();
  301. int width_to_distribute = content_width - kMaxRecommendedApps * tile_width;
  302. return width_to_distribute / ((kMaxRecommendedApps - 1) * 2);
  303. }
  304. BEGIN_METADATA(RecentAppsView, views::View)
  305. END_METADATA
  306. } // namespace ash