app_list_view_delegate.cc revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
1// Copyright (c) 2012 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_view_delegate.h"
6
7#include <vector>
8
9#include "base/callback.h"
10#include "base/files/file_path.h"
11#include "base/metrics/user_metrics.h"
12#include "base/stl_util.h"
13#include "chrome/browser/browser_process.h"
14#include "chrome/browser/chrome_notification_types.h"
15#include "chrome/browser/extensions/extension_service.h"
16#include "chrome/browser/profiles/profile_info_cache.h"
17#include "chrome/browser/profiles/profile_manager.h"
18#include "chrome/browser/search/hotword_service.h"
19#include "chrome/browser/search/hotword_service_factory.h"
20#include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
21#include "chrome/browser/ui/app_list/app_list_service.h"
22#include "chrome/browser/ui/app_list/app_list_syncable_service.h"
23#include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h"
24#include "chrome/browser/ui/app_list/search/search_controller.h"
25#include "chrome/browser/ui/app_list/start_page_service.h"
26#include "chrome/browser/ui/browser_finder.h"
27#include "chrome/browser/ui/chrome_pages.h"
28#include "chrome/browser/ui/host_desktop.h"
29#include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
30#include "chrome/browser/web_applications/web_app.h"
31#include "chrome/common/extensions/extension_constants.h"
32#include "chrome/common/url_constants.h"
33#include "content/public/browser/browser_thread.h"
34#include "content/public/browser/page_navigator.h"
35#include "content/public/browser/user_metrics.h"
36#include "grit/theme_resources.h"
37#include "ui/app_list/app_list_switches.h"
38#include "ui/app_list/app_list_view_delegate_observer.h"
39#include "ui/app_list/search_box_model.h"
40#include "ui/app_list/speech_ui_model.h"
41#include "ui/base/resource/resource_bundle.h"
42
43#if defined(TOOLKIT_VIEWS)
44#include "ui/views/controls/webview/webview.h"
45#endif
46
47#if defined(USE_AURA)
48#include "ui/keyboard/keyboard_util.h"
49#endif
50
51#if defined(USE_ASH)
52#include "ash/shell.h"
53#include "ash/wm/maximize_mode/maximize_mode_controller.h"
54#include "chrome/browser/ui/ash/app_list/app_sync_ui_state_watcher.h"
55#endif
56
57#if defined(OS_WIN)
58#include "chrome/browser/web_applications/web_app_win.h"
59#endif
60
61
62namespace chrome {
63const char kAppLauncherCategoryTag[] = "AppLauncher";
64}  // namespace chrome
65
66namespace {
67
68const int kAutoLaunchDefaultTimeoutMilliSec = 50;
69
70#if defined(OS_WIN)
71void CreateShortcutInWebAppDir(
72    const base::FilePath& app_data_dir,
73    base::Callback<void(const base::FilePath&)> callback,
74    const web_app::ShortcutInfo& info) {
75  content::BrowserThread::PostTaskAndReplyWithResult(
76      content::BrowserThread::FILE,
77      FROM_HERE,
78      base::Bind(web_app::CreateShortcutInWebAppDir, app_data_dir, info),
79      callback);
80}
81#endif
82
83void PopulateUsers(const ProfileInfoCache& profile_info,
84                   const base::FilePath& active_profile_path,
85                   app_list::AppListViewDelegate::Users* users) {
86  users->clear();
87  const size_t count = profile_info.GetNumberOfProfiles();
88  for (size_t i = 0; i < count; ++i) {
89    // Don't display managed users.
90    if (profile_info.ProfileIsSupervisedAtIndex(i))
91      continue;
92
93    app_list::AppListViewDelegate::User user;
94    user.name = profile_info.GetNameOfProfileAtIndex(i);
95    user.email = profile_info.GetUserNameOfProfileAtIndex(i);
96    user.profile_path = profile_info.GetPathOfProfileAtIndex(i);
97    user.active = active_profile_path == user.profile_path;
98    users->push_back(user);
99  }
100}
101
102}  // namespace
103
104AppListViewDelegate::AppListViewDelegate(Profile* profile,
105                                         AppListControllerDelegate* controller)
106    : controller_(controller), profile_(profile), model_(NULL) {
107  CHECK(controller_);
108
109  ProfileManager* profile_manager = g_browser_process->profile_manager();
110  profile_manager->GetProfileInfoCache().AddObserver(this);
111
112  app_list::StartPageService* service =
113      app_list::StartPageService::Get(profile_);
114  speech_ui_.reset(new app_list::SpeechUIModel(
115      service ? service->state() : app_list::SPEECH_RECOGNITION_OFF));
116
117#if defined(GOOGLE_CHROME_BUILD)
118  speech_ui_->set_logo(
119      *ui::ResourceBundle::GetSharedInstance().
120      GetImageSkiaNamed(IDR_APP_LIST_GOOGLE_LOGO_VOICE_SEARCH));
121#endif
122
123  OnProfileChanged();  // sets model_
124  if (service)
125    service->AddObserver(this);
126}
127
128AppListViewDelegate::~AppListViewDelegate() {
129  app_list::StartPageService* service =
130      app_list::StartPageService::Get(profile_);
131  if (service)
132    service->RemoveObserver(this);
133  g_browser_process->
134      profile_manager()->GetProfileInfoCache().RemoveObserver(this);
135
136  // Ensure search controller is released prior to speech_ui_.
137  search_controller_.reset();
138}
139
140void AppListViewDelegate::OnHotwordStateChanged(bool started) {
141  if (started) {
142    if (speech_ui_->state() == app_list::SPEECH_RECOGNITION_READY) {
143      OnSpeechRecognitionStateChanged(
144          app_list::SPEECH_RECOGNITION_HOTWORD_LISTENING);
145    }
146  } else {
147    if (speech_ui_->state() == app_list::SPEECH_RECOGNITION_HOTWORD_LISTENING)
148      OnSpeechRecognitionStateChanged(app_list::SPEECH_RECOGNITION_READY);
149  }
150}
151
152void AppListViewDelegate::OnHotwordRecognized() {
153  DCHECK_EQ(app_list::SPEECH_RECOGNITION_HOTWORD_LISTENING,
154            speech_ui_->state());
155  ToggleSpeechRecognition();
156}
157
158void AppListViewDelegate::OnProfileChanged() {
159  model_ = app_list::AppListSyncableServiceFactory::GetForProfile(
160      profile_)->model();
161
162  search_controller_.reset(new app_list::SearchController(
163      profile_, model_->search_box(), model_->results(),
164      speech_ui_.get(), controller_));
165
166#if defined(USE_ASH)
167  app_sync_ui_state_watcher_.reset(new AppSyncUIStateWatcher(profile_, model_));
168#endif
169
170  // Don't populate the app list users if we are on the ash desktop.
171  chrome::HostDesktopType desktop = chrome::GetHostDesktopTypeForNativeWindow(
172      controller_->GetAppListWindow());
173  if (desktop == chrome::HOST_DESKTOP_TYPE_ASH)
174    return;
175
176  // Populate the app list users.
177  PopulateUsers(g_browser_process->profile_manager()->GetProfileInfoCache(),
178                profile_->GetPath(), &users_);
179
180  FOR_EACH_OBSERVER(app_list::AppListViewDelegateObserver,
181                    observers_,
182                    OnProfilesChanged());
183}
184
185bool AppListViewDelegate::ForceNativeDesktop() const {
186  return controller_->ForceNativeDesktop();
187}
188
189void AppListViewDelegate::SetProfileByPath(const base::FilePath& profile_path) {
190  DCHECK(model_);
191
192  // The profile must be loaded before this is called.
193  profile_ =
194      g_browser_process->profile_manager()->GetProfileByPath(profile_path);
195  DCHECK(profile_);
196
197  OnProfileChanged();
198
199  // Clear search query.
200  model_->search_box()->SetText(base::string16());
201}
202
203app_list::AppListModel* AppListViewDelegate::GetModel() {
204  return model_;
205}
206
207app_list::SpeechUIModel* AppListViewDelegate::GetSpeechUI() {
208  return speech_ui_.get();
209}
210
211void AppListViewDelegate::GetShortcutPathForApp(
212    const std::string& app_id,
213    const base::Callback<void(const base::FilePath&)>& callback) {
214#if defined(OS_WIN)
215  ExtensionService* service = profile_->GetExtensionService();
216  DCHECK(service);
217  const extensions::Extension* extension =
218      service->GetInstalledExtension(app_id);
219  if (!extension) {
220    callback.Run(base::FilePath());
221    return;
222  }
223
224  base::FilePath app_data_dir(
225      web_app::GetWebAppDataDirectory(profile_->GetPath(),
226                                      extension->id(),
227                                      GURL()));
228
229  web_app::UpdateShortcutInfoAndIconForApp(
230      extension,
231      profile_,
232      base::Bind(CreateShortcutInWebAppDir, app_data_dir, callback));
233#else
234  callback.Run(base::FilePath());
235#endif
236}
237
238void AppListViewDelegate::StartSearch() {
239  if (search_controller_)
240    search_controller_->Start();
241}
242
243void AppListViewDelegate::StopSearch() {
244  if (search_controller_)
245    search_controller_->Stop();
246}
247
248void AppListViewDelegate::OpenSearchResult(
249    app_list::SearchResult* result,
250    bool auto_launch,
251    int event_flags) {
252  if (auto_launch)
253    base::RecordAction(base::UserMetricsAction("AppList_AutoLaunched"));
254  search_controller_->OpenResult(result, event_flags);
255}
256
257void AppListViewDelegate::InvokeSearchResultAction(
258    app_list::SearchResult* result,
259    int action_index,
260    int event_flags) {
261  search_controller_->InvokeResultAction(result, action_index, event_flags);
262}
263
264base::TimeDelta AppListViewDelegate::GetAutoLaunchTimeout() {
265  return auto_launch_timeout_;
266}
267
268void AppListViewDelegate::AutoLaunchCanceled() {
269  base::RecordAction(base::UserMetricsAction("AppList_AutoLaunchCanceled"));
270  auto_launch_timeout_ = base::TimeDelta();
271}
272
273void AppListViewDelegate::ViewInitialized() {
274  app_list::StartPageService* service =
275      app_list::StartPageService::Get(profile_);
276  if (service) {
277    service->AppListShown();
278    if (service->HotwordEnabled()) {
279      HotwordService* hotword_service =
280          HotwordServiceFactory::GetForProfile(profile_);
281      if (hotword_service)
282        hotword_service->RequestHotwordSession(this);
283    }
284  }
285}
286
287void AppListViewDelegate::Dismiss()  {
288  controller_->DismissView();
289}
290
291void AppListViewDelegate::ViewClosing() {
292  controller_->ViewClosing();
293
294  app_list::StartPageService* service =
295      app_list::StartPageService::Get(profile_);
296  if (service) {
297    service->AppListHidden();
298    if (service->HotwordEnabled()) {
299      HotwordService* hotword_service =
300          HotwordServiceFactory::GetForProfile(profile_);
301      if (hotword_service)
302        hotword_service->StopHotwordSession(this);
303    }
304  }
305}
306
307gfx::ImageSkia AppListViewDelegate::GetWindowIcon() {
308  return controller_->GetWindowIcon();
309}
310
311void AppListViewDelegate::OpenSettings() {
312  ExtensionService* service = profile_->GetExtensionService();
313  DCHECK(service);
314  const extensions::Extension* extension = service->GetInstalledExtension(
315      extension_misc::kSettingsAppId);
316  DCHECK(extension);
317  controller_->ActivateApp(profile_,
318                           extension,
319                           AppListControllerDelegate::LAUNCH_FROM_UNKNOWN,
320                           0);
321}
322
323void AppListViewDelegate::OpenHelp() {
324  chrome::HostDesktopType desktop = chrome::GetHostDesktopTypeForNativeWindow(
325      controller_->GetAppListWindow());
326  chrome::ScopedTabbedBrowserDisplayer displayer(profile_, desktop);
327  content::OpenURLParams params(GURL(chrome::kAppLauncherHelpURL),
328                                content::Referrer(),
329                                NEW_FOREGROUND_TAB,
330                                content::PAGE_TRANSITION_LINK,
331                                false);
332  displayer.browser()->OpenURL(params);
333}
334
335void AppListViewDelegate::OpenFeedback() {
336  chrome::HostDesktopType desktop = chrome::GetHostDesktopTypeForNativeWindow(
337      controller_->GetAppListWindow());
338  Browser* browser = chrome::FindTabbedBrowser(profile_, false, desktop);
339  chrome::ShowFeedbackPage(browser, std::string(),
340                           chrome::kAppLauncherCategoryTag);
341}
342
343void AppListViewDelegate::ToggleSpeechRecognition() {
344  app_list::StartPageService* service =
345      app_list::StartPageService::Get(profile_);
346  if (service)
347    service->ToggleSpeechRecognition();
348}
349
350void AppListViewDelegate::ShowForProfileByPath(
351    const base::FilePath& profile_path) {
352  controller_->ShowForProfileByPath(profile_path);
353}
354
355void AppListViewDelegate::OnSpeechResult(const base::string16& result,
356                                         bool is_final) {
357  speech_ui_->SetSpeechResult(result, is_final);
358  if (is_final) {
359    auto_launch_timeout_ = base::TimeDelta::FromMilliseconds(
360        kAutoLaunchDefaultTimeoutMilliSec);
361    model_->search_box()->SetText(result);
362  }
363}
364
365void AppListViewDelegate::OnSpeechSoundLevelChanged(int16 level) {
366  speech_ui_->UpdateSoundLevel(level);
367}
368
369void AppListViewDelegate::OnSpeechRecognitionStateChanged(
370    app_list::SpeechRecognitionState new_state) {
371  speech_ui_->SetSpeechRecognitionState(new_state);
372}
373
374void AppListViewDelegate::OnProfileAdded(const base::FilePath& profile_path) {
375  OnProfileChanged();
376}
377
378void AppListViewDelegate::OnProfileWasRemoved(
379    const base::FilePath& profile_path, const base::string16& profile_name) {
380  OnProfileChanged();
381}
382
383void AppListViewDelegate::OnProfileNameChanged(
384    const base::FilePath& profile_path,
385    const base::string16& old_profile_name) {
386  OnProfileChanged();
387}
388
389#if defined(TOOLKIT_VIEWS)
390views::View* AppListViewDelegate::CreateStartPageWebView(
391    const gfx::Size& size) {
392  app_list::StartPageService* service =
393      app_list::StartPageService::Get(profile_);
394  if (!service)
395    return NULL;
396
397  content::WebContents* web_contents = service->GetStartPageContents();
398  if (!web_contents)
399    return NULL;
400
401  views::WebView* web_view = new views::WebView(
402      web_contents->GetBrowserContext());
403  web_view->SetPreferredSize(size);
404  web_view->SetWebContents(web_contents);
405  return web_view;
406}
407#endif
408
409bool AppListViewDelegate::IsSpeechRecognitionEnabled() {
410  app_list::StartPageService* service =
411      app_list::StartPageService::Get(profile_);
412  return service && service->GetSpeechRecognitionContents();
413}
414
415const app_list::AppListViewDelegate::Users&
416AppListViewDelegate::GetUsers() const {
417  return users_;
418}
419
420bool AppListViewDelegate::ShouldCenterWindow() const {
421  if (app_list::switches::IsCenteredAppListEnabled())
422    return true;
423
424  // keyboard depends upon Aura.
425#if defined(USE_AURA)
426  // If the virtual keyboard is enabled, use the new app list position. The old
427  // position is too tall, and doesn't fit in the left-over screen space.
428  if (keyboard::IsKeyboardEnabled())
429    return true;
430#endif
431
432#if defined(USE_ASH)
433  // If it is at all possible to enter maximize mode in this configuration
434  // (which has a virtual keyboard), we should use the experimental position.
435  // This avoids having the app list change shape and position as the user
436  // enters and exits maximize mode.
437  if (ash::Shell::HasInstance() &&
438      ash::Shell::GetInstance()
439          ->maximize_mode_controller()
440          ->CanEnterMaximizeMode()) {
441    return true;
442  }
443#endif
444
445  return false;
446}
447
448void AppListViewDelegate::AddObserver(
449    app_list::AppListViewDelegateObserver* observer) {
450  observers_.AddObserver(observer);
451}
452
453void AppListViewDelegate::RemoveObserver(
454    app_list::AppListViewDelegateObserver* observer) {
455  observers_.RemoveObserver(observer);
456}
457