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