profile_shortcut_manager_win.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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 49using content::BrowserThread; 50 51namespace { 52 53// Name of the badged icon file generated for a given profile. 54const char kProfileIconFileName[] = "Google Profile.ico"; 55 56// Characters that are not allowed in Windows filenames. Taken from 57// http://msdn.microsoft.com/en-us/library/aa365247.aspx 58const base::char16 kReservedCharacters[] = L"<>:\"/\\|?*\x01\x02\x03\x04\x05" 59 L"\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17" 60 L"\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"; 61 62// The maximum number of characters allowed in profile shortcuts' file names. 63// Warning: migration code will be needed if this is changed later, since 64// existing shortcuts might no longer be found if the name is generated 65// differently than it was when a shortcut was originally created. 66const int kMaxProfileShortcutFileNameLength = 64; 67 68// The avatar badge size needs to be half of the shortcut icon size because 69// the Windows taskbar icon is 32x32 and the avatar icon overlay is 16x16. So to 70// get the shortcut avatar badge and the avatar icon overlay to match up, we 71// need to preserve those ratios when creating the shortcut icon. 72const int kShortcutIconSize = 48; 73const int kProfileAvatarBadgeSize = kShortcutIconSize / 2; 74 75const int kCurrentProfileIconVersion = 2; 76 77// 2x sized profile avatar icons. Mirrors |kDefaultAvatarIconResources| in 78// profile_info_cache.cc. 79const int kProfileAvatarIconResources2x[] = { 80 IDR_PROFILE_AVATAR_2X_0, 81 IDR_PROFILE_AVATAR_2X_1, 82 IDR_PROFILE_AVATAR_2X_2, 83 IDR_PROFILE_AVATAR_2X_3, 84 IDR_PROFILE_AVATAR_2X_4, 85 IDR_PROFILE_AVATAR_2X_5, 86 IDR_PROFILE_AVATAR_2X_6, 87 IDR_PROFILE_AVATAR_2X_7, 88 IDR_PROFILE_AVATAR_2X_8, 89 IDR_PROFILE_AVATAR_2X_9, 90 IDR_PROFILE_AVATAR_2X_10, 91 IDR_PROFILE_AVATAR_2X_11, 92 IDR_PROFILE_AVATAR_2X_12, 93 IDR_PROFILE_AVATAR_2X_13, 94 IDR_PROFILE_AVATAR_2X_14, 95 IDR_PROFILE_AVATAR_2X_15, 96 IDR_PROFILE_AVATAR_2X_16, 97 IDR_PROFILE_AVATAR_2X_17, 98 IDR_PROFILE_AVATAR_2X_18, 99 IDR_PROFILE_AVATAR_2X_19, 100 IDR_PROFILE_AVATAR_2X_20, 101 IDR_PROFILE_AVATAR_2X_21, 102 IDR_PROFILE_AVATAR_2X_22, 103 IDR_PROFILE_AVATAR_2X_23, 104 IDR_PROFILE_AVATAR_2X_24, 105 IDR_PROFILE_AVATAR_2X_25, 106}; 107 108// Badges |app_icon_bitmap| with |avatar_bitmap| at the bottom right corner and 109// returns the resulting SkBitmap. 110SkBitmap 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 SkBitmap badged_bitmap; 139 badged_bitmap.allocN32Pixels(app_icon_bitmap.width(), 140 app_icon_bitmap.height()); 141 SkCanvas offscreen_canvas(badged_bitmap); 142 offscreen_canvas.clear(SK_ColorTRANSPARENT); 143 144 offscreen_canvas.drawBitmap(app_icon_bitmap, 0, 0); 145 offscreen_canvas.drawBitmap(sk_icon, 146 app_icon_bitmap.width() - sk_icon.width(), 147 app_icon_bitmap.height() - sk_icon.height()); 148 return badged_bitmap; 149} 150 151// Updates the preferences with the current icon version on icon creation 152// success. 153void OnProfileIconCreateSuccess(base::FilePath profile_path) { 154 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 155 if (!g_browser_process->profile_manager()) 156 return; 157 Profile* profile = 158 g_browser_process->profile_manager()->GetProfileByPath(profile_path); 159 if (profile) { 160 profile->GetPrefs()->SetInteger(prefs::kProfileIconVersion, 161 kCurrentProfileIconVersion); 162 } 163} 164 165// Creates a desktop shortcut icon file (.ico) on the disk for a given profile, 166// badging the browser distribution icon with the profile avatar. 167// Returns a path to the shortcut icon file on disk, which is empty if this 168// fails. Use index 0 when assigning the resulting file as the icon. If both 169// given bitmaps are empty, an unbadged icon is created. 170// Returns the path to the created icon on success and an empty base::FilePath 171// on failure. 172// TODO(calamity): Ideally we'd just copy the app icon verbatim from the exe's 173// resources in the case of an unbadged icon. 174base::FilePath CreateOrUpdateShortcutIconForProfile( 175 const base::FilePath& profile_path, 176 const SkBitmap& avatar_bitmap_1x, 177 const SkBitmap& avatar_bitmap_2x) { 178 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 179 180 if (!base::PathExists(profile_path)) { 181 LOG(ERROR) << "Profile directory " << profile_path.value() 182 << " did not exist when trying to create profile icon"; 183 return base::FilePath(); 184 } 185 186 scoped_ptr<SkBitmap> app_icon_bitmap(GetAppIconForSize(kShortcutIconSize)); 187 if (!app_icon_bitmap) 188 return base::FilePath(); 189 190 gfx::ImageFamily badged_bitmaps; 191 if (!avatar_bitmap_1x.empty()) { 192 badged_bitmaps.Add(gfx::Image::CreateFrom1xBitmap( 193 BadgeIcon(*app_icon_bitmap, avatar_bitmap_1x, 1))); 194 } 195 196 scoped_ptr<SkBitmap> large_app_icon_bitmap( 197 GetAppIconForSize(IconUtil::kLargeIconSize)); 198 if (large_app_icon_bitmap && !avatar_bitmap_2x.empty()) { 199 badged_bitmaps.Add(gfx::Image::CreateFrom1xBitmap( 200 BadgeIcon(*large_app_icon_bitmap, avatar_bitmap_2x, 2))); 201 } 202 203 // If we have no badged bitmaps, we should just use the default chrome icon. 204 if (badged_bitmaps.empty()) { 205 badged_bitmaps.Add(gfx::Image::CreateFrom1xBitmap(*app_icon_bitmap)); 206 if (large_app_icon_bitmap) { 207 badged_bitmaps.Add( 208 gfx::Image::CreateFrom1xBitmap(*large_app_icon_bitmap)); 209 } 210 } 211 // Finally, write the .ico file containing this new bitmap. 212 const base::FilePath icon_path = 213 profiles::internal::GetProfileIconPath(profile_path); 214 const bool had_icon = base::PathExists(icon_path); 215 216 if (!IconUtil::CreateIconFileFromImageFamily(badged_bitmaps, icon_path)) { 217 // This can happen in theory if the profile directory is deleted between the 218 // beginning of this function and here; however this is extremely unlikely 219 // and this check will help catch any regression where this call would start 220 // failing constantly. 221 NOTREACHED(); 222 return base::FilePath(); 223 } 224 225 if (had_icon) { 226 // This invalidates the Windows icon cache and causes the icon changes to 227 // register with the taskbar and desktop. SHCNE_ASSOCCHANGED will cause a 228 // desktop flash and we would like to avoid that if possible. 229 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); 230 } else { 231 SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, icon_path.value().c_str(), NULL); 232 } 233 BrowserThread::PostTask( 234 BrowserThread::UI, FROM_HERE, 235 base::Bind(&OnProfileIconCreateSuccess, profile_path)); 236 return icon_path; 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. 241bool 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. 264base::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|. 276bool IsChromeShortcut(const base::FilePath& path, 277 const base::FilePath& chrome_exe, 278 base::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. 295void ListDesktopShortcutsWithCommandLine(const base::FilePath& chrome_exe, 296 const base::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 base::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) != base::string16::npos)) { 316 paths->push_back(path); 317 } 318 } 319} 320 321// Renames the given desktop shortcut and informs the shell of this change. 322bool 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. 338void RenameChromeDesktopShortcutForProfile( 339 const base::string16& old_shortcut_filename, 340 const base::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 379struct 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 base::string16 old_profile_name; 394 // The new profile name. 395 base::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. 406void CreateOrUpdateDesktopShortcutsAndIconForProfile( 407 const CreateOrUpdateShortcutsParams& params) { 408 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 409 410 const base::FilePath shortcut_icon = 411 CreateOrUpdateShortcutIconForProfile(params.profile_path, 412 params.avatar_image_1x, 413 params.avatar_image_2x); 414 if (shortcut_icon.empty() || 415 params.create_mode == 416 ProfileShortcutManagerWin::CREATE_OR_UPDATE_ICON_ONLY) { 417 return; 418 } 419 420 base::FilePath chrome_exe; 421 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) { 422 NOTREACHED(); 423 return; 424 } 425 426 BrowserDistribution* distribution = BrowserDistribution::GetDistribution(); 427 // Ensure that the distribution supports creating shortcuts. If it doesn't, 428 // the following code may result in NOTREACHED() being hit. 429 DCHECK(distribution->CanCreateDesktopShortcuts()); 430 431 if (params.old_profile_name != params.profile_name) { 432 const base::string16 old_shortcut_filename = 433 profiles::internal::GetShortcutFilenameForProfile( 434 params.old_profile_name, 435 distribution); 436 const base::string16 new_shortcut_filename = 437 profiles::internal::GetShortcutFilenameForProfile(params.profile_name, 438 distribution); 439 RenameChromeDesktopShortcutForProfile(old_shortcut_filename, 440 new_shortcut_filename); 441 } 442 443 ShellUtil::ShortcutProperties properties(ShellUtil::CURRENT_USER); 444 installer::Product product(distribution); 445 product.AddDefaultShortcutProperties(chrome_exe, &properties); 446 447 const base::string16 command_line = 448 profiles::internal::CreateProfileShortcutFlags(params.profile_path); 449 450 // Only set the profile-specific properties when |profile_name| is non empty. 451 // If it is empty, it means the shortcut being created should be a regular, 452 // non-profile Chrome shortcut. 453 if (!params.profile_name.empty()) { 454 properties.set_arguments(command_line); 455 properties.set_icon(shortcut_icon, 0); 456 } else { 457 // Set the arguments explicitly to the empty string to ensure that 458 // |ShellUtil::CreateOrUpdateShortcut| updates that part of the shortcut. 459 properties.set_arguments(base::string16()); 460 } 461 462 properties.set_app_id( 463 ShellIntegration::GetChromiumModelIdForProfile(params.profile_path)); 464 465 ShellUtil::ShortcutOperation operation = 466 ShellUtil::SHELL_SHORTCUT_REPLACE_EXISTING; 467 468 std::vector<base::FilePath> shortcuts; 469 ListDesktopShortcutsWithCommandLine(chrome_exe, command_line, 470 params.action == ProfileShortcutManagerWin::UPDATE_NON_PROFILE_SHORTCUTS, 471 &shortcuts); 472 if (params.create_mode == ProfileShortcutManagerWin::CREATE_WHEN_NONE_FOUND && 473 shortcuts.empty()) { 474 const base::string16 shortcut_name = 475 profiles::internal::GetShortcutFilenameForProfile(params.profile_name, 476 distribution); 477 shortcuts.push_back(base::FilePath(shortcut_name)); 478 operation = ShellUtil::SHELL_SHORTCUT_CREATE_IF_NO_SYSTEM_LEVEL; 479 } 480 481 for (size_t i = 0; i < shortcuts.size(); ++i) { 482 const base::FilePath shortcut_name = 483 shortcuts[i].BaseName().RemoveExtension(); 484 properties.set_shortcut_name(shortcut_name.value()); 485 ShellUtil::CreateOrUpdateShortcut(ShellUtil::SHORTCUT_LOCATION_DESKTOP, 486 distribution, properties, operation); 487 } 488} 489 490// Returns true if any desktop shortcuts exist with target |chrome_exe|, 491// regardless of their command line arguments. 492bool ChromeDesktopShortcutsExist(const base::FilePath& chrome_exe) { 493 base::FilePath user_shortcuts_directory; 494 if (!GetDesktopShortcutsDirectories(&user_shortcuts_directory, NULL)) 495 return false; 496 497 base::FileEnumerator enumerator(user_shortcuts_directory, false, 498 base::FileEnumerator::FILES); 499 for (base::FilePath path = enumerator.Next(); !path.empty(); 500 path = enumerator.Next()) { 501 if (IsChromeShortcut(path, chrome_exe, NULL)) 502 return true; 503 } 504 505 return false; 506} 507 508// Deletes all desktop shortcuts for the specified profile. If 509// |ensure_shortcuts_remain| is true, then a regular non-profile shortcut will 510// be created if this function would otherwise delete the last Chrome desktop 511// shortcut(s). Must be called on the FILE thread. 512void DeleteDesktopShortcuts(const base::FilePath& profile_path, 513 bool ensure_shortcuts_remain) { 514 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 515 516 base::FilePath chrome_exe; 517 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) { 518 NOTREACHED(); 519 return; 520 } 521 522 const base::string16 command_line = 523 profiles::internal::CreateProfileShortcutFlags(profile_path); 524 std::vector<base::FilePath> shortcuts; 525 ListDesktopShortcutsWithCommandLine(chrome_exe, command_line, false, 526 &shortcuts); 527 528 for (size_t i = 0; i < shortcuts.size(); ++i) { 529 // Use base::DeleteFile() instead of ShellUtil::RemoveShortcuts(), as the 530 // latter causes non-profile taskbar shortcuts to be removed since it 531 // doesn't consider the command-line of the shortcuts it deletes. 532 // TODO(huangs): Refactor with ShellUtil::RemoveShortcuts(). 533 base::win::TaskbarUnpinShortcutLink(shortcuts[i].value().c_str()); 534 base::DeleteFile(shortcuts[i], false); 535 // Notify the shell that the shortcut was deleted to ensure desktop refresh. 536 SHChangeNotify(SHCNE_DELETE, SHCNF_PATH, shortcuts[i].value().c_str(), 537 NULL); 538 } 539 540 // If |ensure_shortcuts_remain| is true and deleting this profile caused the 541 // last shortcuts to be removed, re-create a regular non-profile shortcut. 542 const bool had_shortcuts = !shortcuts.empty(); 543 if (ensure_shortcuts_remain && had_shortcuts && 544 !ChromeDesktopShortcutsExist(chrome_exe)) { 545 BrowserDistribution* distribution = BrowserDistribution::GetDistribution(); 546 // Ensure that the distribution supports creating shortcuts. If it doesn't, 547 // the following code may result in NOTREACHED() being hit. 548 DCHECK(distribution->CanCreateDesktopShortcuts()); 549 installer::Product product(distribution); 550 551 ShellUtil::ShortcutProperties properties(ShellUtil::CURRENT_USER); 552 product.AddDefaultShortcutProperties(chrome_exe, &properties); 553 properties.set_shortcut_name( 554 profiles::internal::GetShortcutFilenameForProfile(base::string16(), 555 distribution)); 556 ShellUtil::CreateOrUpdateShortcut( 557 ShellUtil::SHORTCUT_LOCATION_DESKTOP, distribution, properties, 558 ShellUtil::SHELL_SHORTCUT_CREATE_IF_NO_SYSTEM_LEVEL); 559 } 560} 561 562// Returns true if profile at |profile_path| has any shortcuts. Does not 563// consider non-profile shortcuts. Must be called on the FILE thread. 564bool HasAnyProfileShortcuts(const base::FilePath& profile_path) { 565 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 566 567 base::FilePath chrome_exe; 568 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) { 569 NOTREACHED(); 570 return false; 571 } 572 573 const base::string16 command_line = 574 profiles::internal::CreateProfileShortcutFlags(profile_path); 575 std::vector<base::FilePath> shortcuts; 576 ListDesktopShortcutsWithCommandLine(chrome_exe, command_line, false, 577 &shortcuts); 578 return !shortcuts.empty(); 579} 580 581// Replaces any reserved characters with spaces, and trims the resulting string 582// to prevent any leading and trailing spaces. Also makes sure that the 583// resulting filename doesn't exceed |kMaxProfileShortcutFileNameLength|. 584// TODO(macourteau): find a way to limit the total path's length to MAX_PATH 585// instead of limiting the profile's name to |kMaxProfileShortcutFileNameLength| 586// characters. 587base::string16 SanitizeShortcutProfileNameString( 588 const base::string16& profile_name) { 589 base::string16 sanitized = profile_name; 590 size_t pos = sanitized.find_first_of(kReservedCharacters); 591 while (pos != base::string16::npos) { 592 sanitized[pos] = L' '; 593 pos = sanitized.find_first_of(kReservedCharacters, pos + 1); 594 } 595 596 base::TrimWhitespace(sanitized, base::TRIM_LEADING, &sanitized); 597 if (sanitized.size() > kMaxProfileShortcutFileNameLength) 598 sanitized.erase(kMaxProfileShortcutFileNameLength); 599 base::TrimWhitespace(sanitized, base::TRIM_TRAILING, &sanitized); 600 601 return sanitized; 602} 603 604// Returns a copied SkBitmap for the given resource id that can be safely passed 605// to another thread. 606SkBitmap GetImageResourceSkBitmapCopy(int resource_id) { 607 const gfx::Image image = 608 ResourceBundle::GetSharedInstance().GetNativeImageNamed(resource_id); 609 DCHECK(!image.IsEmpty()); 610 611 const SkBitmap* image_bitmap = image.ToSkBitmap(); 612 SkBitmap bitmap_copy; 613 image_bitmap->deepCopyTo(&bitmap_copy); 614 return bitmap_copy; 615} 616 617} // namespace 618 619namespace profiles { 620namespace internal { 621 622base::FilePath GetProfileIconPath(const base::FilePath& profile_path) { 623 return profile_path.AppendASCII(kProfileIconFileName); 624} 625 626base::string16 GetShortcutFilenameForProfile( 627 const base::string16& profile_name, 628 BrowserDistribution* distribution) { 629 base::string16 shortcut_name; 630 if (!profile_name.empty()) { 631 shortcut_name.append(SanitizeShortcutProfileNameString(profile_name)); 632 shortcut_name.append(L" - "); 633 shortcut_name.append(l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME)); 634 } else { 635 shortcut_name.append( 636 distribution->GetShortcutName(BrowserDistribution::SHORTCUT_CHROME)); 637 } 638 return shortcut_name + installer::kLnkExt; 639} 640 641base::string16 CreateProfileShortcutFlags(const base::FilePath& profile_path) { 642 return base::StringPrintf(L"--%ls=\"%ls\"", 643 base::ASCIIToUTF16( 644 switches::kProfileDirectory).c_str(), 645 profile_path.BaseName().value().c_str()); 646} 647 648} // namespace internal 649} // namespace profiles 650 651// static 652bool ProfileShortcutManager::IsFeatureEnabled() { 653 CommandLine* command_line = CommandLine::ForCurrentProcess(); 654 return command_line->HasSwitch(switches::kEnableProfileShortcutManager) || 655 (BrowserDistribution::GetDistribution()->CanCreateDesktopShortcuts() && 656 !command_line->HasSwitch(switches::kUserDataDir)); 657} 658 659// static 660ProfileShortcutManager* ProfileShortcutManager::Create( 661 ProfileManager* manager) { 662 return new ProfileShortcutManagerWin(manager); 663} 664 665ProfileShortcutManagerWin::ProfileShortcutManagerWin(ProfileManager* manager) 666 : profile_manager_(manager) { 667 DCHECK_EQ( 668 arraysize(kProfileAvatarIconResources2x), 669 profile_manager_->GetProfileInfoCache().GetDefaultAvatarIconCount()); 670 671 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CREATED, 672 content::NotificationService::AllSources()); 673 674 profile_manager_->GetProfileInfoCache().AddObserver(this); 675} 676 677ProfileShortcutManagerWin::~ProfileShortcutManagerWin() { 678 profile_manager_->GetProfileInfoCache().RemoveObserver(this); 679} 680 681void ProfileShortcutManagerWin::CreateOrUpdateProfileIcon( 682 const base::FilePath& profile_path) { 683 CreateOrUpdateShortcutsForProfileAtPath(profile_path, 684 CREATE_OR_UPDATE_ICON_ONLY, 685 IGNORE_NON_PROFILE_SHORTCUTS); 686} 687 688void ProfileShortcutManagerWin::CreateProfileShortcut( 689 const base::FilePath& profile_path) { 690 CreateOrUpdateShortcutsForProfileAtPath(profile_path, CREATE_WHEN_NONE_FOUND, 691 IGNORE_NON_PROFILE_SHORTCUTS); 692} 693 694void ProfileShortcutManagerWin::RemoveProfileShortcuts( 695 const base::FilePath& profile_path) { 696 BrowserThread::PostTask( 697 BrowserThread::FILE, FROM_HERE, 698 base::Bind(&DeleteDesktopShortcuts, profile_path, false)); 699} 700 701void ProfileShortcutManagerWin::HasProfileShortcuts( 702 const base::FilePath& profile_path, 703 const base::Callback<void(bool)>& callback) { 704 BrowserThread::PostTaskAndReplyWithResult( 705 BrowserThread::FILE, FROM_HERE, 706 base::Bind(&HasAnyProfileShortcuts, profile_path), callback); 707} 708 709void ProfileShortcutManagerWin::GetShortcutProperties( 710 const base::FilePath& profile_path, 711 CommandLine* command_line, 712 base::string16* name, 713 base::FilePath* icon_path) { 714 base::FilePath chrome_exe; 715 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) { 716 NOTREACHED(); 717 return; 718 } 719 720 const ProfileInfoCache& cache = profile_manager_->GetProfileInfoCache(); 721 size_t profile_index = cache.GetIndexOfProfileWithPath(profile_path); 722 DCHECK_LT(profile_index, cache.GetNumberOfProfiles()); 723 724 // The used profile name should be empty if there is only 1 profile. 725 base::string16 shortcut_profile_name; 726 if (cache.GetNumberOfProfiles() > 1) 727 shortcut_profile_name = cache.GetNameOfProfileAtIndex(profile_index); 728 729 *name = base::FilePath(profiles::internal::GetShortcutFilenameForProfile( 730 shortcut_profile_name, 731 BrowserDistribution::GetDistribution())).RemoveExtension().value(); 732 733 command_line->ParseFromString(L"\"" + chrome_exe.value() + L"\" " + 734 profiles::internal::CreateProfileShortcutFlags(profile_path)); 735 736 *icon_path = profiles::internal::GetProfileIconPath(profile_path); 737} 738 739void ProfileShortcutManagerWin::OnProfileAdded( 740 const base::FilePath& profile_path) { 741 CreateOrUpdateProfileIcon(profile_path); 742 if (profile_manager_->GetProfileInfoCache().GetNumberOfProfiles() == 2) { 743 // When the second profile is added, make existing non-profile shortcuts 744 // point to the first profile and be badged/named appropriately. 745 CreateOrUpdateShortcutsForProfileAtPath(GetOtherProfilePath(profile_path), 746 UPDATE_EXISTING_ONLY, 747 UPDATE_NON_PROFILE_SHORTCUTS); 748 } 749} 750 751void ProfileShortcutManagerWin::OnProfileWasRemoved( 752 const base::FilePath& profile_path, 753 const base::string16& profile_name) { 754 const ProfileInfoCache& cache = profile_manager_->GetProfileInfoCache(); 755 // If there is only one profile remaining, remove the badging information 756 // from an existing shortcut. 757 const bool deleting_down_to_last_profile = (cache.GetNumberOfProfiles() == 1); 758 if (deleting_down_to_last_profile) { 759 // This is needed to unbadge the icon. 760 CreateOrUpdateShortcutsForProfileAtPath(cache.GetPathOfProfileAtIndex(0), 761 UPDATE_EXISTING_ONLY, 762 IGNORE_NON_PROFILE_SHORTCUTS); 763 } 764 765 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 766 base::Bind(&DeleteDesktopShortcuts, 767 profile_path, 768 deleting_down_to_last_profile)); 769} 770 771void ProfileShortcutManagerWin::OnProfileNameChanged( 772 const base::FilePath& profile_path, 773 const base::string16& old_profile_name) { 774 CreateOrUpdateShortcutsForProfileAtPath(profile_path, UPDATE_EXISTING_ONLY, 775 IGNORE_NON_PROFILE_SHORTCUTS); 776} 777 778void ProfileShortcutManagerWin::OnProfileAvatarChanged( 779 const base::FilePath& profile_path) { 780 CreateOrUpdateProfileIcon(profile_path); 781} 782 783base::FilePath ProfileShortcutManagerWin::GetOtherProfilePath( 784 const base::FilePath& profile_path) { 785 const ProfileInfoCache& cache = profile_manager_->GetProfileInfoCache(); 786 DCHECK_EQ(2U, cache.GetNumberOfProfiles()); 787 // Get the index of the current profile, in order to find the index of the 788 // other profile. 789 size_t current_profile_index = cache.GetIndexOfProfileWithPath(profile_path); 790 size_t other_profile_index = (current_profile_index == 0) ? 1 : 0; 791 return cache.GetPathOfProfileAtIndex(other_profile_index); 792} 793 794void ProfileShortcutManagerWin::CreateOrUpdateShortcutsForProfileAtPath( 795 const base::FilePath& profile_path, 796 CreateOrUpdateMode create_mode, 797 NonProfileShortcutAction action) { 798 DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::UI) || 799 BrowserThread::CurrentlyOn(BrowserThread::UI)); 800 CreateOrUpdateShortcutsParams params(profile_path, create_mode, action); 801 802 ProfileInfoCache* cache = &profile_manager_->GetProfileInfoCache(); 803 size_t profile_index = cache->GetIndexOfProfileWithPath(profile_path); 804 if (profile_index == std::string::npos) 805 return; 806 bool remove_badging = cache->GetNumberOfProfiles() == 1; 807 808 params.old_profile_name = 809 cache->GetShortcutNameOfProfileAtIndex(profile_index); 810 811 // Exit early if the mode is to update existing profile shortcuts only and 812 // none were ever created for this profile, per the shortcut name not being 813 // set in the profile info cache. 814 if (params.old_profile_name.empty() && 815 create_mode == UPDATE_EXISTING_ONLY && 816 action == IGNORE_NON_PROFILE_SHORTCUTS) { 817 return; 818 } 819 820 if (!remove_badging) { 821 params.profile_name = cache->GetNameOfProfileAtIndex(profile_index); 822 823 const size_t icon_index = 824 cache->GetAvatarIconIndexOfProfileAtIndex(profile_index); 825 const int resource_id_1x = 826 cache->GetDefaultAvatarIconResourceIDAtIndex(icon_index); 827 const int resource_id_2x = kProfileAvatarIconResources2x[icon_index]; 828 // Make a copy of the SkBitmaps to ensure that we can safely use the image 829 // data on the FILE thread. 830 params.avatar_image_1x = GetImageResourceSkBitmapCopy(resource_id_1x); 831 params.avatar_image_2x = GetImageResourceSkBitmapCopy(resource_id_2x); 832 } 833 BrowserThread::PostTask( 834 BrowserThread::FILE, FROM_HERE, 835 base::Bind(&CreateOrUpdateDesktopShortcutsAndIconForProfile, params)); 836 837 cache->SetShortcutNameOfProfileAtIndex(profile_index, 838 params.profile_name); 839} 840 841void ProfileShortcutManagerWin::Observe( 842 int type, 843 const content::NotificationSource& source, 844 const content::NotificationDetails& details) { 845 switch (type) { 846 // This notification is triggered when a profile is loaded. 847 case chrome::NOTIFICATION_PROFILE_CREATED: { 848 Profile* profile = 849 content::Source<Profile>(source).ptr()->GetOriginalProfile(); 850 if (profile->GetPrefs()->GetInteger(prefs::kProfileIconVersion) < 851 kCurrentProfileIconVersion) { 852 // Ensure the profile's icon file has been created. 853 CreateOrUpdateProfileIcon(profile->GetPath()); 854 } 855 break; 856 } 857 default: 858 NOTREACHED(); 859 break; 860 } 861} 862