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