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