profile_shortcut_manager_win.cc revision 0529e5d033099cbfc42635f6f6183833b09dff6e
1// Copyright (c) 2012 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/profiles/profile_shortcut_manager_win.h" 6 7#include <shlobj.h> // For SHChangeNotify(). 8 9#include <string> 10#include <vector> 11 12#include "base/bind.h" 13#include "base/command_line.h" 14#include "base/file_util.h" 15#include "base/files/file_enumerator.h" 16#include "base/path_service.h" 17#include "base/prefs/pref_service.h" 18#include "base/strings/string16.h" 19#include "base/strings/string_util.h" 20#include "base/strings/stringprintf.h" 21#include "base/strings/utf_string_conversions.h" 22#include "base/win/shortcut.h" 23#include "chrome/browser/app_icon_win.h" 24#include "chrome/browser/browser_process.h" 25#include "chrome/browser/chrome_notification_types.h" 26#include "chrome/browser/profiles/profile_avatar_icon_util.h" 27#include "chrome/browser/profiles/profile_info_cache_observer.h" 28#include "chrome/browser/profiles/profile_manager.h" 29#include "chrome/browser/shell_integration.h" 30#include "chrome/common/chrome_switches.h" 31#include "chrome/common/pref_names.h" 32#include "chrome/installer/util/browser_distribution.h" 33#include "chrome/installer/util/product.h" 34#include "chrome/installer/util/shell_util.h" 35#include "content/public/browser/browser_thread.h" 36#include "content/public/browser/notification_service.h" 37#include "grit/chrome_unscaled_resources.h" 38#include "grit/chromium_strings.h" 39#include "skia/ext/image_operations.h" 40#include "skia/ext/platform_canvas.h" 41#include "ui/base/l10n/l10n_util.h" 42#include "ui/base/resource/resource_bundle.h" 43#include "ui/gfx/icon_util.h" 44#include "ui/gfx/image/image.h" 45#include "ui/gfx/image/image_family.h" 46#include "ui/gfx/rect.h" 47#include "ui/gfx/skia_util.h" 48 49using content::BrowserThread; 50 51namespace { 52 53// Name of the badged icon file generated for a given profile. 54const char kProfileIconFileName[] = "Google Profile.ico"; 55 56// Characters that are not allowed in Windows filenames. Taken from 57// http://msdn.microsoft.com/en-us/library/aa365247.aspx 58const base::char16 kReservedCharacters[] = L"<>:\"/\\|?*\x01\x02\x03\x04\x05" 59 L"\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17" 60 L"\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"; 61 62// The maximum number of characters allowed in profile shortcuts' file names. 63// Warning: migration code will be needed if this is changed later, since 64// existing shortcuts might no longer be found if the name is generated 65// differently than it was when a shortcut was originally created. 66const int kMaxProfileShortcutFileNameLength = 64; 67 68// The avatar badge size needs to be half of the shortcut icon size because 69// the Windows taskbar icon is 32x32 and the avatar icon overlay is 16x16. So to 70// get the shortcut avatar badge and the avatar icon overlay to match up, we 71// need to preserve those ratios when creating the shortcut icon. 72const int kShortcutIconSize = 48; 73const int kProfileAvatarBadgeSize = kShortcutIconSize / 2; 74 75const int kCurrentProfileIconVersion = 2; 76 77// 2x sized profile avatar icons. Mirrors |kDefaultAvatarIconResources| in 78// profile_info_cache.cc. 79const int kProfileAvatarIconResources2x[] = { 80 IDR_PROFILE_AVATAR_2X_0, 81 IDR_PROFILE_AVATAR_2X_1, 82 IDR_PROFILE_AVATAR_2X_2, 83 IDR_PROFILE_AVATAR_2X_3, 84 IDR_PROFILE_AVATAR_2X_4, 85 IDR_PROFILE_AVATAR_2X_5, 86 IDR_PROFILE_AVATAR_2X_6, 87 IDR_PROFILE_AVATAR_2X_7, 88 IDR_PROFILE_AVATAR_2X_8, 89 IDR_PROFILE_AVATAR_2X_9, 90 IDR_PROFILE_AVATAR_2X_10, 91 IDR_PROFILE_AVATAR_2X_11, 92 IDR_PROFILE_AVATAR_2X_12, 93 IDR_PROFILE_AVATAR_2X_13, 94 IDR_PROFILE_AVATAR_2X_14, 95 IDR_PROFILE_AVATAR_2X_15, 96 IDR_PROFILE_AVATAR_2X_16, 97 IDR_PROFILE_AVATAR_2X_17, 98 IDR_PROFILE_AVATAR_2X_18, 99 IDR_PROFILE_AVATAR_2X_19, 100 IDR_PROFILE_AVATAR_2X_20, 101 IDR_PROFILE_AVATAR_2X_21, 102 IDR_PROFILE_AVATAR_2X_22, 103 IDR_PROFILE_AVATAR_2X_23, 104 IDR_PROFILE_AVATAR_2X_24, 105 IDR_PROFILE_AVATAR_2X_25, 106 IDR_PROFILE_AVATAR_2X_26, 107}; 108 109// Badges |app_icon_bitmap| with |avatar_bitmap| at the bottom right corner and 110// returns the resulting SkBitmap. 111SkBitmap BadgeIcon(const SkBitmap& app_icon_bitmap, 112 const SkBitmap& avatar_bitmap, 113 int scale_factor) { 114 // All icons, whether cartoon, GAIA or placeholder, should be square. 115 // TODO(mlerman) - uncomment the ASSERT once noms@ lands the square images. 116 // DCHECK(avatar_bitmap.width() == avatar_bitmap.height()); 117 118 int avatar_badge_size = kProfileAvatarBadgeSize; 119 if (app_icon_bitmap.width() != kShortcutIconSize) { 120 avatar_badge_size = 121 app_icon_bitmap.width() * kProfileAvatarBadgeSize / kShortcutIconSize; 122 } 123 SkBitmap sk_icon = skia::ImageOperations::Resize( 124 avatar_bitmap, skia::ImageOperations::RESIZE_LANCZOS3, avatar_badge_size, 125 avatar_bitmap.height() * avatar_badge_size / avatar_bitmap.width()); 126 127 // Overlay the avatar on the icon, anchoring it to the bottom-right of the 128 // icon. 129 SkBitmap badged_bitmap; 130 badged_bitmap.allocN32Pixels(app_icon_bitmap.width(), 131 app_icon_bitmap.height()); 132 SkCanvas offscreen_canvas(badged_bitmap); 133 offscreen_canvas.clear(SK_ColorTRANSPARENT); 134 135 offscreen_canvas.drawBitmap(app_icon_bitmap, 0, 0); 136 offscreen_canvas.drawBitmap(sk_icon, 137 app_icon_bitmap.width() - sk_icon.width(), 138 app_icon_bitmap.height() - sk_icon.height()); 139 return badged_bitmap; 140} 141 142// Updates the preferences with the current icon version on icon creation 143// success. 144void OnProfileIconCreateSuccess(base::FilePath profile_path) { 145 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 146 if (!g_browser_process->profile_manager()) 147 return; 148 Profile* profile = 149 g_browser_process->profile_manager()->GetProfileByPath(profile_path); 150 if (profile) { 151 profile->GetPrefs()->SetInteger(prefs::kProfileIconVersion, 152 kCurrentProfileIconVersion); 153 } 154} 155 156// Creates a desktop shortcut icon file (.ico) on the disk for a given profile, 157// badging the browser distribution icon with the profile avatar. 158// Returns a path to the shortcut icon file on disk, which is empty if this 159// fails. Use index 0 when assigning the resulting file as the icon. If both 160// given bitmaps are empty, an unbadged icon is created. 161// Returns the path to the created icon on success and an empty base::FilePath 162// on failure. 163// TODO(calamity): Ideally we'd just copy the app icon verbatim from the exe's 164// resources in the case of an unbadged icon. 165base::FilePath CreateOrUpdateShortcutIconForProfile( 166 const base::FilePath& profile_path, 167 const SkBitmap& avatar_bitmap_1x, 168 const SkBitmap& avatar_bitmap_2x) { 169 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 170 171 if (!base::PathExists(profile_path)) { 172 LOG(ERROR) << "Profile directory " << profile_path.value() 173 << " did not exist when trying to create profile icon"; 174 return base::FilePath(); 175 } 176 177 scoped_ptr<SkBitmap> app_icon_bitmap(GetAppIconForSize(kShortcutIconSize)); 178 if (!app_icon_bitmap) 179 return base::FilePath(); 180 181 gfx::ImageFamily badged_bitmaps; 182 if (!avatar_bitmap_1x.empty()) { 183 badged_bitmaps.Add(gfx::Image::CreateFrom1xBitmap( 184 BadgeIcon(*app_icon_bitmap, avatar_bitmap_1x, 1))); 185 } 186 187 scoped_ptr<SkBitmap> large_app_icon_bitmap( 188 GetAppIconForSize(IconUtil::kLargeIconSize)); 189 if (large_app_icon_bitmap && !avatar_bitmap_2x.empty()) { 190 badged_bitmaps.Add(gfx::Image::CreateFrom1xBitmap( 191 BadgeIcon(*large_app_icon_bitmap, avatar_bitmap_2x, 2))); 192 } 193 194 // If we have no badged bitmaps, we should just use the default chrome icon. 195 if (badged_bitmaps.empty()) { 196 badged_bitmaps.Add(gfx::Image::CreateFrom1xBitmap(*app_icon_bitmap)); 197 if (large_app_icon_bitmap) { 198 badged_bitmaps.Add( 199 gfx::Image::CreateFrom1xBitmap(*large_app_icon_bitmap)); 200 } 201 } 202 // Finally, write the .ico file containing this new bitmap. 203 const base::FilePath icon_path = 204 profiles::internal::GetProfileIconPath(profile_path); 205 const bool had_icon = base::PathExists(icon_path); 206 207 if (!IconUtil::CreateIconFileFromImageFamily(badged_bitmaps, icon_path)) { 208 // This can happen in theory if the profile directory is deleted between the 209 // beginning of this function and here; however this is extremely unlikely 210 // and this check will help catch any regression where this call would start 211 // failing constantly. 212 NOTREACHED(); 213 return base::FilePath(); 214 } 215 216 if (had_icon) { 217 // This invalidates the Windows icon cache and causes the icon changes to 218 // register with the taskbar and desktop. SHCNE_ASSOCCHANGED will cause a 219 // desktop flash and we would like to avoid that if possible. 220 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); 221 } else { 222 SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, icon_path.value().c_str(), NULL); 223 } 224 BrowserThread::PostTask( 225 BrowserThread::UI, FROM_HERE, 226 base::Bind(&OnProfileIconCreateSuccess, profile_path)); 227 return icon_path; 228} 229 230// Gets the user and system directories for desktop shortcuts. Parameters may 231// be NULL if a directory type is not needed. Returns true on success. 232bool GetDesktopShortcutsDirectories( 233 base::FilePath* user_shortcuts_directory, 234 base::FilePath* system_shortcuts_directory) { 235 BrowserDistribution* distribution = BrowserDistribution::GetDistribution(); 236 if (user_shortcuts_directory && 237 !ShellUtil::GetShortcutPath(ShellUtil::SHORTCUT_LOCATION_DESKTOP, 238 distribution, ShellUtil::CURRENT_USER, 239 user_shortcuts_directory)) { 240 NOTREACHED(); 241 return false; 242 } 243 if (system_shortcuts_directory && 244 !ShellUtil::GetShortcutPath(ShellUtil::SHORTCUT_LOCATION_DESKTOP, 245 distribution, ShellUtil::SYSTEM_LEVEL, 246 system_shortcuts_directory)) { 247 NOTREACHED(); 248 return false; 249 } 250 return true; 251} 252 253// Returns the long form of |path|, which will expand any shortened components 254// like "foo~2" to their full names. 255base::FilePath ConvertToLongPath(const base::FilePath& path) { 256 const size_t length = GetLongPathName(path.value().c_str(), NULL, 0); 257 if (length != 0 && length != path.value().length()) { 258 std::vector<wchar_t> long_path(length); 259 if (GetLongPathName(path.value().c_str(), &long_path[0], length) != 0) 260 return base::FilePath(&long_path[0]); 261 } 262 return path; 263} 264 265// Returns true if the file at |path| is a Chrome shortcut and returns its 266// command line in output parameter |command_line|. 267bool IsChromeShortcut(const base::FilePath& path, 268 const base::FilePath& chrome_exe, 269 base::string16* command_line) { 270 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 271 272 if (path.Extension() != installer::kLnkExt) 273 return false; 274 275 base::FilePath target_path; 276 if (!base::win::ResolveShortcut(path, &target_path, command_line)) 277 return false; 278 // One of the paths may be in short (elided) form. Compare long paths to 279 // ensure these are still properly matched. 280 return ConvertToLongPath(target_path) == ConvertToLongPath(chrome_exe); 281} 282 283// Populates |paths| with the file paths of Chrome desktop shortcuts that have 284// the specified |command_line|. If |include_empty_command_lines| is true, 285// Chrome desktop shortcuts with empty command lines will also be included. 286void ListDesktopShortcutsWithCommandLine(const base::FilePath& chrome_exe, 287 const base::string16& command_line, 288 bool include_empty_command_lines, 289 std::vector<base::FilePath>* paths) { 290 base::FilePath user_shortcuts_directory; 291 if (!GetDesktopShortcutsDirectories(&user_shortcuts_directory, NULL)) 292 return; 293 294 base::FileEnumerator enumerator(user_shortcuts_directory, false, 295 base::FileEnumerator::FILES); 296 for (base::FilePath path = enumerator.Next(); !path.empty(); 297 path = enumerator.Next()) { 298 base::string16 shortcut_command_line; 299 if (!IsChromeShortcut(path, chrome_exe, &shortcut_command_line)) 300 continue; 301 302 // TODO(asvitkine): Change this to build a CommandLine object and ensure all 303 // args from |command_line| are present in the shortcut's CommandLine. This 304 // will be more robust when |command_line| contains multiple args. 305 if ((shortcut_command_line.empty() && include_empty_command_lines) || 306 (shortcut_command_line.find(command_line) != base::string16::npos)) { 307 paths->push_back(path); 308 } 309 } 310} 311 312// Renames the given desktop shortcut and informs the shell of this change. 313bool RenameDesktopShortcut(const base::FilePath& old_shortcut_path, 314 const base::FilePath& new_shortcut_path) { 315 if (!base::Move(old_shortcut_path, new_shortcut_path)) 316 return false; 317 318 // Notify the shell of the rename, which allows the icon to keep its position 319 // on the desktop when renamed. Note: This only works if either SHCNF_FLUSH or 320 // SHCNF_FLUSHNOWAIT is specified as a flag. 321 SHChangeNotify(SHCNE_RENAMEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT, 322 old_shortcut_path.value().c_str(), 323 new_shortcut_path.value().c_str()); 324 return true; 325} 326 327// Renames an existing Chrome desktop profile shortcut. Must be called on the 328// FILE thread. 329void RenameChromeDesktopShortcutForProfile( 330 const base::string16& old_shortcut_filename, 331 const base::string16& new_shortcut_filename) { 332 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 333 334 base::FilePath user_shortcuts_directory; 335 base::FilePath system_shortcuts_directory; 336 if (!GetDesktopShortcutsDirectories(&user_shortcuts_directory, 337 &system_shortcuts_directory)) { 338 return; 339 } 340 341 const base::FilePath old_shortcut_path = 342 user_shortcuts_directory.Append(old_shortcut_filename); 343 const base::FilePath new_shortcut_path = 344 user_shortcuts_directory.Append(new_shortcut_filename); 345 346 if (base::PathExists(old_shortcut_path)) { 347 // Rename the old shortcut unless a system-level shortcut exists at the 348 // destination, in which case the old shortcut is simply deleted. 349 const base::FilePath possible_new_system_shortcut = 350 system_shortcuts_directory.Append(new_shortcut_filename); 351 if (base::PathExists(possible_new_system_shortcut)) 352 base::DeleteFile(old_shortcut_path, false); 353 else if (!RenameDesktopShortcut(old_shortcut_path, new_shortcut_path)) 354 DLOG(ERROR) << "Could not rename Windows profile desktop shortcut."; 355 } else { 356 // If the shortcut does not exist, it may have been renamed by the user. In 357 // that case, its name should not be changed. 358 // It's also possible that a system-level shortcut exists instead - this 359 // should only be the case for the original Chrome shortcut from an 360 // installation. If that's the case, copy that one over - it will get its 361 // properties updated by 362 // |CreateOrUpdateDesktopShortcutsAndIconForProfile()|. 363 const base::FilePath possible_old_system_shortcut = 364 system_shortcuts_directory.Append(old_shortcut_filename); 365 if (base::PathExists(possible_old_system_shortcut)) 366 base::CopyFile(possible_old_system_shortcut, new_shortcut_path); 367 } 368} 369 370struct CreateOrUpdateShortcutsParams { 371 CreateOrUpdateShortcutsParams( 372 base::FilePath profile_path, 373 ProfileShortcutManagerWin::CreateOrUpdateMode create_mode, 374 ProfileShortcutManagerWin::NonProfileShortcutAction action) 375 : profile_path(profile_path), create_mode(create_mode), action(action) {} 376 ~CreateOrUpdateShortcutsParams() {} 377 378 ProfileShortcutManagerWin::CreateOrUpdateMode create_mode; 379 ProfileShortcutManagerWin::NonProfileShortcutAction action; 380 381 // The path for this profile. 382 base::FilePath profile_path; 383 // The profile name before this update. Empty on create. 384 base::string16 old_profile_name; 385 // The new profile name. 386 base::string16 profile_name; 387 // Avatar images for this profile. 388 SkBitmap avatar_image_1x; 389 SkBitmap avatar_image_2x; 390}; 391 392// Updates all desktop shortcuts for the given profile to have the specified 393// parameters. If |params.create_mode| is CREATE_WHEN_NONE_FOUND, a new shortcut 394// is created if no existing ones were found. Whether non-profile shortcuts 395// should be updated is specified by |params.action|. Must be called on the FILE 396// thread. 397void CreateOrUpdateDesktopShortcutsAndIconForProfile( 398 const CreateOrUpdateShortcutsParams& params) { 399 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 400 401 const base::FilePath shortcut_icon = 402 CreateOrUpdateShortcutIconForProfile(params.profile_path, 403 params.avatar_image_1x, 404 params.avatar_image_2x); 405 if (shortcut_icon.empty() || 406 params.create_mode == 407 ProfileShortcutManagerWin::CREATE_OR_UPDATE_ICON_ONLY) { 408 return; 409 } 410 411 base::FilePath chrome_exe; 412 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) { 413 NOTREACHED(); 414 return; 415 } 416 417 BrowserDistribution* distribution = BrowserDistribution::GetDistribution(); 418 // Ensure that the distribution supports creating shortcuts. If it doesn't, 419 // the following code may result in NOTREACHED() being hit. 420 DCHECK(distribution->CanCreateDesktopShortcuts()); 421 422 if (params.old_profile_name != params.profile_name) { 423 const base::string16 old_shortcut_filename = 424 profiles::internal::GetShortcutFilenameForProfile( 425 params.old_profile_name, 426 distribution); 427 const base::string16 new_shortcut_filename = 428 profiles::internal::GetShortcutFilenameForProfile(params.profile_name, 429 distribution); 430 RenameChromeDesktopShortcutForProfile(old_shortcut_filename, 431 new_shortcut_filename); 432 } 433 434 ShellUtil::ShortcutProperties properties(ShellUtil::CURRENT_USER); 435 installer::Product product(distribution); 436 product.AddDefaultShortcutProperties(chrome_exe, &properties); 437 438 const base::string16 command_line = 439 profiles::internal::CreateProfileShortcutFlags(params.profile_path); 440 441 // Only set the profile-specific properties when |profile_name| is non empty. 442 // If it is empty, it means the shortcut being created should be a regular, 443 // non-profile Chrome shortcut. 444 if (!params.profile_name.empty()) { 445 properties.set_arguments(command_line); 446 properties.set_icon(shortcut_icon, 0); 447 } else { 448 // Set the arguments explicitly to the empty string to ensure that 449 // |ShellUtil::CreateOrUpdateShortcut| updates that part of the shortcut. 450 properties.set_arguments(base::string16()); 451 } 452 453 properties.set_app_id( 454 ShellIntegration::GetChromiumModelIdForProfile(params.profile_path)); 455 456 ShellUtil::ShortcutOperation operation = 457 ShellUtil::SHELL_SHORTCUT_REPLACE_EXISTING; 458 459 std::vector<base::FilePath> shortcuts; 460 ListDesktopShortcutsWithCommandLine(chrome_exe, command_line, 461 params.action == ProfileShortcutManagerWin::UPDATE_NON_PROFILE_SHORTCUTS, 462 &shortcuts); 463 if (params.create_mode == ProfileShortcutManagerWin::CREATE_WHEN_NONE_FOUND && 464 shortcuts.empty()) { 465 const base::string16 shortcut_name = 466 profiles::internal::GetShortcutFilenameForProfile(params.profile_name, 467 distribution); 468 shortcuts.push_back(base::FilePath(shortcut_name)); 469 operation = ShellUtil::SHELL_SHORTCUT_CREATE_IF_NO_SYSTEM_LEVEL; 470 } 471 472 for (size_t i = 0; i < shortcuts.size(); ++i) { 473 const base::FilePath shortcut_name = 474 shortcuts[i].BaseName().RemoveExtension(); 475 properties.set_shortcut_name(shortcut_name.value()); 476 ShellUtil::CreateOrUpdateShortcut(ShellUtil::SHORTCUT_LOCATION_DESKTOP, 477 distribution, properties, operation); 478 } 479} 480 481// Returns true if any desktop shortcuts exist with target |chrome_exe|, 482// regardless of their command line arguments. 483bool ChromeDesktopShortcutsExist(const base::FilePath& chrome_exe) { 484 base::FilePath user_shortcuts_directory; 485 if (!GetDesktopShortcutsDirectories(&user_shortcuts_directory, NULL)) 486 return false; 487 488 base::FileEnumerator enumerator(user_shortcuts_directory, false, 489 base::FileEnumerator::FILES); 490 for (base::FilePath path = enumerator.Next(); !path.empty(); 491 path = enumerator.Next()) { 492 if (IsChromeShortcut(path, chrome_exe, NULL)) 493 return true; 494 } 495 496 return false; 497} 498 499// Deletes all desktop shortcuts for the specified profile. If 500// |ensure_shortcuts_remain| is true, then a regular non-profile shortcut will 501// be created if this function would otherwise delete the last Chrome desktop 502// shortcut(s). Must be called on the FILE thread. 503void DeleteDesktopShortcuts(const base::FilePath& profile_path, 504 bool ensure_shortcuts_remain) { 505 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 506 507 base::FilePath chrome_exe; 508 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) { 509 NOTREACHED(); 510 return; 511 } 512 513 const base::string16 command_line = 514 profiles::internal::CreateProfileShortcutFlags(profile_path); 515 std::vector<base::FilePath> shortcuts; 516 ListDesktopShortcutsWithCommandLine(chrome_exe, command_line, false, 517 &shortcuts); 518 519 for (size_t i = 0; i < shortcuts.size(); ++i) { 520 // Use base::DeleteFile() instead of ShellUtil::RemoveShortcuts(), as the 521 // latter causes non-profile taskbar shortcuts to be removed since it 522 // doesn't consider the command-line of the shortcuts it deletes. 523 // TODO(huangs): Refactor with ShellUtil::RemoveShortcuts(). 524 base::win::TaskbarUnpinShortcutLink(shortcuts[i].value().c_str()); 525 base::DeleteFile(shortcuts[i], false); 526 // Notify the shell that the shortcut was deleted to ensure desktop refresh. 527 SHChangeNotify(SHCNE_DELETE, SHCNF_PATH, shortcuts[i].value().c_str(), 528 NULL); 529 } 530 531 // If |ensure_shortcuts_remain| is true and deleting this profile caused the 532 // last shortcuts to be removed, re-create a regular non-profile shortcut. 533 const bool had_shortcuts = !shortcuts.empty(); 534 if (ensure_shortcuts_remain && had_shortcuts && 535 !ChromeDesktopShortcutsExist(chrome_exe)) { 536 BrowserDistribution* distribution = BrowserDistribution::GetDistribution(); 537 // Ensure that the distribution supports creating shortcuts. If it doesn't, 538 // the following code may result in NOTREACHED() being hit. 539 DCHECK(distribution->CanCreateDesktopShortcuts()); 540 installer::Product product(distribution); 541 542 ShellUtil::ShortcutProperties properties(ShellUtil::CURRENT_USER); 543 product.AddDefaultShortcutProperties(chrome_exe, &properties); 544 properties.set_shortcut_name( 545 profiles::internal::GetShortcutFilenameForProfile(base::string16(), 546 distribution)); 547 ShellUtil::CreateOrUpdateShortcut( 548 ShellUtil::SHORTCUT_LOCATION_DESKTOP, distribution, properties, 549 ShellUtil::SHELL_SHORTCUT_CREATE_IF_NO_SYSTEM_LEVEL); 550 } 551} 552 553// Returns true if profile at |profile_path| has any shortcuts. Does not 554// consider non-profile shortcuts. Must be called on the FILE thread. 555bool HasAnyProfileShortcuts(const base::FilePath& profile_path) { 556 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 557 558 base::FilePath chrome_exe; 559 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) { 560 NOTREACHED(); 561 return false; 562 } 563 564 const base::string16 command_line = 565 profiles::internal::CreateProfileShortcutFlags(profile_path); 566 std::vector<base::FilePath> shortcuts; 567 ListDesktopShortcutsWithCommandLine(chrome_exe, command_line, false, 568 &shortcuts); 569 return !shortcuts.empty(); 570} 571 572// Replaces any reserved characters with spaces, and trims the resulting string 573// to prevent any leading and trailing spaces. Also makes sure that the 574// resulting filename doesn't exceed |kMaxProfileShortcutFileNameLength|. 575// TODO(macourteau): find a way to limit the total path's length to MAX_PATH 576// instead of limiting the profile's name to |kMaxProfileShortcutFileNameLength| 577// characters. 578base::string16 SanitizeShortcutProfileNameString( 579 const base::string16& profile_name) { 580 base::string16 sanitized = profile_name; 581 size_t pos = sanitized.find_first_of(kReservedCharacters); 582 while (pos != base::string16::npos) { 583 sanitized[pos] = L' '; 584 pos = sanitized.find_first_of(kReservedCharacters, pos + 1); 585 } 586 587 base::TrimWhitespace(sanitized, base::TRIM_LEADING, &sanitized); 588 if (sanitized.size() > kMaxProfileShortcutFileNameLength) 589 sanitized.erase(kMaxProfileShortcutFileNameLength); 590 base::TrimWhitespace(sanitized, base::TRIM_TRAILING, &sanitized); 591 592 return sanitized; 593} 594 595// Returns a copied SkBitmap for the given resource id that can be safely passed 596// to another thread. 597SkBitmap GetImageResourceSkBitmapCopy(int resource_id) { 598 const gfx::Image image = 599 ResourceBundle::GetSharedInstance().GetNativeImageNamed(resource_id); 600 DCHECK(!image.IsEmpty()); 601 602 const SkBitmap* image_bitmap = image.ToSkBitmap(); 603 SkBitmap bitmap_copy; 604 image_bitmap->deepCopyTo(&bitmap_copy); 605 return bitmap_copy; 606} 607 608} // namespace 609 610namespace profiles { 611namespace internal { 612 613base::FilePath GetProfileIconPath(const base::FilePath& profile_path) { 614 return profile_path.AppendASCII(kProfileIconFileName); 615} 616 617base::string16 GetShortcutFilenameForProfile( 618 const base::string16& profile_name, 619 BrowserDistribution* distribution) { 620 base::string16 shortcut_name; 621 if (!profile_name.empty()) { 622 shortcut_name.append(SanitizeShortcutProfileNameString(profile_name)); 623 shortcut_name.append(L" - "); 624 shortcut_name.append(l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME)); 625 } else { 626 shortcut_name.append( 627 distribution->GetShortcutName(BrowserDistribution::SHORTCUT_CHROME)); 628 } 629 return shortcut_name + installer::kLnkExt; 630} 631 632base::string16 CreateProfileShortcutFlags(const base::FilePath& profile_path) { 633 return base::StringPrintf(L"--%ls=\"%ls\"", 634 base::ASCIIToUTF16( 635 switches::kProfileDirectory).c_str(), 636 profile_path.BaseName().value().c_str()); 637} 638 639} // namespace internal 640} // namespace profiles 641 642// static 643bool ProfileShortcutManager::IsFeatureEnabled() { 644 CommandLine* command_line = CommandLine::ForCurrentProcess(); 645 return command_line->HasSwitch(switches::kEnableProfileShortcutManager) || 646 (BrowserDistribution::GetDistribution()->CanCreateDesktopShortcuts() && 647 !command_line->HasSwitch(switches::kUserDataDir)); 648} 649 650// static 651ProfileShortcutManager* ProfileShortcutManager::Create( 652 ProfileManager* manager) { 653 return new ProfileShortcutManagerWin(manager); 654} 655 656ProfileShortcutManagerWin::ProfileShortcutManagerWin(ProfileManager* manager) 657 : profile_manager_(manager) { 658 DCHECK_EQ( 659 arraysize(kProfileAvatarIconResources2x), 660 profiles::GetDefaultAvatarIconCount()); 661 662 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CREATED, 663 content::NotificationService::AllSources()); 664 665 profile_manager_->GetProfileInfoCache().AddObserver(this); 666} 667 668ProfileShortcutManagerWin::~ProfileShortcutManagerWin() { 669 profile_manager_->GetProfileInfoCache().RemoveObserver(this); 670} 671 672void ProfileShortcutManagerWin::CreateOrUpdateProfileIcon( 673 const base::FilePath& profile_path) { 674 CreateOrUpdateShortcutsForProfileAtPath(profile_path, 675 CREATE_OR_UPDATE_ICON_ONLY, 676 IGNORE_NON_PROFILE_SHORTCUTS); 677} 678 679void ProfileShortcutManagerWin::CreateProfileShortcut( 680 const base::FilePath& profile_path) { 681 CreateOrUpdateShortcutsForProfileAtPath(profile_path, CREATE_WHEN_NONE_FOUND, 682 IGNORE_NON_PROFILE_SHORTCUTS); 683} 684 685void ProfileShortcutManagerWin::RemoveProfileShortcuts( 686 const base::FilePath& profile_path) { 687 BrowserThread::PostTask( 688 BrowserThread::FILE, FROM_HERE, 689 base::Bind(&DeleteDesktopShortcuts, profile_path, false)); 690} 691 692void ProfileShortcutManagerWin::HasProfileShortcuts( 693 const base::FilePath& profile_path, 694 const base::Callback<void(bool)>& callback) { 695 BrowserThread::PostTaskAndReplyWithResult( 696 BrowserThread::FILE, FROM_HERE, 697 base::Bind(&HasAnyProfileShortcuts, profile_path), callback); 698} 699 700void ProfileShortcutManagerWin::GetShortcutProperties( 701 const base::FilePath& profile_path, 702 CommandLine* command_line, 703 base::string16* name, 704 base::FilePath* icon_path) { 705 base::FilePath chrome_exe; 706 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) { 707 NOTREACHED(); 708 return; 709 } 710 711 const ProfileInfoCache& cache = profile_manager_->GetProfileInfoCache(); 712 size_t profile_index = cache.GetIndexOfProfileWithPath(profile_path); 713 DCHECK_LT(profile_index, cache.GetNumberOfProfiles()); 714 715 // The used profile name should be empty if there is only 1 profile. 716 base::string16 shortcut_profile_name; 717 if (cache.GetNumberOfProfiles() > 1) 718 shortcut_profile_name = cache.GetNameOfProfileAtIndex(profile_index); 719 720 *name = base::FilePath(profiles::internal::GetShortcutFilenameForProfile( 721 shortcut_profile_name, 722 BrowserDistribution::GetDistribution())).RemoveExtension().value(); 723 724 command_line->ParseFromString(L"\"" + chrome_exe.value() + L"\" " + 725 profiles::internal::CreateProfileShortcutFlags(profile_path)); 726 727 *icon_path = profiles::internal::GetProfileIconPath(profile_path); 728} 729 730void ProfileShortcutManagerWin::OnProfileAdded( 731 const base::FilePath& profile_path) { 732 CreateOrUpdateProfileIcon(profile_path); 733 if (profile_manager_->GetProfileInfoCache().GetNumberOfProfiles() == 2) { 734 // When the second profile is added, make existing non-profile shortcuts 735 // point to the first profile and be badged/named appropriately. 736 CreateOrUpdateShortcutsForProfileAtPath(GetOtherProfilePath(profile_path), 737 UPDATE_EXISTING_ONLY, 738 UPDATE_NON_PROFILE_SHORTCUTS); 739 } 740} 741 742void ProfileShortcutManagerWin::OnProfileWasRemoved( 743 const base::FilePath& profile_path, 744 const base::string16& profile_name) { 745 const ProfileInfoCache& cache = profile_manager_->GetProfileInfoCache(); 746 // If there is only one profile remaining, remove the badging information 747 // from an existing shortcut. 748 const bool deleting_down_to_last_profile = (cache.GetNumberOfProfiles() == 1); 749 if (deleting_down_to_last_profile) { 750 // This is needed to unbadge the icon. 751 CreateOrUpdateShortcutsForProfileAtPath(cache.GetPathOfProfileAtIndex(0), 752 UPDATE_EXISTING_ONLY, 753 IGNORE_NON_PROFILE_SHORTCUTS); 754 } 755 756 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 757 base::Bind(&DeleteDesktopShortcuts, 758 profile_path, 759 deleting_down_to_last_profile)); 760} 761 762void ProfileShortcutManagerWin::OnProfileNameChanged( 763 const base::FilePath& profile_path, 764 const base::string16& old_profile_name) { 765 CreateOrUpdateShortcutsForProfileAtPath(profile_path, UPDATE_EXISTING_ONLY, 766 IGNORE_NON_PROFILE_SHORTCUTS); 767} 768 769void ProfileShortcutManagerWin::OnProfileAvatarChanged( 770 const base::FilePath& profile_path) { 771 CreateOrUpdateProfileIcon(profile_path); 772} 773 774base::FilePath ProfileShortcutManagerWin::GetOtherProfilePath( 775 const base::FilePath& profile_path) { 776 const ProfileInfoCache& cache = profile_manager_->GetProfileInfoCache(); 777 DCHECK_EQ(2U, cache.GetNumberOfProfiles()); 778 // Get the index of the current profile, in order to find the index of the 779 // other profile. 780 size_t current_profile_index = cache.GetIndexOfProfileWithPath(profile_path); 781 size_t other_profile_index = (current_profile_index == 0) ? 1 : 0; 782 return cache.GetPathOfProfileAtIndex(other_profile_index); 783} 784 785void ProfileShortcutManagerWin::CreateOrUpdateShortcutsForProfileAtPath( 786 const base::FilePath& profile_path, 787 CreateOrUpdateMode create_mode, 788 NonProfileShortcutAction action) { 789 DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::UI) || 790 BrowserThread::CurrentlyOn(BrowserThread::UI)); 791 CreateOrUpdateShortcutsParams params(profile_path, create_mode, action); 792 793 ProfileInfoCache* cache = &profile_manager_->GetProfileInfoCache(); 794 size_t profile_index = cache->GetIndexOfProfileWithPath(profile_path); 795 if (profile_index == std::string::npos) 796 return; 797 bool remove_badging = cache->GetNumberOfProfiles() == 1; 798 799 params.old_profile_name = 800 cache->GetShortcutNameOfProfileAtIndex(profile_index); 801 802 // Exit early if the mode is to update existing profile shortcuts only and 803 // none were ever created for this profile, per the shortcut name not being 804 // set in the profile info cache. 805 if (params.old_profile_name.empty() && 806 create_mode == UPDATE_EXISTING_ONLY && 807 action == IGNORE_NON_PROFILE_SHORTCUTS) { 808 return; 809 } 810 811 if (!remove_badging) { 812 params.profile_name = cache->GetNameOfProfileAtIndex(profile_index); 813 814 const size_t icon_index = 815 cache->GetAvatarIconIndexOfProfileAtIndex(profile_index); 816 const int resource_id_1x = 817 profiles::GetDefaultAvatarIconResourceIDAtIndex(icon_index); 818 const int resource_id_2x = kProfileAvatarIconResources2x[icon_index]; 819 // Make a copy of the SkBitmaps to ensure that we can safely use the image 820 // data on the FILE thread. 821 params.avatar_image_1x = GetImageResourceSkBitmapCopy(resource_id_1x); 822 params.avatar_image_2x = GetImageResourceSkBitmapCopy(resource_id_2x); 823 } 824 BrowserThread::PostTask( 825 BrowserThread::FILE, FROM_HERE, 826 base::Bind(&CreateOrUpdateDesktopShortcutsAndIconForProfile, params)); 827 828 cache->SetShortcutNameOfProfileAtIndex(profile_index, 829 params.profile_name); 830} 831 832void ProfileShortcutManagerWin::Observe( 833 int type, 834 const content::NotificationSource& source, 835 const content::NotificationDetails& details) { 836 switch (type) { 837 // This notification is triggered when a profile is loaded. 838 case chrome::NOTIFICATION_PROFILE_CREATED: { 839 Profile* profile = 840 content::Source<Profile>(source).ptr()->GetOriginalProfile(); 841 if (profile->GetPrefs()->GetInteger(prefs::kProfileIconVersion) < 842 kCurrentProfileIconVersion) { 843 // Ensure the profile's icon file has been created. 844 CreateOrUpdateProfileIcon(profile->GetPath()); 845 } 846 break; 847 } 848 default: 849 NOTREACHED(); 850 break; 851 } 852} 853