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