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