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