app_list_service_win.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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/files/file_util.h" 12#include "base/memory/singleton.h" 13#include "base/message_loop/message_loop.h" 14#include "base/metrics/histogram.h" 15#include "base/path_service.h" 16#include "base/prefs/pref_service.h" 17#include "base/strings/utf_string_conversions.h" 18#include "base/threading/sequenced_worker_pool.h" 19#include "base/time/time.h" 20#include "base/win/shortcut.h" 21#include "base/win/windows_version.h" 22#include "chrome/app/chrome_dll_resource.h" 23#include "chrome/browser/browser_process.h" 24#include "chrome/browser/browser_shutdown.h" 25#include "chrome/browser/platform_util.h" 26#include "chrome/browser/profiles/profile.h" 27#include "chrome/browser/profiles/profile_manager.h" 28#include "chrome/browser/shell_integration.h" 29#include "chrome/browser/ui/ash/app_list/app_list_service_ash.h" 30#include "chrome/browser/ui/views/app_list/win/activation_tracker_win.h" 31#include "chrome/browser/ui/views/app_list/win/app_list_controller_delegate_win.h" 32#include "chrome/browser/ui/views/app_list/win/app_list_win.h" 33#include "chrome/browser/web_applications/web_app.h" 34#include "chrome/common/chrome_constants.h" 35#include "chrome/common/chrome_switches.h" 36#include "chrome/common/chrome_version_info.h" 37#include "chrome/common/pref_names.h" 38#include "chrome/installer/util/browser_distribution.h" 39#include "content/public/browser/browser_thread.h" 40#include "ui/app_list/views/app_list_view.h" 41#include "ui/base/win/shell.h" 42 43#if defined(GOOGLE_CHROME_BUILD) 44#include "chrome/installer/util/google_update_settings.h" 45#include "chrome/installer/util/install_util.h" 46#include "chrome/installer/util/updating_app_registration_data.h" 47#include "chrome/installer/util/util_constants.h" 48#endif // GOOGLE_CHROME_BUILD 49 50// static 51AppListService* AppListService::Get(chrome::HostDesktopType desktop_type) { 52 if (desktop_type == chrome::HOST_DESKTOP_TYPE_ASH) 53 return AppListServiceAsh::GetInstance(); 54 55 return AppListServiceWin::GetInstance(); 56} 57 58// static 59void AppListService::InitAll(Profile* initial_profile) { 60 AppListServiceAsh::GetInstance()->Init(initial_profile); 61 AppListServiceWin::GetInstance()->Init(initial_profile); 62} 63 64namespace { 65 66// Migrate chrome::kAppLauncherIsEnabled pref to 67// chrome::kAppLauncherHasBeenEnabled pref. 68void MigrateAppLauncherEnabledPref() { 69 PrefService* prefs = g_browser_process->local_state(); 70 if (prefs->HasPrefPath(prefs::kAppLauncherIsEnabled)) { 71 prefs->SetBoolean(prefs::kAppLauncherHasBeenEnabled, 72 prefs->GetBoolean(prefs::kAppLauncherIsEnabled)); 73 prefs->ClearPref(prefs::kAppLauncherIsEnabled); 74 } 75} 76 77int GetAppListIconIndex() { 78 BrowserDistribution* dist = BrowserDistribution::GetDistribution(); 79 return dist->GetIconIndex(BrowserDistribution::SHORTCUT_APP_LAUNCHER); 80} 81 82base::string16 GetAppListIconPath() { 83 base::FilePath icon_path; 84 if (!PathService::Get(base::FILE_EXE, &icon_path)) { 85 NOTREACHED(); 86 return base::string16(); 87 } 88 89 std::stringstream ss; 90 ss << "," << GetAppListIconIndex(); 91 base::string16 result = icon_path.value(); 92 result.append(base::UTF8ToUTF16(ss.str())); 93 return result; 94} 95 96base::string16 GetAppListShortcutName() { 97 BrowserDistribution* dist = BrowserDistribution::GetDistribution(); 98 return dist->GetShortcutName(BrowserDistribution::SHORTCUT_APP_LAUNCHER); 99} 100 101CommandLine GetAppListCommandLine() { 102 const char* const kSwitchesToCopy[] = { switches::kUserDataDir }; 103 CommandLine* current = CommandLine::ForCurrentProcess(); 104 base::FilePath chrome_exe; 105 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) { 106 NOTREACHED(); 107 return CommandLine(CommandLine::NO_PROGRAM); 108 } 109 CommandLine command_line(chrome_exe); 110 command_line.CopySwitchesFrom(*current, kSwitchesToCopy, 111 arraysize(kSwitchesToCopy)); 112 command_line.AppendSwitch(switches::kShowAppList); 113 return command_line; 114} 115 116base::string16 GetAppModelId() { 117 // The AppModelId should be the same for all profiles in a user data directory 118 // but different for different user data directories, so base it on the 119 // initial profile in the current user data directory. 120 base::FilePath initial_profile_path; 121 CommandLine* command_line = CommandLine::ForCurrentProcess(); 122 if (command_line->HasSwitch(switches::kUserDataDir)) { 123 initial_profile_path = 124 command_line->GetSwitchValuePath(switches::kUserDataDir).AppendASCII( 125 chrome::kInitialProfile); 126 } 127 return ShellIntegration::GetAppListAppModelIdForProfile(initial_profile_path); 128} 129 130#if defined(GOOGLE_CHROME_BUILD) 131void SetDidRunForNDayActiveStats() { 132 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 133 base::FilePath exe_path; 134 if (!PathService::Get(base::DIR_EXE, &exe_path)) { 135 NOTREACHED(); 136 return; 137 } 138 bool system_install = 139 !InstallUtil::IsPerUserInstall(exe_path.value().c_str()); 140 // Using Chrome Binary dist: Chrome dist may not exist for the legacy 141 // App Launcher, and App Launcher dist may be "shadow", which does not 142 // contain the information needed to determine multi-install. 143 // Edge case involving Canary: crbug/239163. 144 BrowserDistribution* chrome_binaries_dist = 145 BrowserDistribution::GetSpecificDistribution( 146 BrowserDistribution::CHROME_BINARIES); 147 if (chrome_binaries_dist && 148 InstallUtil::IsMultiInstall(chrome_binaries_dist, system_install)) { 149 UpdatingAppRegistrationData app_launcher_reg_data( 150 installer::kAppLauncherGuid); 151 GoogleUpdateSettings::UpdateDidRunStateForApp( 152 app_launcher_reg_data, true /* did_run */); 153 } 154} 155#endif // GOOGLE_CHROME_BUILD 156 157// The start menu shortcut is created on first run by users that are 158// upgrading. The desktop and taskbar shortcuts are created the first time the 159// user enables the app list. The taskbar shortcut is created in 160// |user_data_dir| and will use a Windows Application Model Id of 161// |app_model_id|. This runs on the FILE thread and not in the blocking IO 162// thread pool as there are other tasks running (also on the FILE thread) 163// which fiddle with shortcut icons 164// (ShellIntegration::MigrateWin7ShortcutsOnPath). Having different threads 165// fiddle with the same shortcuts could cause race issues. 166void CreateAppListShortcuts( 167 const base::FilePath& user_data_dir, 168 const base::string16& app_model_id, 169 const web_app::ShortcutLocations& creation_locations) { 170 DCHECK_CURRENTLY_ON(content::BrowserThread::FILE); 171 172 // Shortcut paths under which to create shortcuts. 173 std::vector<base::FilePath> shortcut_paths = 174 web_app::internals::GetShortcutPaths(creation_locations); 175 176 bool pin_to_taskbar = creation_locations.in_quick_launch_bar && 177 (base::win::GetVersion() >= base::win::VERSION_WIN7); 178 179 // Create a shortcut in the |user_data_dir| for taskbar pinning. 180 if (pin_to_taskbar) 181 shortcut_paths.push_back(user_data_dir); 182 bool success = true; 183 184 base::FilePath chrome_exe; 185 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) { 186 NOTREACHED(); 187 return; 188 } 189 190 base::string16 app_list_shortcut_name = GetAppListShortcutName(); 191 192 base::string16 wide_switches(GetAppListCommandLine().GetArgumentsString()); 193 194 base::win::ShortcutProperties shortcut_properties; 195 shortcut_properties.set_target(chrome_exe); 196 shortcut_properties.set_working_dir(chrome_exe.DirName()); 197 shortcut_properties.set_arguments(wide_switches); 198 shortcut_properties.set_description(app_list_shortcut_name); 199 shortcut_properties.set_icon(chrome_exe, GetAppListIconIndex()); 200 shortcut_properties.set_app_id(app_model_id); 201 202 for (size_t i = 0; i < shortcut_paths.size(); ++i) { 203 base::FilePath shortcut_file = 204 shortcut_paths[i].Append(app_list_shortcut_name). 205 AddExtension(installer::kLnkExt); 206 if (!base::PathExists(shortcut_file.DirName()) && 207 !base::CreateDirectory(shortcut_file.DirName())) { 208 NOTREACHED(); 209 return; 210 } 211 success = success && base::win::CreateOrUpdateShortcutLink( 212 shortcut_file, shortcut_properties, 213 base::win::SHORTCUT_CREATE_ALWAYS); 214 } 215 216 if (success && pin_to_taskbar) { 217 base::FilePath shortcut_to_pin = 218 user_data_dir.Append(app_list_shortcut_name). 219 AddExtension(installer::kLnkExt); 220 success = base::win::TaskbarPinShortcutLink( 221 shortcut_to_pin.value().c_str()) && success; 222 } 223} 224 225// Customizes the app list |hwnd| for Windows (eg: disable aero peek, set up 226// restart params). 227void SetWindowAttributes(HWND hwnd) { 228 if (base::win::GetVersion() > base::win::VERSION_VISTA) { 229 // Disable aero peek. Without this, hovering over the taskbar popup puts 230 // Windows into a mode for switching between windows in the same 231 // application. The app list has just one window, so it is just distracting. 232 BOOL disable_value = TRUE; 233 ::DwmSetWindowAttribute(hwnd, 234 DWMWA_DISALLOW_PEEK, 235 &disable_value, 236 sizeof(disable_value)); 237 } 238 239 ui::win::SetAppIdForWindow(GetAppModelId(), hwnd); 240 CommandLine relaunch = GetAppListCommandLine(); 241 base::string16 app_name(GetAppListShortcutName()); 242 ui::win::SetRelaunchDetailsForWindow( 243 relaunch.GetCommandLineString(), app_name, hwnd); 244 ::SetWindowText(hwnd, app_name.c_str()); 245 base::string16 icon_path = GetAppListIconPath(); 246 ui::win::SetAppIconForWindow(icon_path, hwnd); 247} 248 249} // namespace 250 251// static 252AppListServiceWin* AppListServiceWin::GetInstance() { 253 return Singleton<AppListServiceWin, 254 LeakySingletonTraits<AppListServiceWin> >::get(); 255} 256 257AppListServiceWin::AppListServiceWin() 258 : AppListServiceViews(scoped_ptr<AppListControllerDelegate>( 259 new AppListControllerDelegateWin(this))), 260 enable_app_list_on_next_init_(false) { 261} 262 263AppListServiceWin::~AppListServiceWin() { 264} 265 266void AppListServiceWin::ShowForProfile(Profile* requested_profile) { 267 AppListServiceViews::ShowForProfile(requested_profile); 268 269#if defined(GOOGLE_CHROME_BUILD) 270 content::BrowserThread::PostBlockingPoolTask( 271 FROM_HERE, base::Bind(SetDidRunForNDayActiveStats)); 272#endif // GOOGLE_CHROME_BUILD 273} 274 275void AppListServiceWin::OnLoadProfileForWarmup(Profile* initial_profile) { 276 if (!IsWarmupNeeded()) 277 return; 278 279 base::Time before_warmup(base::Time::Now()); 280 shower().WarmupForProfile(initial_profile); 281 UMA_HISTOGRAM_TIMES("Apps.AppListWarmupDuration", 282 base::Time::Now() - before_warmup); 283} 284 285void AppListServiceWin::SetAppListNextPaintCallback(void (*callback)()) { 286 if (shower().app_list()) 287 shower().app_list()->SetNextPaintCallback(base::Bind(callback)); 288 else 289 next_paint_callback_ = base::Bind(callback); 290} 291 292void AppListServiceWin::HandleFirstRun() { 293 PrefService* local_state = g_browser_process->local_state(); 294 // If the app list is already enabled during first run, then the user had 295 // opted in to the app launcher before uninstalling, so we re-enable to 296 // restore shortcuts to the app list. 297 // Note we can't directly create the shortcuts here because the IO thread 298 // hasn't been created yet. 299 enable_app_list_on_next_init_ = local_state->GetBoolean( 300 prefs::kAppLauncherHasBeenEnabled); 301} 302 303void AppListServiceWin::Init(Profile* initial_profile) { 304 if (enable_app_list_on_next_init_) { 305 enable_app_list_on_next_init_ = false; 306 EnableAppList(initial_profile, ENABLE_ON_REINSTALL); 307 CreateShortcut(); 308 } 309 310 ScheduleWarmup(); 311 312 MigrateAppLauncherEnabledPref(); 313 AppListServiceViews::Init(initial_profile); 314} 315 316void AppListServiceWin::CreateShortcut() { 317 // Check if the app launcher shortcuts have ever been created before. 318 // Shortcuts should only be created once. If the user unpins the taskbar 319 // shortcut, they can restore it by pinning the start menu or desktop 320 // shortcut. 321 web_app::ShortcutLocations shortcut_locations; 322 shortcut_locations.on_desktop = true; 323 shortcut_locations.in_quick_launch_bar = true; 324 shortcut_locations.applications_menu_location = 325 web_app::APP_MENU_LOCATION_SUBDIR_CHROME; 326 base::FilePath user_data_dir( 327 g_browser_process->profile_manager()->user_data_dir()); 328 329 content::BrowserThread::PostTask( 330 content::BrowserThread::FILE, 331 FROM_HERE, 332 base::Bind(&CreateAppListShortcuts, 333 user_data_dir, GetAppModelId(), shortcut_locations)); 334} 335 336void AppListServiceWin::ScheduleWarmup() { 337 // Post a task to create the app list. This is posted to not impact startup 338 // time. 339 const int kInitWindowDelay = 30; 340 base::MessageLoop::current()->PostDelayedTask( 341 FROM_HERE, 342 base::Bind(&AppListServiceWin::LoadProfileForWarmup, 343 base::Unretained(this)), 344 base::TimeDelta::FromSeconds(kInitWindowDelay)); 345} 346 347bool AppListServiceWin::IsWarmupNeeded() { 348 if (!g_browser_process || g_browser_process->IsShuttingDown() || 349 browser_shutdown::IsTryingToQuit()) { 350 return false; 351 } 352 353 // We only need to initialize the view if there's no view already created and 354 // there's no profile loading to be shown. 355 return !shower().HasView() && !profile_loader().IsAnyProfileLoading(); 356} 357 358void AppListServiceWin::LoadProfileForWarmup() { 359 if (!IsWarmupNeeded()) 360 return; 361 362 ProfileManager* profile_manager = g_browser_process->profile_manager(); 363 base::FilePath profile_path(GetProfilePath(profile_manager->user_data_dir())); 364 365 profile_loader().LoadProfileInvalidatingOtherLoads( 366 profile_path, 367 base::Bind(&AppListServiceWin::OnLoadProfileForWarmup, 368 base::Unretained(this))); 369} 370 371void AppListServiceWin::OnViewBeingDestroyed() { 372 activation_tracker_.reset(); 373 AppListServiceViews::OnViewBeingDestroyed(); 374} 375 376void AppListServiceWin::OnViewCreated() { 377 if (!next_paint_callback_.is_null()) { 378 shower().app_list()->SetNextPaintCallback(next_paint_callback_); 379 next_paint_callback_.Reset(); 380 } 381 SetWindowAttributes(shower().app_list()->GetHWND()); 382 activation_tracker_.reset(new ActivationTrackerWin(this)); 383} 384 385void AppListServiceWin::OnViewDismissed() { 386 activation_tracker_->OnViewHidden(); 387} 388 389void AppListServiceWin::MoveNearCursor(app_list::AppListView* view) { 390 AppListWin::MoveNearCursor(view); 391} 392