1 // Copyright 2018 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 
5 #include "chrome/browser/ui/app_list/app_list_client_impl.h"
6 
7 #include <stddef.h>
8 
9 #include <utility>
10 #include <vector>
11 
12 #include "ash/public/cpp/app_list/app_list_controller.h"
13 #include "ash/public/cpp/new_window_delegate.h"
14 #include "ash/public/cpp/tablet_mode.h"
15 #include "base/bind.h"
16 #include "base/metrics/histogram_functions.h"
17 #include "base/metrics/histogram_macros.h"
18 #include "base/metrics/user_metrics.h"
19 #include "base/strings/strcat.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/browser/profiles/profile_manager.h"
22 #include "chrome/browser/search_engines/template_url_service_factory.h"
23 #include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
24 #include "chrome/browser/ui/app_list/app_list_model_updater.h"
25 #include "chrome/browser/ui/app_list/app_list_notifier_impl.h"
26 #include "chrome/browser/ui/app_list/app_list_syncable_service.h"
27 #include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h"
28 #include "chrome/browser/ui/app_list/app_sync_ui_state_watcher.h"
29 #include "chrome/browser/ui/app_list/search/app_result.h"
30 #include "chrome/browser/ui/app_list/search/chrome_search_result.h"
31 #include "chrome/browser/ui/app_list/search/cros_action_history/cros_action_recorder.h"
32 #include "chrome/browser/ui/app_list/search/search_controller.h"
33 #include "chrome/browser/ui/app_list/search/search_controller_factory.h"
34 #include "chrome/browser/ui/app_list/search/search_result_ranker/app_launch_data.h"
35 #include "chrome/browser/ui/app_list/search/search_result_ranker/ranking_item_util.h"
36 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
37 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_util.h"
38 #include "chrome/browser/ui/browser_commands.h"
39 #include "chrome/browser/ui/browser_navigator.h"
40 #include "chrome/browser/ui/browser_navigator_params.h"
41 #include "extensions/common/extension.h"
42 #include "ui/display/display.h"
43 #include "ui/display/screen.h"
44 #include "ui/display/types/display_constants.h"
45 #include "ui/gfx/geometry/rect.h"
46 
47 namespace {
48 
49 AppListClientImpl* g_app_list_client_instance = nullptr;
50 
IsTabletMode()51 bool IsTabletMode() {
52   return ash::TabletMode::Get() && ash::TabletMode::Get()->InTabletMode();
53 }
54 
55 }  // namespace
56 
AppListClientImpl()57 AppListClientImpl::AppListClientImpl()
58     : app_list_controller_(ash::AppListController::Get()),
59       app_list_notifier_(
60           std::make_unique<AppListNotifierImpl>(app_list_controller_)) {
61   app_list_controller_->SetClient(this);
62   user_manager::UserManager::Get()->AddSessionStateObserver(this);
63 
64   DCHECK(!g_app_list_client_instance);
65   g_app_list_client_instance = this;
66 }
67 
~AppListClientImpl()68 AppListClientImpl::~AppListClientImpl() {
69   SetProfile(nullptr);
70 
71   user_manager::UserManager::Get()->RemoveSessionStateObserver(this);
72 
73   DCHECK_EQ(this, g_app_list_client_instance);
74   g_app_list_client_instance = nullptr;
75 
76   if (app_list_controller_)
77     app_list_controller_->SetClient(nullptr);
78 }
79 
80 // static
GetInstance()81 AppListClientImpl* AppListClientImpl::GetInstance() {
82   return g_app_list_client_instance;
83 }
84 
OnAppListControllerDestroyed()85 void AppListClientImpl::OnAppListControllerDestroyed() {
86   // |app_list_controller_| could be released earlier, e.g. starting a kiosk
87   // next session.
88   app_list_controller_ = nullptr;
89   if (current_model_updater_)
90     current_model_updater_->SetActive(false);
91 }
92 
StartSearch(const base::string16 & trimmed_query)93 void AppListClientImpl::StartSearch(const base::string16& trimmed_query) {
94   if (search_controller_) {
95     search_controller_->Start(trimmed_query);
96     OnSearchStarted();
97   }
98 }
99 
OpenSearchResult(const std::string & result_id,int event_flags,ash::AppListLaunchedFrom launched_from,ash::AppListLaunchType launch_type,int suggestion_index,bool launch_as_default)100 void AppListClientImpl::OpenSearchResult(const std::string& result_id,
101                                          int event_flags,
102                                          ash::AppListLaunchedFrom launched_from,
103                                          ash::AppListLaunchType launch_type,
104                                          int suggestion_index,
105                                          bool launch_as_default) {
106   if (!search_controller_)
107     return;
108 
109   ChromeSearchResult* result = search_controller_->FindSearchResult(result_id);
110   if (!result)
111     return;
112 
113   app_list::AppLaunchData app_launch_data;
114   app_launch_data.id = result_id;
115   app_launch_data.ranking_item_type =
116       app_list::RankingItemTypeFromSearchResult(*result);
117   app_launch_data.launch_type = launch_type;
118   app_launch_data.launched_from = launched_from;
119   app_launch_data.suggestion_index = suggestion_index;
120 
121   if (launch_type == ash::AppListLaunchType::kAppSearchResult &&
122       launched_from == ash::AppListLaunchedFrom::kLaunchedFromSearchBox &&
123       app_launch_data.ranking_item_type == app_list::RankingItemType::kApp &&
124       search_controller_->GetLastQueryLength() != 0) {
125     ash::RecordSuccessfulAppLaunchUsingSearch(
126         launched_from, search_controller_->GetLastQueryLength());
127   }
128 
129   // Send training signal to search controller.
130   search_controller_->Train(std::move(app_launch_data));
131 
132   RecordSearchResultOpenTypeHistogram(launched_from, result->metrics_type(),
133                                       IsTabletMode());
134 
135   if (launch_as_default)
136     RecordDefaultSearchResultOpenTypeHistogram(result->metrics_type());
137 
138   if (!search_controller_->GetLastQueryLength() &&
139       launched_from == ash::AppListLaunchedFrom::kLaunchedFromSearchBox)
140     RecordZeroStateSuggestionOpenTypeHistogram(result->metrics_type());
141 
142   // OpenResult may cause |result| to be deleted.
143   search_controller_->OpenResult(result, event_flags);
144 }
145 
InvokeSearchResultAction(const std::string & result_id,int action_index)146 void AppListClientImpl::InvokeSearchResultAction(const std::string& result_id,
147                                                  int action_index) {
148   if (!search_controller_)
149     return;
150   ChromeSearchResult* result = search_controller_->FindSearchResult(result_id);
151   if (result)
152     search_controller_->InvokeResultAction(result, action_index);
153 }
154 
GetSearchResultContextMenuModel(const std::string & result_id,GetContextMenuModelCallback callback)155 void AppListClientImpl::GetSearchResultContextMenuModel(
156     const std::string& result_id,
157     GetContextMenuModelCallback callback) {
158   if (!search_controller_) {
159     std::move(callback).Run(nullptr);
160     return;
161   }
162   ChromeSearchResult* result = search_controller_->FindSearchResult(result_id);
163   if (!result) {
164     std::move(callback).Run(nullptr);
165     return;
166   }
167   result->GetContextMenuModel(base::BindOnce(
168       [](GetContextMenuModelCallback callback,
169          std::unique_ptr<ui::SimpleMenuModel> menu_model) {
170         std::move(callback).Run(std::move(menu_model));
171       },
172       std::move(callback)));
173 }
174 
ViewClosing()175 void AppListClientImpl::ViewClosing() {
176   display_id_ = display::kInvalidDisplayId;
177   if (search_controller_)
178     search_controller_->ViewClosing();
179 }
180 
ViewShown(int64_t display_id)181 void AppListClientImpl::ViewShown(int64_t display_id) {
182   if (current_model_updater_) {
183     base::RecordAction(base::UserMetricsAction("Launcher_Show"));
184     base::UmaHistogramSparse("Apps.AppListBadgedAppsCount",
185                              current_model_updater_->BadgedItemCount());
186   }
187   display_id_ = display_id;
188 }
189 
ActivateItem(int profile_id,const std::string & id,int event_flags)190 void AppListClientImpl::ActivateItem(int profile_id,
191                                      const std::string& id,
192                                      int event_flags) {
193   auto* requested_model_updater = profile_model_mappings_[profile_id];
194 
195   // Pointless to notify the AppListModelUpdater of the activated item if the
196   // |requested_model_updater| is not the current one, which means that the
197   // active profile is changed. The same rule applies to the GetContextMenuModel
198   // and ContextMenuItemSelected.
199   if (requested_model_updater != current_model_updater_ ||
200       !requested_model_updater) {
201     return;
202   }
203 
204   // Send a training signal to the search controller.
205   const auto* item = current_model_updater_->FindItem(id);
206   if (item) {
207     app_list::AppLaunchData app_launch_data;
208     app_launch_data.id = id;
209     app_launch_data.ranking_item_type =
210         app_list::RankingItemTypeFromChromeAppListItem(*item);
211     app_launch_data.launched_from = ash::AppListLaunchedFrom::kLaunchedFromGrid;
212     search_controller_->Train(std::move(app_launch_data));
213   }
214 
215   requested_model_updater->ActivateChromeItem(id, event_flags);
216 }
217 
GetContextMenuModel(int profile_id,const std::string & id,GetContextMenuModelCallback callback)218 void AppListClientImpl::GetContextMenuModel(
219     int profile_id,
220     const std::string& id,
221     GetContextMenuModelCallback callback) {
222   auto* requested_model_updater = profile_model_mappings_[profile_id];
223   if (requested_model_updater != current_model_updater_ ||
224       !requested_model_updater) {
225     std::move(callback).Run(nullptr);
226     return;
227   }
228   requested_model_updater->GetContextMenuModel(
229       id, base::BindOnce(
230               [](GetContextMenuModelCallback callback,
231                  std::unique_ptr<ui::SimpleMenuModel> menu_model) {
232                 std::move(callback).Run(std::move(menu_model));
233               },
234               std::move(callback)));
235 }
236 
OnAppListVisibilityWillChange(bool visible)237 void AppListClientImpl::OnAppListVisibilityWillChange(bool visible) {
238   app_list_target_visibility_ = visible;
239   if (visible && search_controller_)
240     search_controller_->Start(base::string16());
241 }
242 
OnAppListVisibilityChanged(bool visible)243 void AppListClientImpl::OnAppListVisibilityChanged(bool visible) {
244   app_list_visible_ = visible;
245   if (visible && search_controller_)
246     search_controller_->AppListShown();
247 }
248 
OnItemAdded(int profile_id,std::unique_ptr<ash::AppListItemMetadata> item)249 void AppListClientImpl::OnItemAdded(
250     int profile_id,
251     std::unique_ptr<ash::AppListItemMetadata> item) {
252   auto* requested_model_updater = profile_model_mappings_[profile_id];
253   if (!requested_model_updater)
254     return;
255   requested_model_updater->OnItemAdded(std::move(item));
256 }
257 
OnItemUpdated(int profile_id,std::unique_ptr<ash::AppListItemMetadata> item)258 void AppListClientImpl::OnItemUpdated(
259     int profile_id,
260     std::unique_ptr<ash::AppListItemMetadata> item) {
261   auto* requested_model_updater = profile_model_mappings_[profile_id];
262   if (!requested_model_updater)
263     return;
264   requested_model_updater->OnItemUpdated(std::move(item));
265 }
266 
OnFolderDeleted(int profile_id,std::unique_ptr<ash::AppListItemMetadata> item)267 void AppListClientImpl::OnFolderDeleted(
268     int profile_id,
269     std::unique_ptr<ash::AppListItemMetadata> item) {
270   auto* requested_model_updater = profile_model_mappings_[profile_id];
271   if (!requested_model_updater)
272     return;
273   DCHECK(item->is_folder);
274   requested_model_updater->OnFolderDeleted(std::move(item));
275 }
276 
OnPageBreakItemDeleted(int profile_id,const std::string & id)277 void AppListClientImpl::OnPageBreakItemDeleted(int profile_id,
278                                                const std::string& id) {
279   auto* requested_model_updater = profile_model_mappings_[profile_id];
280   if (!requested_model_updater)
281     return;
282   requested_model_updater->OnPageBreakItemDeleted(id);
283 }
284 
OnSearchResultVisibilityChanged(const std::string & id,bool visibility)285 void AppListClientImpl::OnSearchResultVisibilityChanged(const std::string& id,
286                                                         bool visibility) {
287   if (!search_controller_)
288     return;
289 
290   ChromeSearchResult* result = search_controller_->FindSearchResult(id);
291   if (result == nullptr) {
292     return;
293   }
294   result->OnVisibilityChanged(visibility);
295 }
296 
OnQuickSettingsChanged(const std::string & setting_name,const std::map<std::string,int> & values)297 void AppListClientImpl::OnQuickSettingsChanged(
298     const std::string& setting_name,
299     const std::map<std::string, int>& values) {
300   // CrOS action recorder.
301   app_list::CrOSActionRecorder::GetCrosActionRecorder()->RecordAction(
302       {base::StrCat({"SettingsChanged-", setting_name})}, values);
303 }
304 
ActiveUserChanged(user_manager::User * active_user)305 void AppListClientImpl::ActiveUserChanged(user_manager::User* active_user) {
306   if (!active_user->is_profile_created())
307     return;
308 
309   UpdateProfile();
310 }
311 
UpdateProfile()312 void AppListClientImpl::UpdateProfile() {
313   Profile* profile = ProfileManager::GetActiveUserProfile();
314   app_list::AppListSyncableService* syncable_service =
315       app_list::AppListSyncableServiceFactory::GetForProfile(profile);
316   // AppListSyncableService is null in tests.
317   if (syncable_service)
318     SetProfile(profile);
319 }
320 
SetProfile(Profile * new_profile)321 void AppListClientImpl::SetProfile(Profile* new_profile) {
322   if (profile_ == new_profile)
323     return;
324 
325   if (profile_) {
326     DCHECK(current_model_updater_);
327     current_model_updater_->SetActive(false);
328 
329     search_controller_.reset();
330     app_sync_ui_state_watcher_.reset();
331     current_model_updater_ = nullptr;
332   }
333 
334   template_url_service_observer_.RemoveAll();
335 
336   profile_ = new_profile;
337   if (!profile_)
338     return;
339 
340   // If we are in guest mode, the new profile should be an OffTheRecord profile.
341   // Otherwise, this may later hit a check (same condition as this one) in
342   // Browser::Browser when opening links in a browser window (see
343   // http://crbug.com/460437).
344   DCHECK(!profile_->IsGuestSession() || profile_->IsOffTheRecord())
345       << "Guest mode must use OffTheRecord profile";
346 
347   template_url_service_observer_.Add(
348       TemplateURLServiceFactory::GetForProfile(profile_));
349 
350   app_list::AppListSyncableService* syncable_service =
351       app_list::AppListSyncableServiceFactory::GetForProfile(profile_);
352 
353   current_model_updater_ = syncable_service->GetModelUpdater();
354   current_model_updater_->SetActive(true);
355 
356   // On ChromeOS, there is no way to sign-off just one user. When signing off
357   // all users, AppListClientImpl instance is destructed before profiles are
358   // unloaded. So we don't need to remove elements from
359   // |profile_model_mappings_| explicitly.
360   profile_model_mappings_[current_model_updater_->model_id()] =
361       current_model_updater_;
362 
363   app_sync_ui_state_watcher_ =
364       std::make_unique<AppSyncUIStateWatcher>(profile_, current_model_updater_);
365 
366   SetUpSearchUI();
367   OnTemplateURLServiceChanged();
368 
369   // Clear search query.
370   current_model_updater_->UpdateSearchBox(base::string16(),
371                                           false /* initiated_by_user */);
372 }
373 
SetUpSearchUI()374 void AppListClientImpl::SetUpSearchUI() {
375   search_controller_ = app_list::CreateSearchController(
376       profile_, current_model_updater_, this, GetNotifier());
377 
378   // Refresh the results used for the suggestion chips with empty query.
379   // This fixes crbug.com/999287.
380   StartSearch(base::string16());
381 }
382 
search_controller()383 app_list::SearchController* AppListClientImpl::search_controller() {
384   return search_controller_.get();
385 }
386 
GetModelUpdaterForTest()387 AppListModelUpdater* AppListClientImpl::GetModelUpdaterForTest() {
388   return current_model_updater_;
389 }
390 
OnTemplateURLServiceChanged()391 void AppListClientImpl::OnTemplateURLServiceChanged() {
392   DCHECK(current_model_updater_);
393 
394   TemplateURLService* template_url_service =
395       TemplateURLServiceFactory::GetForProfile(profile_);
396   const TemplateURL* default_provider =
397       template_url_service->GetDefaultSearchProvider();
398   const bool is_google =
399       default_provider &&
400       default_provider->GetEngineType(
401           template_url_service->search_terms_data()) == SEARCH_ENGINE_GOOGLE;
402 
403   current_model_updater_->SetSearchEngineIsGoogle(is_google);
404 }
405 
ShowAppList()406 void AppListClientImpl::ShowAppList() {
407   // This may not work correctly if the profile passed in is different from the
408   // one the ash Shell is currently using.
409   if (!app_list_controller_)
410     return;
411   app_list_controller_->ShowAppList();
412 }
413 
GetCurrentAppListProfile() const414 Profile* AppListClientImpl::GetCurrentAppListProfile() const {
415   return ChromeLauncherController::instance()->profile();
416 }
417 
GetAppListController() const418 ash::AppListController* AppListClientImpl::GetAppListController() const {
419   return app_list_controller_;
420 }
421 
DismissView()422 void AppListClientImpl::DismissView() {
423   if (!app_list_controller_)
424     return;
425   app_list_controller_->DismissAppList();
426 }
427 
GetAppListWindow()428 aura::Window* AppListClientImpl::GetAppListWindow() {
429   return app_list_controller_->GetWindow();
430 }
431 
GetAppListDisplayId()432 int64_t AppListClientImpl::GetAppListDisplayId() {
433   return display_id_;
434 }
435 
GetAppInfoDialogBounds(GetAppInfoDialogBoundsCallback callback)436 void AppListClientImpl::GetAppInfoDialogBounds(
437     GetAppInfoDialogBoundsCallback callback) {
438   if (!app_list_controller_) {
439     LOG(ERROR) << "app_list_controller_ is null";
440     std::move(callback).Run(gfx::Rect());
441     return;
442   }
443   app_list_controller_->GetAppInfoDialogBounds(std::move(callback));
444 }
445 
IsAppPinned(const std::string & app_id)446 bool AppListClientImpl::IsAppPinned(const std::string& app_id) {
447   return ChromeLauncherController::instance()->IsAppPinned(app_id);
448 }
449 
IsAppOpen(const std::string & app_id) const450 bool AppListClientImpl::IsAppOpen(const std::string& app_id) const {
451   return ChromeLauncherController::instance()->IsOpen(ash::ShelfID(app_id));
452 }
453 
PinApp(const std::string & app_id)454 void AppListClientImpl::PinApp(const std::string& app_id) {
455   ChromeLauncherController::instance()->PinAppWithID(app_id);
456 }
457 
UnpinApp(const std::string & app_id)458 void AppListClientImpl::UnpinApp(const std::string& app_id) {
459   ChromeLauncherController::instance()->UnpinAppWithID(app_id);
460 }
461 
GetPinnable(const std::string & app_id)462 AppListControllerDelegate::Pinnable AppListClientImpl::GetPinnable(
463     const std::string& app_id) {
464   return GetPinnableForAppID(app_id,
465                              ChromeLauncherController::instance()->profile());
466 }
467 
CreateNewWindow(bool incognito)468 void AppListClientImpl::CreateNewWindow(bool incognito) {
469   ash::NewWindowDelegate::GetInstance()->NewWindow(incognito);
470 }
471 
OpenURL(Profile * profile,const GURL & url,ui::PageTransition transition,WindowOpenDisposition disposition)472 void AppListClientImpl::OpenURL(Profile* profile,
473                                 const GURL& url,
474                                 ui::PageTransition transition,
475                                 WindowOpenDisposition disposition) {
476   NavigateParams params(profile, url, transition);
477   params.disposition = disposition;
478   Navigate(&params);
479 }
480 
ActivateApp(Profile * profile,const extensions::Extension * extension,AppListSource source,int event_flags)481 void AppListClientImpl::ActivateApp(Profile* profile,
482                                     const extensions::Extension* extension,
483                                     AppListSource source,
484                                     int event_flags) {
485   // Platform apps treat activations as a launch. The app can decide whether to
486   // show a new window or focus an existing window as it sees fit.
487   if (extension->is_platform_app()) {
488     LaunchApp(profile, extension, source, event_flags, GetAppListDisplayId());
489     return;
490   }
491 
492   ChromeLauncherController::instance()->ActivateApp(
493       extension->id(), AppListSourceToLaunchSource(source), event_flags,
494       GetAppListDisplayId());
495 
496   if (!IsTabletMode())
497     DismissView();
498 }
499 
LaunchApp(Profile * profile,const extensions::Extension * extension,AppListSource source,int event_flags,int64_t display_id)500 void AppListClientImpl::LaunchApp(Profile* profile,
501                                   const extensions::Extension* extension,
502                                   AppListSource source,
503                                   int event_flags,
504                                   int64_t display_id) {
505   ChromeLauncherController::instance()->LaunchApp(
506       ash::ShelfID(extension->id()), AppListSourceToLaunchSource(source),
507       event_flags, display_id);
508 
509   if (!IsTabletMode())
510     DismissView();
511 }
512 
NotifySearchResultsForLogging(const base::string16 & trimmed_query,const ash::SearchResultIdWithPositionIndices & results,int position_index)513 void AppListClientImpl::NotifySearchResultsForLogging(
514     const base::string16& trimmed_query,
515     const ash::SearchResultIdWithPositionIndices& results,
516     int position_index) {
517   if (search_controller_) {
518     search_controller_->OnSearchResultsDisplayed(trimmed_query, results,
519                                                  position_index);
520   }
521 }
522 
GetNotifier()523 ash::AppListNotifier* AppListClientImpl::GetNotifier() {
524   return app_list_notifier_.get();
525 }
526 
AppListSourceToLaunchSource(AppListSource source)527 ash::ShelfLaunchSource AppListClientImpl::AppListSourceToLaunchSource(
528     AppListSource source) {
529   switch (source) {
530     case LAUNCH_FROM_APP_LIST:
531       return ash::LAUNCH_FROM_APP_LIST;
532     case LAUNCH_FROM_APP_LIST_SEARCH:
533       return ash::LAUNCH_FROM_APP_LIST_SEARCH;
534     default:
535       return ash::LAUNCH_FROM_UNKNOWN;
536   }
537 }
538