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