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