app_list_service_win.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
1// Copyright 2013 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/views/app_list/win/app_list_service_win.h" 6 7#include <dwmapi.h> 8#include <sstream> 9 10#include "base/command_line.h" 11#include "base/file_util.h" 12#include "base/lazy_instance.h" 13#include "base/memory/weak_ptr.h" 14#include "base/message_loop/message_loop.h" 15#include "base/metrics/histogram.h" 16#include "base/path_service.h" 17#include "base/prefs/pref_service.h" 18#include "base/strings/utf_string_conversions.h" 19#include "base/threading/sequenced_worker_pool.h" 20#include "base/time/time.h" 21#include "base/timer/timer.h" 22#include "base/win/shortcut.h" 23#include "base/win/windows_version.h" 24#include "chrome/app/chrome_dll_resource.h" 25#include "chrome/browser/browser_process.h" 26#include "chrome/browser/browser_shutdown.h" 27#include "chrome/browser/platform_util.h" 28#include "chrome/browser/profiles/profile.h" 29#include "chrome/browser/profiles/profile_manager.h" 30#include "chrome/browser/shell_integration.h" 31#include "chrome/browser/ui/app_list/app_list.h" 32#include "chrome/browser/ui/app_list/app_list_controller_delegate.h" 33#include "chrome/browser/ui/app_list/app_list_factory.h" 34#include "chrome/browser/ui/app_list/app_list_service_impl.h" 35#include "chrome/browser/ui/app_list/app_list_shower.h" 36#include "chrome/browser/ui/app_list/app_list_view_delegate.h" 37#include "chrome/browser/ui/app_list/keep_alive_service_impl.h" 38#include "chrome/browser/ui/apps/app_metro_infobar_delegate_win.h" 39#include "chrome/browser/ui/views/app_list/win/activation_tracker_win.h" 40#include "chrome/browser/ui/views/app_list/win/app_list_controller_delegate_win.h" 41#include "chrome/browser/ui/views/app_list/win/app_list_win.h" 42#include "chrome/browser/web_applications/web_app.h" 43#include "chrome/common/chrome_constants.h" 44#include "chrome/common/chrome_switches.h" 45#include "chrome/common/chrome_version_info.h" 46#include "chrome/common/pref_names.h" 47#include "chrome/installer/launcher_support/chrome_launcher_support.h" 48#include "chrome/installer/util/browser_distribution.h" 49#include "chrome/installer/util/google_update_settings.h" 50#include "chrome/installer/util/install_util.h" 51#include "chrome/installer/util/util_constants.h" 52#include "content/public/browser/browser_thread.h" 53#include "grit/chromium_strings.h" 54#include "grit/generated_resources.h" 55#include "grit/google_chrome_strings.h" 56#include "grit/theme_resources.h" 57#include "ui/app_list/app_list_model.h" 58#include "ui/app_list/pagination_model.h" 59#include "ui/app_list/views/app_list_view.h" 60#include "ui/base/l10n/l10n_util.h" 61#include "ui/base/resource/resource_bundle.h" 62#include "ui/base/win/shell.h" 63#include "ui/gfx/display.h" 64#include "ui/gfx/image/image_skia.h" 65#include "ui/gfx/screen.h" 66#include "ui/views/bubble/bubble_border.h" 67#include "ui/views/widget/widget.h" 68#include "win8/util/win8_util.h" 69 70#if defined(USE_AURA) 71#include "ui/aura/window.h" 72#include "ui/aura/window_event_dispatcher.h" 73#endif 74 75#if defined(USE_ASH) 76#include "chrome/browser/ui/app_list/app_list_service_ash.h" 77#include "chrome/browser/ui/host_desktop.h" 78#endif 79 80#if defined(GOOGLE_CHROME_BUILD) 81#include "chrome/installer/util/install_util.h" 82#endif 83 84// static 85AppListService* AppListService::Get(chrome::HostDesktopType desktop_type) { 86#if defined(USE_ASH) 87 if (desktop_type == chrome::HOST_DESKTOP_TYPE_ASH) 88 return chrome::GetAppListServiceAsh(); 89#endif 90 91 return chrome::GetAppListServiceWin(); 92} 93 94// static 95void AppListService::InitAll(Profile* initial_profile) { 96#if defined(USE_ASH) 97 chrome::GetAppListServiceAsh()->Init(initial_profile); 98#endif 99 chrome::GetAppListServiceWin()->Init(initial_profile); 100} 101 102namespace { 103 104// Migrate chrome::kAppLauncherIsEnabled pref to 105// chrome::kAppLauncherHasBeenEnabled pref. 106void MigrateAppLauncherEnabledPref() { 107 PrefService* prefs = g_browser_process->local_state(); 108 if (prefs->HasPrefPath(prefs::kAppLauncherIsEnabled)) { 109 prefs->SetBoolean(prefs::kAppLauncherHasBeenEnabled, 110 prefs->GetBoolean(prefs::kAppLauncherIsEnabled)); 111 prefs->ClearPref(prefs::kAppLauncherIsEnabled); 112 } 113} 114 115int GetAppListIconIndex() { 116 BrowserDistribution* dist = BrowserDistribution::GetDistribution(); 117 return dist->GetIconIndex(BrowserDistribution::SHORTCUT_APP_LAUNCHER); 118} 119 120base::string16 GetAppListIconPath() { 121 base::FilePath icon_path; 122 if (!PathService::Get(base::FILE_EXE, &icon_path)) { 123 NOTREACHED(); 124 return base::string16(); 125 } 126 127 std::stringstream ss; 128 ss << "," << GetAppListIconIndex(); 129 base::string16 result = icon_path.value(); 130 result.append(base::UTF8ToUTF16(ss.str())); 131 return result; 132} 133 134base::string16 GetAppListShortcutName() { 135 BrowserDistribution* dist = BrowserDistribution::GetDistribution(); 136 return dist->GetShortcutName(BrowserDistribution::SHORTCUT_APP_LAUNCHER); 137} 138 139CommandLine GetAppListCommandLine() { 140 const char* const kSwitchesToCopy[] = { switches::kUserDataDir }; 141 CommandLine* current = CommandLine::ForCurrentProcess(); 142 base::FilePath chrome_exe; 143 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) { 144 NOTREACHED(); 145 return CommandLine(CommandLine::NO_PROGRAM); 146 } 147 CommandLine command_line(chrome_exe); 148 command_line.CopySwitchesFrom(*current, kSwitchesToCopy, 149 arraysize(kSwitchesToCopy)); 150 command_line.AppendSwitch(switches::kShowAppList); 151 return command_line; 152} 153 154base::string16 GetAppModelId() { 155 // The AppModelId should be the same for all profiles in a user data directory 156 // but different for different user data directories, so base it on the 157 // initial profile in the current user data directory. 158 base::FilePath initial_profile_path; 159 CommandLine* command_line = CommandLine::ForCurrentProcess(); 160 if (command_line->HasSwitch(switches::kUserDataDir)) { 161 initial_profile_path = 162 command_line->GetSwitchValuePath(switches::kUserDataDir).AppendASCII( 163 chrome::kInitialProfile); 164 } 165 return ShellIntegration::GetAppListAppModelIdForProfile(initial_profile_path); 166} 167 168void SetDidRunForNDayActiveStats() { 169 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 170 base::FilePath exe_path; 171 if (!PathService::Get(base::DIR_EXE, &exe_path)) { 172 NOTREACHED(); 173 return; 174 } 175 bool system_install = 176 !InstallUtil::IsPerUserInstall(exe_path.value().c_str()); 177 // Using Chrome Binary dist: Chrome dist may not exist for the legacy 178 // App Launcher, and App Launcher dist may be "shadow", which does not 179 // contain the information needed to determine multi-install. 180 // Edge case involving Canary: crbug/239163. 181 BrowserDistribution* chrome_binaries_dist = 182 BrowserDistribution::GetSpecificDistribution( 183 BrowserDistribution::CHROME_BINARIES); 184 if (chrome_binaries_dist && 185 InstallUtil::IsMultiInstall(chrome_binaries_dist, system_install)) { 186 BrowserDistribution* app_launcher_dist = 187 BrowserDistribution::GetSpecificDistribution( 188 BrowserDistribution::CHROME_APP_HOST); 189 GoogleUpdateSettings::UpdateDidRunStateForDistribution( 190 app_launcher_dist, 191 true /* did_run */, 192 system_install); 193 } 194} 195 196// The start menu shortcut is created on first run by users that are 197// upgrading. The desktop and taskbar shortcuts are created the first time the 198// user enables the app list. The taskbar shortcut is created in 199// |user_data_dir| and will use a Windows Application Model Id of 200// |app_model_id|. This runs on the FILE thread and not in the blocking IO 201// thread pool as there are other tasks running (also on the FILE thread) 202// which fiddle with shortcut icons 203// (ShellIntegration::MigrateWin7ShortcutsOnPath). Having different threads 204// fiddle with the same shortcuts could cause race issues. 205void CreateAppListShortcuts( 206 const base::FilePath& user_data_dir, 207 const base::string16& app_model_id, 208 const ShellIntegration::ShortcutLocations& creation_locations) { 209 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE)); 210 211 // Shortcut paths under which to create shortcuts. 212 std::vector<base::FilePath> shortcut_paths = 213 web_app::internals::GetShortcutPaths(creation_locations); 214 215 bool pin_to_taskbar = creation_locations.in_quick_launch_bar && 216 (base::win::GetVersion() >= base::win::VERSION_WIN7); 217 218 // Create a shortcut in the |user_data_dir| for taskbar pinning. 219 if (pin_to_taskbar) 220 shortcut_paths.push_back(user_data_dir); 221 bool success = true; 222 223 base::FilePath chrome_exe; 224 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) { 225 NOTREACHED(); 226 return; 227 } 228 229 base::string16 app_list_shortcut_name = GetAppListShortcutName(); 230 231 base::string16 wide_switches(GetAppListCommandLine().GetArgumentsString()); 232 233 base::win::ShortcutProperties shortcut_properties; 234 shortcut_properties.set_target(chrome_exe); 235 shortcut_properties.set_working_dir(chrome_exe.DirName()); 236 shortcut_properties.set_arguments(wide_switches); 237 shortcut_properties.set_description(app_list_shortcut_name); 238 shortcut_properties.set_icon(chrome_exe, GetAppListIconIndex()); 239 shortcut_properties.set_app_id(app_model_id); 240 241 for (size_t i = 0; i < shortcut_paths.size(); ++i) { 242 base::FilePath shortcut_file = 243 shortcut_paths[i].Append(app_list_shortcut_name). 244 AddExtension(installer::kLnkExt); 245 if (!base::PathExists(shortcut_file.DirName()) && 246 !base::CreateDirectory(shortcut_file.DirName())) { 247 NOTREACHED(); 248 return; 249 } 250 success = success && base::win::CreateOrUpdateShortcutLink( 251 shortcut_file, shortcut_properties, 252 base::win::SHORTCUT_CREATE_ALWAYS); 253 } 254 255 if (success && pin_to_taskbar) { 256 base::FilePath shortcut_to_pin = 257 user_data_dir.Append(app_list_shortcut_name). 258 AddExtension(installer::kLnkExt); 259 success = base::win::TaskbarPinShortcutLink( 260 shortcut_to_pin.value().c_str()) && success; 261 } 262} 263 264// Customizes the app list |hwnd| for Windows (eg: disable aero peek, set up 265// restart params). 266void SetWindowAttributes(HWND hwnd) { 267 if (base::win::GetVersion() > base::win::VERSION_VISTA) { 268 // Disable aero peek. Without this, hovering over the taskbar popup puts 269 // Windows into a mode for switching between windows in the same 270 // application. The app list has just one window, so it is just distracting. 271 BOOL disable_value = TRUE; 272 ::DwmSetWindowAttribute(hwnd, 273 DWMWA_DISALLOW_PEEK, 274 &disable_value, 275 sizeof(disable_value)); 276 } 277 278 ui::win::SetAppIdForWindow(GetAppModelId(), hwnd); 279 CommandLine relaunch = GetAppListCommandLine(); 280 base::string16 app_name(GetAppListShortcutName()); 281 ui::win::SetRelaunchDetailsForWindow( 282 relaunch.GetCommandLineString(), app_name, hwnd); 283 ::SetWindowText(hwnd, app_name.c_str()); 284 base::string16 icon_path = GetAppListIconPath(); 285 ui::win::SetAppIconForWindow(icon_path, hwnd); 286} 287 288class AppListFactoryWin : public AppListFactory { 289 public: 290 explicit AppListFactoryWin(AppListServiceWin* service) 291 : service_(service) { 292 } 293 294 virtual ~AppListFactoryWin() { 295 } 296 297 virtual AppList* CreateAppList( 298 Profile* profile, 299 AppListService* service, 300 const base::Closure& on_should_dismiss) OVERRIDE { 301 // The view delegate will be owned by the app list view. The app list view 302 // manages it's own lifetime. 303 AppListViewDelegate* view_delegate = 304 new AppListViewDelegate(profile, 305 service->GetControllerDelegate()); 306 app_list::AppListView* view = new app_list::AppListView(view_delegate); 307 gfx::Point cursor = gfx::Screen::GetNativeScreen()->GetCursorScreenPoint(); 308 view->InitAsBubbleAtFixedLocation(NULL, 309 &pagination_model_, 310 cursor, 311 views::BubbleBorder::FLOAT, 312 false /* border_accepts_events */); 313 SetWindowAttributes(view->GetHWND()); 314 return new AppListWin(view, on_should_dismiss); 315 } 316 317 private: 318 // PaginationModel that is shared across all views. 319 app_list::PaginationModel pagination_model_; 320 AppListServiceWin* service_; 321 322 DISALLOW_COPY_AND_ASSIGN(AppListFactoryWin); 323}; 324 325} // namespace 326 327// static 328AppListServiceWin* AppListServiceWin::GetInstance() { 329 return Singleton<AppListServiceWin, 330 LeakySingletonTraits<AppListServiceWin> >::get(); 331} 332 333AppListServiceWin::AppListServiceWin() 334 : enable_app_list_on_next_init_(false), 335 shower_(new AppListShower( 336 scoped_ptr<AppListFactory>(new AppListFactoryWin(this)), 337 scoped_ptr<KeepAliveService>(new KeepAliveServiceImpl), 338 this)), 339 controller_delegate_(new AppListControllerDelegateWin(this)), 340 weak_factory_(this) { 341} 342 343AppListServiceWin::~AppListServiceWin() { 344} 345 346void AppListServiceWin::set_can_close(bool can_close) { 347 shower_->set_can_close(can_close); 348} 349 350gfx::NativeWindow AppListServiceWin::GetAppListWindow() { 351 return shower_->GetWindow(); 352} 353 354Profile* AppListServiceWin::GetCurrentAppListProfile() { 355 return shower_->profile(); 356} 357 358AppListControllerDelegate* AppListServiceWin::GetControllerDelegate() { 359 return controller_delegate_.get(); 360} 361 362void AppListServiceWin::ShowForProfile(Profile* requested_profile) { 363 DCHECK(requested_profile); 364 if (requested_profile->IsManaged()) 365 return; 366 367 ScopedKeepAlive show_app_list_keepalive; 368 369 content::BrowserThread::PostBlockingPoolTask( 370 FROM_HERE, base::Bind(SetDidRunForNDayActiveStats)); 371 372 if (win8::IsSingleWindowMetroMode()) { 373 // This request came from Windows 8 in desktop mode, but chrome is currently 374 // running in Metro mode. 375 AppMetroInfoBarDelegateWin::Create( 376 requested_profile, AppMetroInfoBarDelegateWin::SHOW_APP_LIST, 377 std::string()); 378 return; 379 } 380 381 InvalidatePendingProfileLoads(); 382 SetProfilePath(requested_profile->GetPath()); 383 shower_->ShowForProfile(requested_profile); 384 RecordAppListLaunch(); 385} 386 387void AppListServiceWin::DismissAppList() { 388 shower_->DismissAppList(); 389} 390 391void AppListServiceWin::OnAppListClosing() { 392 shower_->CloseAppList(); 393} 394 395void AppListServiceWin::OnLoadProfileForWarmup(Profile* initial_profile) { 396 if (!IsWarmupNeeded()) 397 return; 398 399 base::Time before_warmup(base::Time::Now()); 400 shower_->WarmupForProfile(initial_profile); 401 UMA_HISTOGRAM_TIMES("Apps.AppListWarmupDuration", 402 base::Time::Now() - before_warmup); 403} 404 405void AppListServiceWin::SetAppListNextPaintCallback(void (*callback)()) { 406 app_list::AppListView::SetNextPaintCallback(callback); 407} 408 409void AppListServiceWin::HandleFirstRun() { 410 PrefService* local_state = g_browser_process->local_state(); 411 // If the app list is already enabled during first run, then the user had 412 // opted in to the app launcher before uninstalling, so we re-enable to 413 // restore shortcuts to the app list. 414 // Note we can't directly create the shortcuts here because the IO thread 415 // hasn't been created yet. 416 enable_app_list_on_next_init_ = local_state->GetBoolean( 417 prefs::kAppLauncherHasBeenEnabled); 418} 419 420void AppListServiceWin::Init(Profile* initial_profile) { 421 // In non-Ash metro mode, we can not show the app list for this process, so do 422 // not bother performing Init tasks. 423 if (win8::IsSingleWindowMetroMode()) 424 return; 425 426 if (enable_app_list_on_next_init_) { 427 enable_app_list_on_next_init_ = false; 428 EnableAppList(initial_profile, ENABLE_ON_REINSTALL); 429 CreateShortcut(); 430 } 431 432 PrefService* prefs = g_browser_process->local_state(); 433 if (prefs->HasPrefPath(prefs::kRestartWithAppList) && 434 prefs->GetBoolean(prefs::kRestartWithAppList)) { 435 prefs->SetBoolean(prefs::kRestartWithAppList, false); 436 // If we are restarting in Metro mode we will lose focus straight away. We 437 // need to reacquire focus when that happens. 438 shower_->ShowAndReacquireFocus(initial_profile); 439 } 440 441 // Migrate from legacy app launcher if we are on a non-canary and non-chromium 442 // build. 443#if defined(GOOGLE_CHROME_BUILD) 444 if (!InstallUtil::IsChromeSxSProcess() && 445 !chrome_launcher_support::GetAnyAppHostPath().empty()) { 446 chrome_launcher_support::InstallationState state = 447 chrome_launcher_support::GetAppLauncherInstallationState(); 448 if (state == chrome_launcher_support::NOT_INSTALLED) { 449 // If app_host.exe is found but can't be located in the registry, 450 // skip the migration as this is likely a developer build. 451 return; 452 } else if (state == chrome_launcher_support::INSTALLED_AT_SYSTEM_LEVEL) { 453 chrome_launcher_support::UninstallLegacyAppLauncher( 454 chrome_launcher_support::SYSTEM_LEVEL_INSTALLATION); 455 } else if (state == chrome_launcher_support::INSTALLED_AT_USER_LEVEL) { 456 chrome_launcher_support::UninstallLegacyAppLauncher( 457 chrome_launcher_support::USER_LEVEL_INSTALLATION); 458 } 459 EnableAppList(initial_profile, ENABLE_ON_REINSTALL); 460 CreateShortcut(); 461 } 462#endif 463 464 ScheduleWarmup(); 465 466 MigrateAppLauncherEnabledPref(); 467 PerformStartupChecks(initial_profile); 468} 469 470void AppListServiceWin::CreateForProfile(Profile* profile) { 471 shower_->CreateViewForProfile(profile); 472} 473 474bool AppListServiceWin::IsAppListVisible() const { 475 return shower_->IsAppListVisible(); 476} 477 478void AppListServiceWin::CreateShortcut() { 479 // Check if the app launcher shortcuts have ever been created before. 480 // Shortcuts should only be created once. If the user unpins the taskbar 481 // shortcut, they can restore it by pinning the start menu or desktop 482 // shortcut. 483 ShellIntegration::ShortcutLocations shortcut_locations; 484 shortcut_locations.on_desktop = true; 485 shortcut_locations.in_quick_launch_bar = true; 486 shortcut_locations.applications_menu_location = 487 ShellIntegration::APP_MENU_LOCATION_SUBDIR_CHROME; 488 base::FilePath user_data_dir( 489 g_browser_process->profile_manager()->user_data_dir()); 490 491 content::BrowserThread::PostTask( 492 content::BrowserThread::FILE, 493 FROM_HERE, 494 base::Bind(&CreateAppListShortcuts, 495 user_data_dir, GetAppModelId(), shortcut_locations)); 496} 497 498void AppListServiceWin::ScheduleWarmup() { 499 // Post a task to create the app list. This is posted to not impact startup 500 // time. 501 const int kInitWindowDelay = 30; 502 base::MessageLoop::current()->PostDelayedTask( 503 FROM_HERE, 504 base::Bind(&AppListServiceWin::LoadProfileForWarmup, 505 weak_factory_.GetWeakPtr()), 506 base::TimeDelta::FromSeconds(kInitWindowDelay)); 507} 508 509bool AppListServiceWin::IsWarmupNeeded() { 510 if (!g_browser_process || g_browser_process->IsShuttingDown() || 511 browser_shutdown::IsTryingToQuit()) { 512 return false; 513 } 514 515 // We only need to initialize the view if there's no view already created and 516 // there's no profile loading to be shown. 517 return !shower_->HasView() && !profile_loader().IsAnyProfileLoading(); 518} 519 520void AppListServiceWin::LoadProfileForWarmup() { 521 if (!IsWarmupNeeded()) 522 return; 523 524 ProfileManager* profile_manager = g_browser_process->profile_manager(); 525 base::FilePath profile_path(GetProfilePath(profile_manager->user_data_dir())); 526 527 profile_loader().LoadProfileInvalidatingOtherLoads( 528 profile_path, 529 base::Bind(&AppListServiceWin::OnLoadProfileForWarmup, 530 weak_factory_.GetWeakPtr())); 531} 532 533namespace chrome { 534 535AppListService* GetAppListServiceWin() { 536 return AppListServiceWin::GetInstance(); 537} 538 539} // namespace chrome 540