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