app_list_view_delegate.cc revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
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 "apps/custom_launcher_page_contents.h"
10#include "base/callback.h"
11#include "base/command_line.h"
12#include "base/files/file_path.h"
13#include "base/metrics/user_metrics.h"
14#include "base/stl_util.h"
15#include "chrome/browser/browser_process.h"
16#include "chrome/browser/chrome_notification_types.h"
17#include "chrome/browser/profiles/profile_info_cache.h"
18#include "chrome/browser/profiles/profile_manager.h"
19#include "chrome/browser/search/hotword_service.h"
20#include "chrome/browser/search/hotword_service_factory.h"
21#include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
22#include "chrome/browser/ui/app_list/app_list_service.h"
23#include "chrome/browser/ui/app_list/app_list_syncable_service.h"
24#include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h"
25#include "chrome/browser/ui/app_list/search/search_controller.h"
26#include "chrome/browser/ui/app_list/start_page_service.h"
27#include "chrome/browser/ui/apps/chrome_app_delegate.h"
28#include "chrome/browser/ui/browser_finder.h"
29#include "chrome/browser/ui/chrome_pages.h"
30#include "chrome/browser/ui/host_desktop.h"
31#include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
32#include "chrome/browser/web_applications/web_app.h"
33#include "chrome/common/chrome_switches.h"
34#include "chrome/common/extensions/extension_constants.h"
35#include "chrome/common/url_constants.h"
36#include "components/signin/core/browser/signin_manager.h"
37#include "content/public/browser/browser_thread.h"
38#include "content/public/browser/page_navigator.h"
39#include "content/public/browser/user_metrics.h"
40#include "content/public/browser/web_contents.h"
41#include "extensions/browser/extension_registry.h"
42#include "extensions/common/constants.h"
43#include "extensions/common/extension_set.h"
44#include "extensions/common/manifest.h"
45#include "extensions/common/manifest_constants.h"
46#include "grit/theme_resources.h"
47#include "ui/app_list/app_list_switches.h"
48#include "ui/app_list/app_list_view_delegate_observer.h"
49#include "ui/app_list/search_box_model.h"
50#include "ui/app_list/speech_ui_model.h"
51#include "ui/base/resource/resource_bundle.h"
52#include "ui/views/controls/webview/webview.h"
53
54#if defined(TOOLKIT_VIEWS)
55#include "ui/views/controls/webview/webview.h"
56#endif
57
58#if defined(USE_AURA)
59#include "ui/keyboard/keyboard_util.h"
60#endif
61
62#if defined(USE_ASH)
63#include "ash/shell.h"
64#include "ash/wm/maximize_mode/maximize_mode_controller.h"
65#include "chrome/browser/ui/ash/app_list/app_sync_ui_state_watcher.h"
66#endif
67
68#if defined(OS_WIN)
69#include "chrome/browser/web_applications/web_app_win.h"
70#endif
71
72
73namespace chrome {
74const char kAppLauncherCategoryTag[] = "AppLauncher";
75}  // namespace chrome
76
77namespace {
78
79const int kAutoLaunchDefaultTimeoutMilliSec = 50;
80
81#if defined(OS_WIN)
82void CreateShortcutInWebAppDir(
83    const base::FilePath& app_data_dir,
84    base::Callback<void(const base::FilePath&)> callback,
85    const web_app::ShortcutInfo& info) {
86  content::BrowserThread::PostTaskAndReplyWithResult(
87      content::BrowserThread::FILE,
88      FROM_HERE,
89      base::Bind(web_app::CreateShortcutInWebAppDir, app_data_dir, info),
90      callback);
91}
92#endif
93
94void PopulateUsers(const ProfileInfoCache& profile_info,
95                   const base::FilePath& active_profile_path,
96                   app_list::AppListViewDelegate::Users* users) {
97  users->clear();
98  const size_t count = profile_info.GetNumberOfProfiles();
99  for (size_t i = 0; i < count; ++i) {
100    app_list::AppListViewDelegate::User user;
101    user.name = profile_info.GetNameOfProfileAtIndex(i);
102    user.email = profile_info.GetUserNameOfProfileAtIndex(i);
103    user.profile_path = profile_info.GetPathOfProfileAtIndex(i);
104    user.active = active_profile_path == user.profile_path;
105    users->push_back(user);
106  }
107}
108
109// Gets a list of URLs of the custom launcher pages to show in the launcher.
110// Returns a URL for each installed launcher page. If --custom-launcher-page is
111// specified and valid, also includes that URL.
112void GetCustomLauncherPageUrls(content::BrowserContext* browser_context,
113                               std::vector<GURL>* urls) {
114  // First, check the command line.
115  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
116  if (app_list::switches::IsExperimentalAppListEnabled() &&
117      command_line->HasSwitch(switches::kCustomLauncherPage)) {
118    GURL custom_launcher_page_url(
119        command_line->GetSwitchValueASCII(switches::kCustomLauncherPage));
120
121    if (custom_launcher_page_url.SchemeIs(extensions::kExtensionScheme)) {
122      urls->push_back(custom_launcher_page_url);
123    } else {
124      // TODO(mgiuca): Add a proper manifest parser to catch this error properly
125      // and display it on the extensions page.
126      LOG(ERROR) << "Invalid custom launcher page URL: "
127                 << custom_launcher_page_url.possibly_invalid_spec();
128    }
129  }
130
131  // Search the list of installed extensions for ones with 'launcher_page'.
132  extensions::ExtensionRegistry* extension_registry =
133      extensions::ExtensionRegistry::Get(browser_context);
134  const extensions::ExtensionSet& enabled_extensions =
135      extension_registry->enabled_extensions();
136  for (extensions::ExtensionSet::const_iterator it = enabled_extensions.begin();
137       it != enabled_extensions.end();
138       ++it) {
139    const extensions::Extension* extension = it->get();
140    const extensions::Manifest* manifest = extension->manifest();
141    if (!manifest->HasKey(extensions::manifest_keys::kLauncherPage))
142      continue;
143    std::string launcher_page_page;
144    if (!manifest->GetString(extensions::manifest_keys::kLauncherPagePage,
145                             &launcher_page_page)) {
146      LOG(ERROR) << "Extension " << extension->id() << ": "
147                 << extensions::manifest_keys::kLauncherPage
148                 << " has no 'page' attribute; will be ignored.";
149      continue;
150    }
151    urls->push_back(extension->GetResourceURL(launcher_page_page));
152  }
153}
154
155}  // namespace
156
157AppListViewDelegate::AppListViewDelegate(Profile* profile,
158                                         AppListControllerDelegate* controller)
159    : controller_(controller),
160      profile_(profile),
161      model_(NULL),
162      scoped_observer_(this) {
163  CHECK(controller_);
164  // The SigninManagerFactor and the SigninManagers are observed to keep the
165  // profile switcher menu up to date, with the correct list of profiles and the
166  // correct email address (or none for signed out users) for each.
167  SigninManagerFactory::GetInstance()->AddObserver(this);
168
169  // Start observing all already-created SigninManagers.
170  ProfileManager* profile_manager = g_browser_process->profile_manager();
171  std::vector<Profile*> profiles = profile_manager->GetLoadedProfiles();
172  for (std::vector<Profile*>::iterator i = profiles.begin();
173       i != profiles.end();
174       ++i) {
175    SigninManagerBase* manager =
176        SigninManagerFactory::GetForProfileIfExists(*i);
177    if (manager) {
178      DCHECK(!scoped_observer_.IsObserving(manager));
179      scoped_observer_.Add(manager);
180    }
181  }
182
183  profile_manager->GetProfileInfoCache().AddObserver(this);
184
185  app_list::StartPageService* service =
186      app_list::StartPageService::Get(profile_);
187  speech_ui_.reset(new app_list::SpeechUIModel(
188      service ? service->state() : app_list::SPEECH_RECOGNITION_OFF));
189
190#if defined(GOOGLE_CHROME_BUILD)
191  speech_ui_->set_logo(
192      *ui::ResourceBundle::GetSharedInstance().
193      GetImageSkiaNamed(IDR_APP_LIST_GOOGLE_LOGO_VOICE_SEARCH));
194#endif
195
196  OnProfileChanged();  // sets model_
197  if (service)
198    service->AddObserver(this);
199
200  // Set up the custom launcher pages.
201  std::vector<GURL> custom_launcher_page_urls;
202  GetCustomLauncherPageUrls(profile, &custom_launcher_page_urls);
203  for (std::vector<GURL>::const_iterator it = custom_launcher_page_urls.begin();
204       it != custom_launcher_page_urls.end();
205       ++it) {
206    std::string extension_id = it->host();
207    apps::CustomLauncherPageContents* page_contents =
208        new apps::CustomLauncherPageContents(
209            scoped_ptr<apps::AppDelegate>(new ChromeAppDelegate), extension_id);
210    page_contents->Initialize(profile, *it);
211    custom_page_contents_.push_back(page_contents);
212  }
213}
214
215AppListViewDelegate::~AppListViewDelegate() {
216  app_list::StartPageService* service =
217      app_list::StartPageService::Get(profile_);
218  if (service)
219    service->RemoveObserver(this);
220  g_browser_process->
221      profile_manager()->GetProfileInfoCache().RemoveObserver(this);
222
223  SigninManagerFactory* factory = SigninManagerFactory::GetInstance();
224  if (factory)
225    factory->RemoveObserver(this);
226
227  // Ensure search controller is released prior to speech_ui_.
228  search_controller_.reset();
229}
230
231void AppListViewDelegate::OnHotwordStateChanged(bool started) {
232  if (started) {
233    if (speech_ui_->state() == app_list::SPEECH_RECOGNITION_READY) {
234      OnSpeechRecognitionStateChanged(
235          app_list::SPEECH_RECOGNITION_HOTWORD_LISTENING);
236    }
237  } else {
238    if (speech_ui_->state() == app_list::SPEECH_RECOGNITION_HOTWORD_LISTENING)
239      OnSpeechRecognitionStateChanged(app_list::SPEECH_RECOGNITION_READY);
240  }
241}
242
243void AppListViewDelegate::OnHotwordRecognized() {
244  DCHECK_EQ(app_list::SPEECH_RECOGNITION_HOTWORD_LISTENING,
245            speech_ui_->state());
246  ToggleSpeechRecognition();
247}
248
249void AppListViewDelegate::SigninManagerCreated(SigninManagerBase* manager) {
250  scoped_observer_.Add(manager);
251}
252
253void AppListViewDelegate::SigninManagerShutdown(SigninManagerBase* manager) {
254  if (scoped_observer_.IsObserving(manager))
255    scoped_observer_.Remove(manager);
256}
257
258void AppListViewDelegate::GoogleSigninFailed(
259    const GoogleServiceAuthError& error) {
260  OnProfileChanged();
261}
262
263void AppListViewDelegate::GoogleSigninSucceeded(const std::string& username,
264                                                const std::string& password) {
265  OnProfileChanged();
266}
267
268void AppListViewDelegate::GoogleSignedOut(const std::string& username) {
269  OnProfileChanged();
270}
271
272void AppListViewDelegate::OnProfileChanged() {
273  model_ = app_list::AppListSyncableServiceFactory::GetForProfile(
274      profile_)->model();
275
276  search_controller_.reset(new app_list::SearchController(
277      profile_, model_->search_box(), model_->results(),
278      speech_ui_.get(), controller_));
279
280#if defined(USE_ASH)
281  app_sync_ui_state_watcher_.reset(new AppSyncUIStateWatcher(profile_, model_));
282#endif
283
284  // Don't populate the app list users if we are on the ash desktop.
285  chrome::HostDesktopType desktop = chrome::GetHostDesktopTypeForNativeWindow(
286      controller_->GetAppListWindow());
287  if (desktop == chrome::HOST_DESKTOP_TYPE_ASH)
288    return;
289
290  // Populate the app list users.
291  PopulateUsers(g_browser_process->profile_manager()->GetProfileInfoCache(),
292                profile_->GetPath(), &users_);
293
294  FOR_EACH_OBSERVER(app_list::AppListViewDelegateObserver,
295                    observers_,
296                    OnProfilesChanged());
297}
298
299bool AppListViewDelegate::ForceNativeDesktop() const {
300  return controller_->ForceNativeDesktop();
301}
302
303void AppListViewDelegate::SetProfileByPath(const base::FilePath& profile_path) {
304  DCHECK(model_);
305
306  // The profile must be loaded before this is called.
307  profile_ =
308      g_browser_process->profile_manager()->GetProfileByPath(profile_path);
309  DCHECK(profile_);
310
311  OnProfileChanged();
312
313  // Clear search query.
314  model_->search_box()->SetText(base::string16());
315}
316
317app_list::AppListModel* AppListViewDelegate::GetModel() {
318  return model_;
319}
320
321app_list::SpeechUIModel* AppListViewDelegate::GetSpeechUI() {
322  return speech_ui_.get();
323}
324
325void AppListViewDelegate::GetShortcutPathForApp(
326    const std::string& app_id,
327    const base::Callback<void(const base::FilePath&)>& callback) {
328#if defined(OS_WIN)
329  const extensions::Extension* extension =
330      extensions::ExtensionRegistry::Get(profile_)->GetExtensionById(
331          app_id, extensions::ExtensionRegistry::EVERYTHING);
332  if (!extension) {
333    callback.Run(base::FilePath());
334    return;
335  }
336
337  base::FilePath app_data_dir(
338      web_app::GetWebAppDataDirectory(profile_->GetPath(),
339                                      extension->id(),
340                                      GURL()));
341
342  web_app::GetShortcutInfoForApp(
343      extension,
344      profile_,
345      base::Bind(CreateShortcutInWebAppDir, app_data_dir, callback));
346#else
347  callback.Run(base::FilePath());
348#endif
349}
350
351void AppListViewDelegate::StartSearch() {
352  if (search_controller_)
353    search_controller_->Start();
354}
355
356void AppListViewDelegate::StopSearch() {
357  if (search_controller_)
358    search_controller_->Stop();
359}
360
361void AppListViewDelegate::OpenSearchResult(
362    app_list::SearchResult* result,
363    bool auto_launch,
364    int event_flags) {
365  if (auto_launch)
366    base::RecordAction(base::UserMetricsAction("AppList_AutoLaunched"));
367  search_controller_->OpenResult(result, event_flags);
368}
369
370void AppListViewDelegate::InvokeSearchResultAction(
371    app_list::SearchResult* result,
372    int action_index,
373    int event_flags) {
374  search_controller_->InvokeResultAction(result, action_index, event_flags);
375}
376
377base::TimeDelta AppListViewDelegate::GetAutoLaunchTimeout() {
378  return auto_launch_timeout_;
379}
380
381void AppListViewDelegate::AutoLaunchCanceled() {
382  base::RecordAction(base::UserMetricsAction("AppList_AutoLaunchCanceled"));
383  auto_launch_timeout_ = base::TimeDelta();
384}
385
386void AppListViewDelegate::ViewInitialized() {
387  app_list::StartPageService* service =
388      app_list::StartPageService::Get(profile_);
389  if (service) {
390    service->AppListShown();
391    if (service->HotwordEnabled()) {
392      HotwordService* hotword_service =
393          HotwordServiceFactory::GetForProfile(profile_);
394      if (hotword_service)
395        hotword_service->RequestHotwordSession(this);
396    }
397  }
398}
399
400void AppListViewDelegate::Dismiss()  {
401  controller_->DismissView();
402}
403
404void AppListViewDelegate::ViewClosing() {
405  controller_->ViewClosing();
406
407  app_list::StartPageService* service =
408      app_list::StartPageService::Get(profile_);
409  if (service) {
410    service->AppListHidden();
411    if (service->HotwordEnabled()) {
412      HotwordService* hotword_service =
413          HotwordServiceFactory::GetForProfile(profile_);
414      if (hotword_service)
415        hotword_service->StopHotwordSession(this);
416    }
417  }
418}
419
420gfx::ImageSkia AppListViewDelegate::GetWindowIcon() {
421  return controller_->GetWindowIcon();
422}
423
424void AppListViewDelegate::OpenSettings() {
425  const extensions::Extension* extension =
426      extensions::ExtensionRegistry::Get(profile_)->GetExtensionById(
427          extension_misc::kSettingsAppId,
428          extensions::ExtensionRegistry::EVERYTHING);
429  DCHECK(extension);
430  controller_->ActivateApp(profile_,
431                           extension,
432                           AppListControllerDelegate::LAUNCH_FROM_UNKNOWN,
433                           0);
434}
435
436void AppListViewDelegate::OpenHelp() {
437  chrome::HostDesktopType desktop = chrome::GetHostDesktopTypeForNativeWindow(
438      controller_->GetAppListWindow());
439  chrome::ScopedTabbedBrowserDisplayer displayer(profile_, desktop);
440  content::OpenURLParams params(GURL(chrome::kAppLauncherHelpURL),
441                                content::Referrer(),
442                                NEW_FOREGROUND_TAB,
443                                content::PAGE_TRANSITION_LINK,
444                                false);
445  displayer.browser()->OpenURL(params);
446}
447
448void AppListViewDelegate::OpenFeedback() {
449  chrome::HostDesktopType desktop = chrome::GetHostDesktopTypeForNativeWindow(
450      controller_->GetAppListWindow());
451  Browser* browser = chrome::FindTabbedBrowser(profile_, false, desktop);
452  chrome::ShowFeedbackPage(browser, std::string(),
453                           chrome::kAppLauncherCategoryTag);
454}
455
456void AppListViewDelegate::ToggleSpeechRecognition() {
457  app_list::StartPageService* service =
458      app_list::StartPageService::Get(profile_);
459  if (service)
460    service->ToggleSpeechRecognition();
461}
462
463void AppListViewDelegate::ShowForProfileByPath(
464    const base::FilePath& profile_path) {
465  controller_->ShowForProfileByPath(profile_path);
466}
467
468void AppListViewDelegate::OnSpeechResult(const base::string16& result,
469                                         bool is_final) {
470  speech_ui_->SetSpeechResult(result, is_final);
471  if (is_final) {
472    auto_launch_timeout_ = base::TimeDelta::FromMilliseconds(
473        kAutoLaunchDefaultTimeoutMilliSec);
474    model_->search_box()->SetText(result);
475  }
476}
477
478void AppListViewDelegate::OnSpeechSoundLevelChanged(int16 level) {
479  speech_ui_->UpdateSoundLevel(level);
480}
481
482void AppListViewDelegate::OnSpeechRecognitionStateChanged(
483    app_list::SpeechRecognitionState new_state) {
484  speech_ui_->SetSpeechRecognitionState(new_state);
485}
486
487void AppListViewDelegate::OnProfileAdded(const base::FilePath& profile_path) {
488  OnProfileChanged();
489}
490
491void AppListViewDelegate::OnProfileWasRemoved(
492    const base::FilePath& profile_path, const base::string16& profile_name) {
493  OnProfileChanged();
494}
495
496void AppListViewDelegate::OnProfileNameChanged(
497    const base::FilePath& profile_path,
498    const base::string16& old_profile_name) {
499  OnProfileChanged();
500}
501
502#if defined(TOOLKIT_VIEWS)
503views::View* AppListViewDelegate::CreateStartPageWebView(
504    const gfx::Size& size) {
505  app_list::StartPageService* service =
506      app_list::StartPageService::Get(profile_);
507  if (!service)
508    return NULL;
509
510  content::WebContents* web_contents = service->GetStartPageContents();
511  if (!web_contents)
512    return NULL;
513
514  DCHECK_EQ(profile_, web_contents->GetBrowserContext());
515  views::WebView* web_view = new views::WebView(
516      web_contents->GetBrowserContext());
517  web_view->SetPreferredSize(size);
518  web_view->SetWebContents(web_contents);
519  return web_view;
520}
521
522std::vector<views::View*> AppListViewDelegate::CreateCustomPageWebViews(
523    const gfx::Size& size) {
524  std::vector<views::View*> web_views;
525
526  for (ScopedVector<apps::CustomLauncherPageContents>::const_iterator it =
527           custom_page_contents_.begin();
528       it != custom_page_contents_.end();
529       ++it) {
530    content::WebContents* web_contents = (*it)->web_contents();
531    // TODO(mgiuca): DCHECK_EQ(profile_, web_contents->GetBrowserContext())
532    // after http://crbug.com/392763 resolved.
533    views::WebView* web_view =
534        new views::WebView(web_contents->GetBrowserContext());
535    web_view->SetPreferredSize(size);
536    web_view->SetWebContents(web_contents);
537    web_views.push_back(web_view);
538  }
539
540  return web_views;
541}
542#endif
543
544bool AppListViewDelegate::IsSpeechRecognitionEnabled() {
545  app_list::StartPageService* service =
546      app_list::StartPageService::Get(profile_);
547  return service && service->GetSpeechRecognitionContents();
548}
549
550const app_list::AppListViewDelegate::Users&
551AppListViewDelegate::GetUsers() const {
552  return users_;
553}
554
555bool AppListViewDelegate::ShouldCenterWindow() const {
556  if (app_list::switches::IsCenteredAppListEnabled())
557    return true;
558
559  // keyboard depends upon Aura.
560#if defined(USE_AURA)
561  // If the virtual keyboard is enabled, use the new app list position. The old
562  // position is too tall, and doesn't fit in the left-over screen space.
563  if (keyboard::IsKeyboardEnabled())
564    return true;
565#endif
566
567#if defined(USE_ASH)
568  // If it is at all possible to enter maximize mode in this configuration
569  // (which has a virtual keyboard), we should use the experimental position.
570  // This avoids having the app list change shape and position as the user
571  // enters and exits maximize mode.
572  if (ash::Shell::HasInstance() &&
573      ash::Shell::GetInstance()
574          ->maximize_mode_controller()
575          ->CanEnterMaximizeMode()) {
576    return true;
577  }
578#endif
579
580  return false;
581}
582
583void AppListViewDelegate::AddObserver(
584    app_list::AppListViewDelegateObserver* observer) {
585  observers_.AddObserver(observer);
586}
587
588void AppListViewDelegate::RemoveObserver(
589    app_list::AppListViewDelegateObserver* observer) {
590  observers_.RemoveObserver(observer);
591}
592