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