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