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