profile_shortcut_manager_win.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
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/path_service.h" 16#include "base/string16.h" 17#include "base/string_util.h" 18#include "base/stringprintf.h" 19#include "base/utf_string_conversions.h" 20#include "base/win/shortcut.h" 21#include "chrome/browser/app_icon_win.h" 22#include "chrome/browser/browser_process.h" 23#include "chrome/browser/profiles/profile_info_cache_observer.h" 24#include "chrome/browser/profiles/profile_info_util.h" 25#include "chrome/browser/profiles/profile_manager.h" 26#include "chrome/common/chrome_switches.h" 27#include "chrome/installer/util/browser_distribution.h" 28#include "chrome/installer/util/product.h" 29#include "chrome/installer/util/shell_util.h" 30#include "content/public/browser/browser_thread.h" 31#include "grit/chrome_unscaled_resources.h" 32#include "grit/chromium_strings.h" 33#include "skia/ext/image_operations.h" 34#include "skia/ext/platform_canvas.h" 35#include "ui/base/l10n/l10n_util.h" 36#include "ui/base/resource/resource_bundle.h" 37#include "ui/gfx/icon_util.h" 38#include "ui/gfx/image/image.h" 39#include "ui/gfx/image/image_family.h" 40#include "ui/gfx/rect.h" 41#include "ui/gfx/skia_util.h" 42 43using content::BrowserThread; 44 45namespace { 46 47// Characters that are not allowed in Windows filenames. Taken from 48// http://msdn.microsoft.com/en-us/library/aa365247.aspx 49const char16 kReservedCharacters[] = L"<>:\"/\\|?*\x01\x02\x03\x04\x05\x06\x07" 50 L"\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19" 51 L"\x1A\x1B\x1C\x1D\x1E\x1F"; 52 53// The maximum number of characters allowed in profile shortcuts' file names. 54// Warning: migration code will be needed if this is changed later, since 55// existing shortcuts might no longer be found if the name is generated 56// differently than it was when a shortcut was originally created. 57const int kMaxProfileShortcutFileNameLength = 64; 58 59const int kProfileAvatarBadgeSize = 28; 60const int kShortcutIconSize = 48; 61 62// 2x sized profile avatar icons. Mirrors |kDefaultAvatarIconResources| in 63// profile_info_cache.cc. 64const int kProfileAvatarIconResources2x[] = { 65 IDR_PROFILE_AVATAR_2X_0, 66 IDR_PROFILE_AVATAR_2X_1, 67 IDR_PROFILE_AVATAR_2X_2, 68 IDR_PROFILE_AVATAR_2X_3, 69 IDR_PROFILE_AVATAR_2X_4, 70 IDR_PROFILE_AVATAR_2X_5, 71 IDR_PROFILE_AVATAR_2X_6, 72 IDR_PROFILE_AVATAR_2X_7, 73 IDR_PROFILE_AVATAR_2X_8, 74 IDR_PROFILE_AVATAR_2X_9, 75 IDR_PROFILE_AVATAR_2X_10, 76 IDR_PROFILE_AVATAR_2X_11, 77 IDR_PROFILE_AVATAR_2X_12, 78 IDR_PROFILE_AVATAR_2X_13, 79 IDR_PROFILE_AVATAR_2X_14, 80 IDR_PROFILE_AVATAR_2X_15, 81 IDR_PROFILE_AVATAR_2X_16, 82 IDR_PROFILE_AVATAR_2X_17, 83 IDR_PROFILE_AVATAR_2X_18, 84 IDR_PROFILE_AVATAR_2X_19, 85 IDR_PROFILE_AVATAR_2X_20, 86 IDR_PROFILE_AVATAR_2X_21, 87 IDR_PROFILE_AVATAR_2X_22, 88 IDR_PROFILE_AVATAR_2X_23, 89 IDR_PROFILE_AVATAR_2X_24, 90 IDR_PROFILE_AVATAR_2X_25, 91}; 92 93// Badges |app_icon_bitmap| with |avatar_bitmap| at the bottom right corner and 94// returns the resulting SkBitmap. 95SkBitmap BadgeIcon(const SkBitmap& app_icon_bitmap, 96 const SkBitmap& avatar_bitmap, 97 int scale_factor) { 98 // TODO(rlp): Share this chunk of code with 99 // avatar_menu_button::DrawTaskBarDecoration. 100 SkBitmap source_bitmap = avatar_bitmap; 101 if ((avatar_bitmap.width() == scale_factor * profiles::kAvatarIconWidth) && 102 (avatar_bitmap.height() == scale_factor * profiles::kAvatarIconHeight)) { 103 // Shave a couple of columns so the bitmap is more square. So when 104 // resized to a square aspect ratio it looks pretty. 105 gfx::Rect frame(scale_factor * profiles::kAvatarIconWidth, 106 scale_factor * profiles::kAvatarIconHeight); 107 frame.Inset(scale_factor * 2, 0, scale_factor * 2, 0); 108 avatar_bitmap.extractSubset(&source_bitmap, gfx::RectToSkIRect(frame)); 109 } else { 110 NOTREACHED(); 111 } 112 int avatar_badge_size = kProfileAvatarBadgeSize; 113 if (app_icon_bitmap.width() != kShortcutIconSize) { 114 avatar_badge_size = 115 app_icon_bitmap.width() * kProfileAvatarBadgeSize / kShortcutIconSize; 116 } 117 SkBitmap sk_icon = skia::ImageOperations::Resize( 118 source_bitmap, skia::ImageOperations::RESIZE_LANCZOS3, avatar_badge_size, 119 source_bitmap.height() * avatar_badge_size / source_bitmap.width()); 120 121 // Overlay the avatar on the icon, anchoring it to the bottom-right of the 122 // icon. 123 scoped_ptr<SkCanvas> offscreen_canvas( 124 skia::CreateBitmapCanvas(app_icon_bitmap.width(), 125 app_icon_bitmap.height(), 126 false)); 127 DCHECK(offscreen_canvas.get()); 128 offscreen_canvas->drawBitmap(app_icon_bitmap, 0, 0); 129 offscreen_canvas->drawBitmap(sk_icon, 130 app_icon_bitmap.width() - sk_icon.width(), 131 app_icon_bitmap.height() - sk_icon.height()); 132 const SkBitmap& badged_bitmap = 133 offscreen_canvas->getDevice()->accessBitmap(false); 134 SkBitmap badged_bitmap_copy; 135 badged_bitmap.deepCopyTo(&badged_bitmap_copy, badged_bitmap.getConfig()); 136 return badged_bitmap_copy; 137} 138 139// Creates a desktop shortcut icon file (.ico) on the disk for a given profile, 140// badging the browser distribution icon with the profile avatar. 141// Returns a path to the shortcut icon file on disk, which is empty if this 142// fails. Use index 0 when assigning the resulting file as the icon. 143base::FilePath CreateChromeDesktopShortcutIconForProfile( 144 const base::FilePath& profile_path, 145 const SkBitmap& avatar_bitmap_1x, 146 const SkBitmap& avatar_bitmap_2x) { 147 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 148 scoped_ptr<SkBitmap> app_icon_bitmap(GetAppIconForSize(kShortcutIconSize)); 149 if (!app_icon_bitmap.get()) 150 return base::FilePath(); 151 152 gfx::ImageFamily badged_bitmaps; 153 badged_bitmaps.Add(gfx::Image::CreateFrom1xBitmap( 154 BadgeIcon(*app_icon_bitmap, avatar_bitmap_1x, 1))); 155 156 app_icon_bitmap = GetAppIconForSize(IconUtil::kLargeIconSize); 157 if (app_icon_bitmap.get()) { 158 badged_bitmaps.Add(gfx::Image::CreateFrom1xBitmap( 159 BadgeIcon(*app_icon_bitmap, avatar_bitmap_2x, 2))); 160 } 161 162 // Finally, write the .ico file containing this new bitmap. 163 const base::FilePath icon_path = 164 profile_path.AppendASCII(profiles::internal::kProfileIconFileName); 165 if (!IconUtil::CreateIconFileFromImageFamily(badged_bitmaps, icon_path)) 166 return base::FilePath(); 167 168 return icon_path; 169} 170 171// Gets the user and system directories for desktop shortcuts. Parameters may 172// be NULL if a directory type is not needed. Returns true on success. 173bool GetDesktopShortcutsDirectories( 174 base::FilePath* user_shortcuts_directory, 175 base::FilePath* system_shortcuts_directory) { 176 BrowserDistribution* distribution = BrowserDistribution::GetDistribution(); 177 if (user_shortcuts_directory && 178 !ShellUtil::GetShortcutPath(ShellUtil::SHORTCUT_LOCATION_DESKTOP, 179 distribution, ShellUtil::CURRENT_USER, 180 user_shortcuts_directory)) { 181 NOTREACHED(); 182 return false; 183 } 184 if (system_shortcuts_directory && 185 !ShellUtil::GetShortcutPath(ShellUtil::SHORTCUT_LOCATION_DESKTOP, 186 distribution, ShellUtil::SYSTEM_LEVEL, 187 system_shortcuts_directory)) { 188 NOTREACHED(); 189 return false; 190 } 191 return true; 192} 193 194// Returns the long form of |path|, which will expand any shortened components 195// like "foo~2" to their full names. 196base::FilePath ConvertToLongPath(const base::FilePath& path) { 197 const size_t length = GetLongPathName(path.value().c_str(), NULL, 0); 198 if (length != 0 && length != path.value().length()) { 199 std::vector<wchar_t> long_path(length); 200 if (GetLongPathName(path.value().c_str(), &long_path[0], length) != 0) 201 return base::FilePath(&long_path[0]); 202 } 203 return path; 204} 205 206// Returns true if the file at |path| is a Chrome shortcut and returns its 207// command line in output parameter |command_line|. 208bool IsChromeShortcut(const base::FilePath& path, 209 const base::FilePath& chrome_exe, 210 string16* command_line) { 211 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 212 213 if (path.Extension() != installer::kLnkExt) 214 return false; 215 216 base::FilePath target_path; 217 if (!base::win::ResolveShortcut(path, &target_path, command_line)) 218 return false; 219 // One of the paths may be in short (elided) form. Compare long paths to 220 // ensure these are still properly matched. 221 return ConvertToLongPath(target_path) == ConvertToLongPath(chrome_exe); 222} 223 224// Populates |paths| with the file paths of Chrome desktop shortcuts that have 225// the specified |command_line|. If |include_empty_command_lines| is true, 226// Chrome desktop shortcuts with empty command lines will also be included. 227void ListDesktopShortcutsWithCommandLine(const base::FilePath& chrome_exe, 228 const string16& command_line, 229 bool include_empty_command_lines, 230 std::vector<base::FilePath>* paths) { 231 base::FilePath user_shortcuts_directory; 232 if (!GetDesktopShortcutsDirectories(&user_shortcuts_directory, NULL)) 233 return; 234 235 file_util::FileEnumerator enumerator(user_shortcuts_directory, false, 236 file_util::FileEnumerator::FILES); 237 for (base::FilePath path = enumerator.Next(); !path.empty(); 238 path = enumerator.Next()) { 239 string16 shortcut_command_line; 240 if (!IsChromeShortcut(path, chrome_exe, &shortcut_command_line)) 241 continue; 242 243 // TODO(asvitkine): Change this to build a CommandLine object and ensure all 244 // args from |command_line| are present in the shortcut's CommandLine. This 245 // will be more robust when |command_line| contains multiple args. 246 if ((shortcut_command_line.empty() && include_empty_command_lines) || 247 (shortcut_command_line.find(command_line) != string16::npos)) { 248 paths->push_back(path); 249 } 250 } 251} 252 253// Renames the given desktop shortcut and informs the shell of this change. 254bool RenameDesktopShortcut(const base::FilePath& old_shortcut_path, 255 const base::FilePath& new_shortcut_path) { 256 if (!file_util::Move(old_shortcut_path, new_shortcut_path)) 257 return false; 258 259 // Notify the shell of the rename, which allows the icon to keep its position 260 // on the desktop when renamed. Note: This only works if either SHCNF_FLUSH or 261 // SHCNF_FLUSHNOWAIT is specified as a flag. 262 SHChangeNotify(SHCNE_RENAMEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT, 263 old_shortcut_path.value().c_str(), 264 new_shortcut_path.value().c_str()); 265 return true; 266} 267 268// Renames an existing Chrome desktop profile shortcut. Must be called on the 269// FILE thread. 270void RenameChromeDesktopShortcutForProfile( 271 const string16& old_shortcut_filename, 272 const string16& new_shortcut_filename) { 273 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 274 275 base::FilePath user_shortcuts_directory; 276 base::FilePath system_shortcuts_directory; 277 if (!GetDesktopShortcutsDirectories(&user_shortcuts_directory, 278 &system_shortcuts_directory)) { 279 return; 280 } 281 282 const base::FilePath old_shortcut_path = 283 user_shortcuts_directory.Append(old_shortcut_filename); 284 const base::FilePath new_shortcut_path = 285 user_shortcuts_directory.Append(new_shortcut_filename); 286 287 if (file_util::PathExists(old_shortcut_path)) { 288 // Rename the old shortcut unless a system-level shortcut exists at the 289 // destination, in which case the old shortcut is simply deleted. 290 const base::FilePath possible_new_system_shortcut = 291 system_shortcuts_directory.Append(new_shortcut_filename); 292 if (file_util::PathExists(possible_new_system_shortcut)) 293 file_util::Delete(old_shortcut_path, false); 294 else if (!RenameDesktopShortcut(old_shortcut_path, new_shortcut_path)) 295 DLOG(ERROR) << "Could not rename Windows profile desktop shortcut."; 296 } else { 297 // If the shortcut does not exist, it may have been renamed by the user. In 298 // that case, its name should not be changed. 299 // It's also possible that a system-level shortcut exists instead - this 300 // should only be the case for the original Chrome shortcut from an 301 // installation. If that's the case, copy that one over - it will get its 302 // properties updated by |CreateOrUpdateDesktopShortcutsForProfile()|. 303 const base::FilePath possible_old_system_shortcut = 304 system_shortcuts_directory.Append(old_shortcut_filename); 305 if (file_util::PathExists(possible_old_system_shortcut)) 306 file_util::CopyFile(possible_old_system_shortcut, new_shortcut_path); 307 } 308} 309 310// Updates all desktop shortcuts for the given profile to have the specified 311// parameters. If |create_mode| is CREATE_WHEN_NONE_FOUND, a new shortcut is 312// created if no existing ones were found. Whether non-profile shortcuts should 313// be updated is specified by |action|. Must be called on the FILE thread. 314void CreateOrUpdateDesktopShortcutsForProfile( 315 const base::FilePath& profile_path, 316 const string16& old_profile_name, 317 const string16& profile_name, 318 const SkBitmap& avatar_image_1x, 319 const SkBitmap& avatar_image_2x, 320 ProfileShortcutManagerWin::CreateOrUpdateMode create_mode, 321 ProfileShortcutManagerWin::NonProfileShortcutAction action) { 322 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 323 324 base::FilePath chrome_exe; 325 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) { 326 NOTREACHED(); 327 return; 328 } 329 330 BrowserDistribution* distribution = BrowserDistribution::GetDistribution(); 331 // Ensure that the distribution supports creating shortcuts. If it doesn't, 332 // the following code may result in NOTREACHED() being hit. 333 DCHECK(distribution->CanCreateDesktopShortcuts()); 334 335 if (old_profile_name != profile_name) { 336 const string16 old_shortcut_filename = 337 profiles::internal::GetShortcutFilenameForProfile(old_profile_name, 338 distribution); 339 const string16 new_shortcut_filename = 340 profiles::internal::GetShortcutFilenameForProfile(profile_name, 341 distribution); 342 RenameChromeDesktopShortcutForProfile(old_shortcut_filename, 343 new_shortcut_filename); 344 } 345 346 ShellUtil::ShortcutProperties properties(ShellUtil::CURRENT_USER); 347 installer::Product product(distribution); 348 product.AddDefaultShortcutProperties(chrome_exe, &properties); 349 350 const string16 command_line = 351 profiles::internal::CreateProfileShortcutFlags(profile_path); 352 353 // Only set the profile-specific properties when |profile_name| is non empty. 354 // If it is empty, it means the shortcut being created should be a regular, 355 // non-profile Chrome shortcut. 356 if (!profile_name.empty()) { 357 const base::FilePath shortcut_icon = 358 CreateChromeDesktopShortcutIconForProfile(profile_path, 359 avatar_image_1x, 360 avatar_image_2x); 361 if (!shortcut_icon.empty()) 362 properties.set_icon(shortcut_icon, 0); 363 properties.set_arguments(command_line); 364 } else { 365 // Set the arguments explicitly to the empty string to ensure that 366 // |ShellUtil::CreateOrUpdateShortcut| updates that part of the shortcut. 367 properties.set_arguments(string16()); 368 } 369 370 ShellUtil::ShortcutOperation operation = 371 ShellUtil::SHELL_SHORTCUT_REPLACE_EXISTING; 372 373 std::vector<base::FilePath> shortcuts; 374 ListDesktopShortcutsWithCommandLine(chrome_exe, command_line, 375 action == ProfileShortcutManagerWin::UPDATE_NON_PROFILE_SHORTCUTS, 376 &shortcuts); 377 if (create_mode == ProfileShortcutManagerWin::CREATE_WHEN_NONE_FOUND && 378 shortcuts.empty()) { 379 const string16 shortcut_name = 380 profiles::internal::GetShortcutFilenameForProfile(profile_name, 381 distribution); 382 shortcuts.push_back(base::FilePath(shortcut_name)); 383 operation = ShellUtil::SHELL_SHORTCUT_CREATE_IF_NO_SYSTEM_LEVEL; 384 } 385 386 for (size_t i = 0; i < shortcuts.size(); ++i) { 387 const base::FilePath shortcut_name = 388 shortcuts[i].BaseName().RemoveExtension(); 389 properties.set_shortcut_name(shortcut_name.value()); 390 ShellUtil::CreateOrUpdateShortcut(ShellUtil::SHORTCUT_LOCATION_DESKTOP, 391 distribution, properties, operation); 392 } 393} 394 395// Returns true if any desktop shortcuts exist with target |chrome_exe|, 396// regardless of their command line arguments. 397bool ChromeDesktopShortcutsExist(const base::FilePath& chrome_exe) { 398 base::FilePath user_shortcuts_directory; 399 if (!GetDesktopShortcutsDirectories(&user_shortcuts_directory, NULL)) 400 return false; 401 402 file_util::FileEnumerator enumerator(user_shortcuts_directory, false, 403 file_util::FileEnumerator::FILES); 404 for (base::FilePath path = enumerator.Next(); !path.empty(); 405 path = enumerator.Next()) { 406 if (IsChromeShortcut(path, chrome_exe, NULL)) 407 return true; 408 } 409 410 return false; 411} 412 413// Deletes all desktop shortcuts for the specified profile and also removes the 414// corresponding icon file. If |ensure_shortcuts_remain| is true, then a regular 415// non-profile shortcut will be created if this function would otherwise delete 416// the last Chrome desktop shortcut(s). Must be called on the FILE thread. 417void DeleteDesktopShortcutsAndIconFile(const base::FilePath& profile_path, 418 bool ensure_shortcuts_remain) { 419 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 420 421 base::FilePath chrome_exe; 422 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) { 423 NOTREACHED(); 424 return; 425 } 426 427 const string16 command_line = 428 profiles::internal::CreateProfileShortcutFlags(profile_path); 429 std::vector<base::FilePath> shortcuts; 430 ListDesktopShortcutsWithCommandLine(chrome_exe, command_line, false, 431 &shortcuts); 432 433 BrowserDistribution* distribution = BrowserDistribution::GetDistribution(); 434 for (size_t i = 0; i < shortcuts.size(); ++i) { 435 // Use file_util::Delete() instead of ShellUtil::RemoveShortcut(), as the 436 // latter causes non-profile taskbar shortcuts to be unpinned. 437 file_util::Delete(shortcuts[i], false); 438 // Notify the shell that the shortcut was deleted to ensure desktop refresh. 439 SHChangeNotify(SHCNE_DELETE, SHCNF_PATH, shortcuts[i].value().c_str(), 440 NULL); 441 } 442 443 const base::FilePath icon_path = 444 profile_path.AppendASCII(profiles::internal::kProfileIconFileName); 445 file_util::Delete(icon_path, false); 446 447 // If |ensure_shortcuts_remain| is true and deleting this profile caused the 448 // last shortcuts to be removed, re-create a regular non-profile shortcut. 449 const bool had_shortcuts = !shortcuts.empty(); 450 if (ensure_shortcuts_remain && had_shortcuts && 451 !ChromeDesktopShortcutsExist(chrome_exe)) { 452 BrowserDistribution* distribution = BrowserDistribution::GetDistribution(); 453 // Ensure that the distribution supports creating shortcuts. If it doesn't, 454 // the following code may result in NOTREACHED() being hit. 455 DCHECK(distribution->CanCreateDesktopShortcuts()); 456 installer::Product product(distribution); 457 458 ShellUtil::ShortcutProperties properties(ShellUtil::CURRENT_USER); 459 product.AddDefaultShortcutProperties(chrome_exe, &properties); 460 properties.set_shortcut_name( 461 profiles::internal::GetShortcutFilenameForProfile(string16(), 462 distribution)); 463 ShellUtil::CreateOrUpdateShortcut( 464 ShellUtil::SHORTCUT_LOCATION_DESKTOP, distribution, properties, 465 ShellUtil::SHELL_SHORTCUT_CREATE_IF_NO_SYSTEM_LEVEL); 466 } 467} 468 469// Returns true if profile at |profile_path| has any shortcuts. Does not 470// consider non-profile shortcuts. Must be called on the FILE thread. 471bool HasAnyProfileShortcuts(const base::FilePath& profile_path) { 472 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 473 474 base::FilePath chrome_exe; 475 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) { 476 NOTREACHED(); 477 return false; 478 } 479 480 const string16 command_line = 481 profiles::internal::CreateProfileShortcutFlags(profile_path); 482 std::vector<base::FilePath> shortcuts; 483 ListDesktopShortcutsWithCommandLine(chrome_exe, command_line, false, 484 &shortcuts); 485 return !shortcuts.empty(); 486} 487 488// Replaces any reserved characters with spaces, and trims the resulting string 489// to prevent any leading and trailing spaces. Also makes sure that the 490// resulting filename doesn't exceed |kMaxProfileShortcutFileNameLength|. 491// TODO(macourteau): find a way to limit the total path's length to MAX_PATH 492// instead of limiting the profile's name to |kMaxProfileShortcutFileNameLength| 493// characters. 494string16 SanitizeShortcutProfileNameString(const string16& profile_name) { 495 string16 sanitized = profile_name; 496 size_t pos = sanitized.find_first_of(kReservedCharacters); 497 while (pos != string16::npos) { 498 sanitized[pos] = L' '; 499 pos = sanitized.find_first_of(kReservedCharacters, pos + 1); 500 } 501 502 TrimWhitespace(sanitized, TRIM_LEADING, &sanitized); 503 if (sanitized.size() > kMaxProfileShortcutFileNameLength) 504 sanitized.erase(kMaxProfileShortcutFileNameLength); 505 TrimWhitespace(sanitized, TRIM_TRAILING, &sanitized); 506 507 return sanitized; 508} 509 510// Returns a copied SkBitmap for the given resource id that can be safely passed 511// to another thread. 512SkBitmap GetImageResourceSkBitmapCopy(int resource_id) { 513 const gfx::Image image = 514 ResourceBundle::GetSharedInstance().GetNativeImageNamed(resource_id); 515 DCHECK(!image.IsEmpty()); 516 517 const SkBitmap* image_bitmap = image.ToSkBitmap(); 518 SkBitmap bitmap_copy; 519 image_bitmap->deepCopyTo(&bitmap_copy, image_bitmap->getConfig()); 520 return bitmap_copy; 521} 522 523} // namespace 524 525namespace profiles { 526namespace internal { 527 528const char kProfileIconFileName[] = "Google Profile.ico"; 529 530string16 GetShortcutFilenameForProfile(const string16& profile_name, 531 BrowserDistribution* distribution) { 532 string16 shortcut_name; 533 if (!profile_name.empty()) { 534 shortcut_name.append(SanitizeShortcutProfileNameString(profile_name)); 535 shortcut_name.append(L" - "); 536 shortcut_name.append(l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME)); 537 } else { 538 shortcut_name.append(distribution->GetAppShortCutName()); 539 } 540 return shortcut_name + installer::kLnkExt; 541} 542 543string16 CreateProfileShortcutFlags(const base::FilePath& profile_path) { 544 return base::StringPrintf(L"--%ls=\"%ls\"", 545 ASCIIToUTF16(switches::kProfileDirectory).c_str(), 546 profile_path.BaseName().value().c_str()); 547} 548 549} // namespace internal 550} // namespace profiles 551 552// static 553bool ProfileShortcutManager::IsFeatureEnabled() { 554 return BrowserDistribution::GetDistribution()->CanCreateDesktopShortcuts() && 555 !CommandLine::ForCurrentProcess()->HasSwitch(switches::kUserDataDir) && 556 !CommandLine::ForCurrentProcess()->HasSwitch(switches::kShowAppList); 557} 558 559// static 560ProfileShortcutManager* ProfileShortcutManager::Create( 561 ProfileManager* manager) { 562 return new ProfileShortcutManagerWin(manager); 563} 564 565ProfileShortcutManagerWin::ProfileShortcutManagerWin(ProfileManager* manager) 566 : profile_manager_(manager) { 567 DCHECK_EQ( 568 arraysize(kProfileAvatarIconResources2x), 569 profile_manager_->GetProfileInfoCache().GetDefaultAvatarIconCount()); 570 571 profile_manager_->GetProfileInfoCache().AddObserver(this); 572} 573 574ProfileShortcutManagerWin::~ProfileShortcutManagerWin() { 575 profile_manager_->GetProfileInfoCache().RemoveObserver(this); 576} 577 578void ProfileShortcutManagerWin::CreateProfileShortcut( 579 const base::FilePath& profile_path) { 580 CreateOrUpdateShortcutsForProfileAtPath(profile_path, CREATE_WHEN_NONE_FOUND, 581 IGNORE_NON_PROFILE_SHORTCUTS); 582} 583 584void ProfileShortcutManagerWin::RemoveProfileShortcuts( 585 const base::FilePath& profile_path) { 586 BrowserThread::PostTask( 587 BrowserThread::FILE, FROM_HERE, 588 base::Bind(&DeleteDesktopShortcutsAndIconFile, profile_path, false)); 589} 590 591void ProfileShortcutManagerWin::HasProfileShortcuts( 592 const base::FilePath& profile_path, 593 const base::Callback<void(bool)>& callback) { 594 BrowserThread::PostTaskAndReplyWithResult( 595 BrowserThread::FILE, FROM_HERE, 596 base::Bind(&HasAnyProfileShortcuts, profile_path), callback); 597} 598 599void ProfileShortcutManagerWin::OnProfileAdded( 600 const base::FilePath& profile_path) { 601 const size_t profile_count = 602 profile_manager_->GetProfileInfoCache().GetNumberOfProfiles(); 603 if (profile_count == 1) { 604 CreateOrUpdateShortcutsForProfileAtPath(profile_path, 605 CREATE_WHEN_NONE_FOUND, 606 UPDATE_NON_PROFILE_SHORTCUTS); 607 } else if (profile_count == 2) { 608 CreateOrUpdateShortcutsForProfileAtPath(GetOtherProfilePath(profile_path), 609 UPDATE_EXISTING_ONLY, 610 UPDATE_NON_PROFILE_SHORTCUTS); 611 } 612} 613 614void ProfileShortcutManagerWin::OnProfileWillBeRemoved( 615 const base::FilePath& profile_path) { 616} 617 618void ProfileShortcutManagerWin::OnProfileWasRemoved( 619 const base::FilePath& profile_path, 620 const string16& profile_name) { 621 const ProfileInfoCache& cache = profile_manager_->GetProfileInfoCache(); 622 // If there is only one profile remaining, remove the badging information 623 // from an existing shortcut. 624 const bool deleting_down_to_last_profile = (cache.GetNumberOfProfiles() == 1); 625 if (deleting_down_to_last_profile) { 626 CreateOrUpdateShortcutsForProfileAtPath(cache.GetPathOfProfileAtIndex(0), 627 UPDATE_EXISTING_ONLY, 628 IGNORE_NON_PROFILE_SHORTCUTS); 629 } 630 631 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 632 base::Bind(&DeleteDesktopShortcutsAndIconFile, 633 profile_path, 634 deleting_down_to_last_profile)); 635} 636 637void ProfileShortcutManagerWin::OnProfileNameChanged( 638 const base::FilePath& profile_path, 639 const string16& old_profile_name) { 640 CreateOrUpdateShortcutsForProfileAtPath(profile_path, UPDATE_EXISTING_ONLY, 641 IGNORE_NON_PROFILE_SHORTCUTS); 642} 643 644void ProfileShortcutManagerWin::OnProfileAvatarChanged( 645 const base::FilePath& profile_path) { 646 CreateOrUpdateShortcutsForProfileAtPath(profile_path, UPDATE_EXISTING_ONLY, 647 IGNORE_NON_PROFILE_SHORTCUTS); 648} 649 650base::FilePath ProfileShortcutManagerWin::GetOtherProfilePath( 651 const base::FilePath& profile_path) { 652 const ProfileInfoCache& cache = profile_manager_->GetProfileInfoCache(); 653 DCHECK_EQ(2U, cache.GetNumberOfProfiles()); 654 // Get the index of the current profile, in order to find the index of the 655 // other profile. 656 size_t current_profile_index = cache.GetIndexOfProfileWithPath(profile_path); 657 size_t other_profile_index = (current_profile_index == 0) ? 1 : 0; 658 return cache.GetPathOfProfileAtIndex(other_profile_index); 659} 660 661void ProfileShortcutManagerWin::CreateOrUpdateShortcutsForProfileAtPath( 662 const base::FilePath& profile_path, 663 CreateOrUpdateMode create_mode, 664 NonProfileShortcutAction action) { 665 ProfileInfoCache* cache = &profile_manager_->GetProfileInfoCache(); 666 size_t profile_index = cache->GetIndexOfProfileWithPath(profile_path); 667 if (profile_index == std::string::npos) 668 return; 669 bool remove_badging = cache->GetNumberOfProfiles() == 1; 670 671 string16 old_shortcut_appended_name = 672 cache->GetShortcutNameOfProfileAtIndex(profile_index); 673 674 // Exit early if the mode is to update existing profile shortcuts only and 675 // none were ever created for this profile, per the shortcut name not being 676 // set in the profile info cache. 677 if (old_shortcut_appended_name.empty() && 678 create_mode == UPDATE_EXISTING_ONLY && 679 action == IGNORE_NON_PROFILE_SHORTCUTS) { 680 return; 681 } 682 683 string16 new_shortcut_appended_name; 684 if (!remove_badging) 685 new_shortcut_appended_name = cache->GetNameOfProfileAtIndex(profile_index); 686 687 SkBitmap avatar_bitmap_copy_1x; 688 SkBitmap avatar_bitmap_copy_2x; 689 if (!remove_badging) { 690 const size_t icon_index = 691 cache->GetAvatarIconIndexOfProfileAtIndex(profile_index); 692 const int resource_id_1x = 693 cache->GetDefaultAvatarIconResourceIDAtIndex(icon_index); 694 const int resource_id_2x = kProfileAvatarIconResources2x[icon_index]; 695 // Make a copy of the SkBitmaps to ensure that we can safely use the image 696 // data on the FILE thread. 697 avatar_bitmap_copy_1x = GetImageResourceSkBitmapCopy(resource_id_1x); 698 avatar_bitmap_copy_2x = GetImageResourceSkBitmapCopy(resource_id_2x); 699 } 700 BrowserThread::PostTask( 701 BrowserThread::FILE, FROM_HERE, 702 base::Bind(&CreateOrUpdateDesktopShortcutsForProfile, profile_path, 703 old_shortcut_appended_name, new_shortcut_appended_name, 704 avatar_bitmap_copy_1x, avatar_bitmap_copy_2x, create_mode, 705 action)); 706 707 cache->SetShortcutNameOfProfileAtIndex(profile_index, 708 new_shortcut_appended_name); 709} 710