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