Home | History | Annotate | Download | only in profiles
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/profiles/profile_shortcut_manager_win.h"
      6 
      7 #include <shlobj.h>  // For SHChangeNotify().
      8 
      9 #include <string>
     10 #include <vector>
     11 
     12 #include "base/bind.h"
     13 #include "base/command_line.h"
     14 #include "base/file_util.h"
     15 #include "base/files/file_enumerator.h"
     16 #include "base/path_service.h"
     17 #include "base/prefs/pref_service.h"
     18 #include "base/strings/string16.h"
     19 #include "base/strings/string_util.h"
     20 #include "base/strings/stringprintf.h"
     21 #include "base/strings/utf_string_conversions.h"
     22 #include "base/win/shortcut.h"
     23 #include "chrome/browser/app_icon_win.h"
     24 #include "chrome/browser/browser_process.h"
     25 #include "chrome/browser/chrome_notification_types.h"
     26 #include "chrome/browser/profiles/profile_info_cache_observer.h"
     27 #include "chrome/browser/profiles/profile_info_util.h"
     28 #include "chrome/browser/profiles/profile_manager.h"
     29 #include "chrome/browser/shell_integration.h"
     30 #include "chrome/common/chrome_switches.h"
     31 #include "chrome/common/pref_names.h"
     32 #include "chrome/installer/util/browser_distribution.h"
     33 #include "chrome/installer/util/product.h"
     34 #include "chrome/installer/util/shell_util.h"
     35 #include "content/public/browser/browser_thread.h"
     36 #include "content/public/browser/notification_service.h"
     37 #include "grit/chrome_unscaled_resources.h"
     38 #include "grit/chromium_strings.h"
     39 #include "skia/ext/image_operations.h"
     40 #include "skia/ext/platform_canvas.h"
     41 #include "ui/base/l10n/l10n_util.h"
     42 #include "ui/base/resource/resource_bundle.h"
     43 #include "ui/gfx/icon_util.h"
     44 #include "ui/gfx/image/image.h"
     45 #include "ui/gfx/image/image_family.h"
     46 #include "ui/gfx/rect.h"
     47 #include "ui/gfx/skia_util.h"
     48 
     49 using content::BrowserThread;
     50 
     51 namespace {
     52 
     53 // Name of the badged icon file generated for a given profile.
     54 const char kProfileIconFileName[] = "Google Profile.ico";
     55 
     56 // Characters that are not allowed in Windows filenames. Taken from
     57 // http://msdn.microsoft.com/en-us/library/aa365247.aspx
     58 const char16 kReservedCharacters[] = L"<>:\"/\\|?*\x01\x02\x03\x04\x05\x06\x07"
     59     L"\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19"
     60     L"\x1A\x1B\x1C\x1D\x1E\x1F";
     61 
     62 // The maximum number of characters allowed in profile shortcuts' file names.
     63 // Warning: migration code will be needed if this is changed later, since
     64 // existing shortcuts might no longer be found if the name is generated
     65 // differently than it was when a shortcut was originally created.
     66 const int kMaxProfileShortcutFileNameLength = 64;
     67 
     68 // 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.
     72 const int kShortcutIconSize = 48;
     73 const int kProfileAvatarBadgeSize = kShortcutIconSize / 2;
     74 
     75 const int kCurrentProfileIconVersion = 2;
     76 
     77 // 2x sized profile avatar icons. Mirrors |kDefaultAvatarIconResources| in
     78 // profile_info_cache.cc.
     79 const 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.
    110 SkBitmap 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   scoped_ptr<SkCanvas> offscreen_canvas(
    139       skia::CreateBitmapCanvas(app_icon_bitmap.width(),
    140                                app_icon_bitmap.height(),
    141                                false));
    142   DCHECK(offscreen_canvas);
    143   offscreen_canvas->drawBitmap(app_icon_bitmap, 0, 0);
    144   offscreen_canvas->drawBitmap(sk_icon,
    145                                app_icon_bitmap.width() - sk_icon.width(),
    146                                app_icon_bitmap.height() - sk_icon.height());
    147   const SkBitmap& badged_bitmap =
    148       offscreen_canvas->getDevice()->accessBitmap(false);
    149   SkBitmap badged_bitmap_copy;
    150   badged_bitmap.deepCopyTo(&badged_bitmap_copy, badged_bitmap.getConfig());
    151   return badged_bitmap_copy;
    152 }
    153 
    154 // Updates the preferences with the current icon version on icon creation
    155 // success.
    156 void OnProfileIconCreateSuccess(base::FilePath profile_path) {
    157   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    158   if (!g_browser_process->profile_manager())
    159     return;
    160   Profile* profile =
    161       g_browser_process->profile_manager()->GetProfileByPath(profile_path);
    162   if (profile) {
    163     profile->GetPrefs()->SetInteger(prefs::kProfileIconVersion,
    164                                     kCurrentProfileIconVersion);
    165   }
    166 }
    167 
    168 // Creates a desktop shortcut icon file (.ico) on the disk for a given profile,
    169 // badging the browser distribution icon with the profile avatar.
    170 // Returns a path to the shortcut icon file on disk, which is empty if this
    171 // fails. Use index 0 when assigning the resulting file as the icon. If both
    172 // given bitmaps are empty, an unbadged icon is created.
    173 // Returns the path to the created icon on success and an empty base::FilePath
    174 // on failure.
    175 // TODO(calamity): Ideally we'd just copy the app icon verbatim from the exe's
    176 // resources in the case of an unbadged icon.
    177 base::FilePath CreateOrUpdateShortcutIconForProfile(
    178     const base::FilePath& profile_path,
    179     const SkBitmap& avatar_bitmap_1x,
    180     const SkBitmap& avatar_bitmap_2x) {
    181   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    182 
    183   if (!base::PathExists(profile_path)) {
    184     LOG(ERROR) << "Profile directory " << profile_path.value()
    185                << " did not exist when trying to create profile icon";
    186     return base::FilePath();
    187   }
    188 
    189   scoped_ptr<SkBitmap> app_icon_bitmap(GetAppIconForSize(kShortcutIconSize));
    190   if (!app_icon_bitmap)
    191     return base::FilePath();
    192 
    193   gfx::ImageFamily badged_bitmaps;
    194   if (!avatar_bitmap_1x.empty()) {
    195     badged_bitmaps.Add(gfx::Image::CreateFrom1xBitmap(
    196         BadgeIcon(*app_icon_bitmap, avatar_bitmap_1x, 1)));
    197   }
    198 
    199   scoped_ptr<SkBitmap> large_app_icon_bitmap(
    200       GetAppIconForSize(IconUtil::kLargeIconSize));
    201   if (large_app_icon_bitmap && !avatar_bitmap_2x.empty()) {
    202     badged_bitmaps.Add(gfx::Image::CreateFrom1xBitmap(
    203         BadgeIcon(*large_app_icon_bitmap, avatar_bitmap_2x, 2)));
    204   }
    205 
    206   // If we have no badged bitmaps, we should just use the default chrome icon.
    207   if (badged_bitmaps.empty()) {
    208     badged_bitmaps.Add(gfx::Image::CreateFrom1xBitmap(*app_icon_bitmap));
    209     if (large_app_icon_bitmap) {
    210       badged_bitmaps.Add(
    211           gfx::Image::CreateFrom1xBitmap(*large_app_icon_bitmap));
    212     }
    213   }
    214   // Finally, write the .ico file containing this new bitmap.
    215   const base::FilePath icon_path =
    216       profiles::internal::GetProfileIconPath(profile_path);
    217   const bool had_icon = base::PathExists(icon_path);
    218 
    219   if (!IconUtil::CreateIconFileFromImageFamily(badged_bitmaps, icon_path)) {
    220     // This can happen in theory if the profile directory is deleted between the
    221     // beginning of this function and here; however this is extremely unlikely
    222     // and this check will help catch any regression where this call would start
    223     // failing constantly.
    224     NOTREACHED();
    225     return base::FilePath();
    226   }
    227 
    228   if (had_icon) {
    229     // This invalidates the Windows icon cache and causes the icon changes to
    230     // register with the taskbar and desktop. SHCNE_ASSOCCHANGED will cause a
    231     // desktop flash and we would like to avoid that if possible.
    232     SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
    233   } else {
    234     SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, icon_path.value().c_str(), NULL);
    235   }
    236   BrowserThread::PostTask(
    237       BrowserThread::UI, FROM_HERE,
    238       base::Bind(&OnProfileIconCreateSuccess, profile_path));
    239   return icon_path;
    240 }
    241 
    242 // Gets the user and system directories for desktop shortcuts. Parameters may
    243 // be NULL if a directory type is not needed. Returns true on success.
    244 bool GetDesktopShortcutsDirectories(
    245     base::FilePath* user_shortcuts_directory,
    246     base::FilePath* system_shortcuts_directory) {
    247   BrowserDistribution* distribution = BrowserDistribution::GetDistribution();
    248   if (user_shortcuts_directory &&
    249       !ShellUtil::GetShortcutPath(ShellUtil::SHORTCUT_LOCATION_DESKTOP,
    250                                   distribution, ShellUtil::CURRENT_USER,
    251                                   user_shortcuts_directory)) {
    252     NOTREACHED();
    253     return false;
    254   }
    255   if (system_shortcuts_directory &&
    256       !ShellUtil::GetShortcutPath(ShellUtil::SHORTCUT_LOCATION_DESKTOP,
    257                                   distribution, ShellUtil::SYSTEM_LEVEL,
    258                                   system_shortcuts_directory)) {
    259     NOTREACHED();
    260     return false;
    261   }
    262   return true;
    263 }
    264 
    265 // Returns the long form of |path|, which will expand any shortened components
    266 // like "foo~2" to their full names.
    267 base::FilePath ConvertToLongPath(const base::FilePath& path) {
    268   const size_t length = GetLongPathName(path.value().c_str(), NULL, 0);
    269   if (length != 0 && length != path.value().length()) {
    270     std::vector<wchar_t> long_path(length);
    271     if (GetLongPathName(path.value().c_str(), &long_path[0], length) != 0)
    272       return base::FilePath(&long_path[0]);
    273   }
    274   return path;
    275 }
    276 
    277 // Returns true if the file at |path| is a Chrome shortcut and returns its
    278 // command line in output parameter |command_line|.
    279 bool IsChromeShortcut(const base::FilePath& path,
    280                       const base::FilePath& chrome_exe,
    281                       base::string16* command_line) {
    282   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    283 
    284   if (path.Extension() != installer::kLnkExt)
    285     return false;
    286 
    287   base::FilePath target_path;
    288   if (!base::win::ResolveShortcut(path, &target_path, command_line))
    289     return false;
    290   // One of the paths may be in short (elided) form. Compare long paths to
    291   // ensure these are still properly matched.
    292   return ConvertToLongPath(target_path) == ConvertToLongPath(chrome_exe);
    293 }
    294 
    295 // Populates |paths| with the file paths of Chrome desktop shortcuts that have
    296 // the specified |command_line|. If |include_empty_command_lines| is true,
    297 // Chrome desktop shortcuts with empty command lines will also be included.
    298 void ListDesktopShortcutsWithCommandLine(const base::FilePath& chrome_exe,
    299                                          const base::string16& command_line,
    300                                          bool include_empty_command_lines,
    301                                          std::vector<base::FilePath>* paths) {
    302   base::FilePath user_shortcuts_directory;
    303   if (!GetDesktopShortcutsDirectories(&user_shortcuts_directory, NULL))
    304     return;
    305 
    306   base::FileEnumerator enumerator(user_shortcuts_directory, false,
    307                                   base::FileEnumerator::FILES);
    308   for (base::FilePath path = enumerator.Next(); !path.empty();
    309        path = enumerator.Next()) {
    310     base::string16 shortcut_command_line;
    311     if (!IsChromeShortcut(path, chrome_exe, &shortcut_command_line))
    312       continue;
    313 
    314     // TODO(asvitkine): Change this to build a CommandLine object and ensure all
    315     // args from |command_line| are present in the shortcut's CommandLine. This
    316     // will be more robust when |command_line| contains multiple args.
    317     if ((shortcut_command_line.empty() && include_empty_command_lines) ||
    318         (shortcut_command_line.find(command_line) != base::string16::npos)) {
    319       paths->push_back(path);
    320     }
    321   }
    322 }
    323 
    324 // Renames the given desktop shortcut and informs the shell of this change.
    325 bool RenameDesktopShortcut(const base::FilePath& old_shortcut_path,
    326                            const base::FilePath& new_shortcut_path) {
    327   if (!base::Move(old_shortcut_path, new_shortcut_path))
    328     return false;
    329 
    330   // Notify the shell of the rename, which allows the icon to keep its position
    331   // on the desktop when renamed. Note: This only works if either SHCNF_FLUSH or
    332   // SHCNF_FLUSHNOWAIT is specified as a flag.
    333   SHChangeNotify(SHCNE_RENAMEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT,
    334                  old_shortcut_path.value().c_str(),
    335                  new_shortcut_path.value().c_str());
    336   return true;
    337 }
    338 
    339 // Renames an existing Chrome desktop profile shortcut. Must be called on the
    340 // FILE thread.
    341 void RenameChromeDesktopShortcutForProfile(
    342     const base::string16& old_shortcut_filename,
    343     const base::string16& new_shortcut_filename) {
    344   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    345 
    346   base::FilePath user_shortcuts_directory;
    347   base::FilePath system_shortcuts_directory;
    348   if (!GetDesktopShortcutsDirectories(&user_shortcuts_directory,
    349                                       &system_shortcuts_directory)) {
    350     return;
    351   }
    352 
    353   const base::FilePath old_shortcut_path =
    354       user_shortcuts_directory.Append(old_shortcut_filename);
    355   const base::FilePath new_shortcut_path =
    356       user_shortcuts_directory.Append(new_shortcut_filename);
    357 
    358   if (base::PathExists(old_shortcut_path)) {
    359     // Rename the old shortcut unless a system-level shortcut exists at the
    360     // destination, in which case the old shortcut is simply deleted.
    361     const base::FilePath possible_new_system_shortcut =
    362         system_shortcuts_directory.Append(new_shortcut_filename);
    363     if (base::PathExists(possible_new_system_shortcut))
    364       base::DeleteFile(old_shortcut_path, false);
    365     else if (!RenameDesktopShortcut(old_shortcut_path, new_shortcut_path))
    366       DLOG(ERROR) << "Could not rename Windows profile desktop shortcut.";
    367   } else {
    368     // If the shortcut does not exist, it may have been renamed by the user. In
    369     // that case, its name should not be changed.
    370     // It's also possible that a system-level shortcut exists instead - this
    371     // should only be the case for the original Chrome shortcut from an
    372     // installation. If that's the case, copy that one over - it will get its
    373     // properties updated by
    374     // |CreateOrUpdateDesktopShortcutsAndIconForProfile()|.
    375     const base::FilePath possible_old_system_shortcut =
    376         system_shortcuts_directory.Append(old_shortcut_filename);
    377     if (base::PathExists(possible_old_system_shortcut))
    378       base::CopyFile(possible_old_system_shortcut, new_shortcut_path);
    379   }
    380 }
    381 
    382 struct CreateOrUpdateShortcutsParams {
    383   CreateOrUpdateShortcutsParams(
    384       base::FilePath profile_path,
    385       ProfileShortcutManagerWin::CreateOrUpdateMode create_mode,
    386       ProfileShortcutManagerWin::NonProfileShortcutAction action)
    387       : profile_path(profile_path), create_mode(create_mode), action(action) {}
    388   ~CreateOrUpdateShortcutsParams() {}
    389 
    390   ProfileShortcutManagerWin::CreateOrUpdateMode create_mode;
    391   ProfileShortcutManagerWin::NonProfileShortcutAction action;
    392 
    393   // The path for this profile.
    394   base::FilePath profile_path;
    395   // The profile name before this update. Empty on create.
    396   base::string16 old_profile_name;
    397   // The new profile name.
    398   base::string16 profile_name;
    399   // Avatar images for this profile.
    400   SkBitmap avatar_image_1x;
    401   SkBitmap avatar_image_2x;
    402 };
    403 
    404 // Updates all desktop shortcuts for the given profile to have the specified
    405 // parameters. If |params.create_mode| is CREATE_WHEN_NONE_FOUND, a new shortcut
    406 // is created if no existing ones were found. Whether non-profile shortcuts
    407 // should be updated is specified by |params.action|. Must be called on the FILE
    408 // thread.
    409 void CreateOrUpdateDesktopShortcutsAndIconForProfile(
    410     const CreateOrUpdateShortcutsParams& params) {
    411   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    412 
    413   const base::FilePath shortcut_icon =
    414       CreateOrUpdateShortcutIconForProfile(params.profile_path,
    415                                            params.avatar_image_1x,
    416                                            params.avatar_image_2x);
    417   if (shortcut_icon.empty() ||
    418       params.create_mode ==
    419           ProfileShortcutManagerWin::CREATE_OR_UPDATE_ICON_ONLY) {
    420     return;
    421   }
    422 
    423   base::FilePath chrome_exe;
    424   if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
    425     NOTREACHED();
    426     return;
    427   }
    428 
    429   BrowserDistribution* distribution = BrowserDistribution::GetDistribution();
    430   // Ensure that the distribution supports creating shortcuts. If it doesn't,
    431   // the following code may result in NOTREACHED() being hit.
    432   DCHECK(distribution->CanCreateDesktopShortcuts());
    433 
    434   if (params.old_profile_name != params.profile_name) {
    435     const base::string16 old_shortcut_filename =
    436         profiles::internal::GetShortcutFilenameForProfile(
    437             params.old_profile_name,
    438             distribution);
    439     const base::string16 new_shortcut_filename =
    440         profiles::internal::GetShortcutFilenameForProfile(params.profile_name,
    441                                                           distribution);
    442     RenameChromeDesktopShortcutForProfile(old_shortcut_filename,
    443                                           new_shortcut_filename);
    444   }
    445 
    446   ShellUtil::ShortcutProperties properties(ShellUtil::CURRENT_USER);
    447   installer::Product product(distribution);
    448   product.AddDefaultShortcutProperties(chrome_exe, &properties);
    449 
    450   const base::string16 command_line =
    451       profiles::internal::CreateProfileShortcutFlags(params.profile_path);
    452 
    453   // Only set the profile-specific properties when |profile_name| is non empty.
    454   // If it is empty, it means the shortcut being created should be a regular,
    455   // non-profile Chrome shortcut.
    456   if (!params.profile_name.empty()) {
    457     properties.set_arguments(command_line);
    458     properties.set_icon(shortcut_icon, 0);
    459   } else {
    460     // Set the arguments explicitly to the empty string to ensure that
    461     // |ShellUtil::CreateOrUpdateShortcut| updates that part of the shortcut.
    462     properties.set_arguments(base::string16());
    463   }
    464 
    465   properties.set_app_id(
    466       ShellIntegration::GetChromiumModelIdForProfile(params.profile_path));
    467 
    468   ShellUtil::ShortcutOperation operation =
    469       ShellUtil::SHELL_SHORTCUT_REPLACE_EXISTING;
    470 
    471   std::vector<base::FilePath> shortcuts;
    472   ListDesktopShortcutsWithCommandLine(chrome_exe, command_line,
    473       params.action == ProfileShortcutManagerWin::UPDATE_NON_PROFILE_SHORTCUTS,
    474       &shortcuts);
    475   if (params.create_mode == ProfileShortcutManagerWin::CREATE_WHEN_NONE_FOUND &&
    476       shortcuts.empty()) {
    477     const base::string16 shortcut_name =
    478         profiles::internal::GetShortcutFilenameForProfile(params.profile_name,
    479                                                           distribution);
    480     shortcuts.push_back(base::FilePath(shortcut_name));
    481     operation = ShellUtil::SHELL_SHORTCUT_CREATE_IF_NO_SYSTEM_LEVEL;
    482   }
    483 
    484   for (size_t i = 0; i < shortcuts.size(); ++i) {
    485     const base::FilePath shortcut_name =
    486         shortcuts[i].BaseName().RemoveExtension();
    487     properties.set_shortcut_name(shortcut_name.value());
    488     ShellUtil::CreateOrUpdateShortcut(ShellUtil::SHORTCUT_LOCATION_DESKTOP,
    489         distribution, properties, operation);
    490   }
    491 }
    492 
    493 // Returns true if any desktop shortcuts exist with target |chrome_exe|,
    494 // regardless of their command line arguments.
    495 bool ChromeDesktopShortcutsExist(const base::FilePath& chrome_exe) {
    496   base::FilePath user_shortcuts_directory;
    497   if (!GetDesktopShortcutsDirectories(&user_shortcuts_directory, NULL))
    498     return false;
    499 
    500   base::FileEnumerator enumerator(user_shortcuts_directory, false,
    501                                   base::FileEnumerator::FILES);
    502   for (base::FilePath path = enumerator.Next(); !path.empty();
    503        path = enumerator.Next()) {
    504     if (IsChromeShortcut(path, chrome_exe, NULL))
    505       return true;
    506   }
    507 
    508   return false;
    509 }
    510 
    511 // Deletes all desktop shortcuts for the specified profile. If
    512 // |ensure_shortcuts_remain| is true, then a regular non-profile shortcut will
    513 // be created if this function would otherwise delete the last Chrome desktop
    514 // shortcut(s). Must be called on the FILE thread.
    515 void DeleteDesktopShortcuts(const base::FilePath& profile_path,
    516                             bool ensure_shortcuts_remain) {
    517   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    518 
    519   base::FilePath chrome_exe;
    520   if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
    521     NOTREACHED();
    522     return;
    523   }
    524 
    525   const base::string16 command_line =
    526       profiles::internal::CreateProfileShortcutFlags(profile_path);
    527   std::vector<base::FilePath> shortcuts;
    528   ListDesktopShortcutsWithCommandLine(chrome_exe, command_line, false,
    529                                       &shortcuts);
    530 
    531   for (size_t i = 0; i < shortcuts.size(); ++i) {
    532     // Use base::DeleteFile() instead of ShellUtil::RemoveShortcuts(), as the
    533     // latter causes non-profile taskbar shortcuts to be removed since it
    534     // doesn't consider the command-line of the shortcuts it deletes.
    535     // TODO(huangs): Refactor with ShellUtil::RemoveShortcuts().
    536     base::win::TaskbarUnpinShortcutLink(shortcuts[i].value().c_str());
    537     base::DeleteFile(shortcuts[i], false);
    538     // Notify the shell that the shortcut was deleted to ensure desktop refresh.
    539     SHChangeNotify(SHCNE_DELETE, SHCNF_PATH, shortcuts[i].value().c_str(),
    540                    NULL);
    541   }
    542 
    543   // If |ensure_shortcuts_remain| is true and deleting this profile caused the
    544   // last shortcuts to be removed, re-create a regular non-profile shortcut.
    545   const bool had_shortcuts = !shortcuts.empty();
    546   if (ensure_shortcuts_remain && had_shortcuts &&
    547       !ChromeDesktopShortcutsExist(chrome_exe)) {
    548     BrowserDistribution* distribution = BrowserDistribution::GetDistribution();
    549     // Ensure that the distribution supports creating shortcuts. If it doesn't,
    550     // the following code may result in NOTREACHED() being hit.
    551     DCHECK(distribution->CanCreateDesktopShortcuts());
    552     installer::Product product(distribution);
    553 
    554     ShellUtil::ShortcutProperties properties(ShellUtil::CURRENT_USER);
    555     product.AddDefaultShortcutProperties(chrome_exe, &properties);
    556     properties.set_shortcut_name(
    557         profiles::internal::GetShortcutFilenameForProfile(base::string16(),
    558                                                           distribution));
    559     ShellUtil::CreateOrUpdateShortcut(
    560         ShellUtil::SHORTCUT_LOCATION_DESKTOP, distribution, properties,
    561         ShellUtil::SHELL_SHORTCUT_CREATE_IF_NO_SYSTEM_LEVEL);
    562   }
    563 }
    564 
    565 // Returns true if profile at |profile_path| has any shortcuts. Does not
    566 // consider non-profile shortcuts. Must be called on the FILE thread.
    567 bool HasAnyProfileShortcuts(const base::FilePath& profile_path) {
    568   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    569 
    570   base::FilePath chrome_exe;
    571   if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
    572     NOTREACHED();
    573     return false;
    574   }
    575 
    576   const base::string16 command_line =
    577       profiles::internal::CreateProfileShortcutFlags(profile_path);
    578   std::vector<base::FilePath> shortcuts;
    579   ListDesktopShortcutsWithCommandLine(chrome_exe, command_line, false,
    580                                       &shortcuts);
    581   return !shortcuts.empty();
    582 }
    583 
    584 // Replaces any reserved characters with spaces, and trims the resulting string
    585 // to prevent any leading and trailing spaces. Also makes sure that the
    586 // resulting filename doesn't exceed |kMaxProfileShortcutFileNameLength|.
    587 // TODO(macourteau): find a way to limit the total path's length to MAX_PATH
    588 // instead of limiting the profile's name to |kMaxProfileShortcutFileNameLength|
    589 // characters.
    590 base::string16 SanitizeShortcutProfileNameString(
    591     const base::string16& profile_name) {
    592   base::string16 sanitized = profile_name;
    593   size_t pos = sanitized.find_first_of(kReservedCharacters);
    594   while (pos != base::string16::npos) {
    595     sanitized[pos] = L' ';
    596     pos = sanitized.find_first_of(kReservedCharacters, pos + 1);
    597   }
    598 
    599   TrimWhitespace(sanitized, TRIM_LEADING, &sanitized);
    600   if (sanitized.size() > kMaxProfileShortcutFileNameLength)
    601     sanitized.erase(kMaxProfileShortcutFileNameLength);
    602   TrimWhitespace(sanitized, TRIM_TRAILING, &sanitized);
    603 
    604   return sanitized;
    605 }
    606 
    607 // Returns a copied SkBitmap for the given resource id that can be safely passed
    608 // to another thread.
    609 SkBitmap GetImageResourceSkBitmapCopy(int resource_id) {
    610   const gfx::Image image =
    611       ResourceBundle::GetSharedInstance().GetNativeImageNamed(resource_id);
    612   DCHECK(!image.IsEmpty());
    613 
    614   const SkBitmap* image_bitmap = image.ToSkBitmap();
    615   SkBitmap bitmap_copy;
    616   image_bitmap->deepCopyTo(&bitmap_copy, image_bitmap->getConfig());
    617   return bitmap_copy;
    618 }
    619 
    620 }  // namespace
    621 
    622 namespace profiles {
    623 namespace internal {
    624 
    625 base::FilePath GetProfileIconPath(const base::FilePath& profile_path) {
    626   return profile_path.AppendASCII(kProfileIconFileName);
    627 }
    628 
    629 base::string16 GetShortcutFilenameForProfile(
    630     const base::string16& profile_name,
    631     BrowserDistribution* distribution) {
    632   base::string16 shortcut_name;
    633   if (!profile_name.empty()) {
    634     shortcut_name.append(SanitizeShortcutProfileNameString(profile_name));
    635     shortcut_name.append(L" - ");
    636     shortcut_name.append(l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME));
    637   } else {
    638     shortcut_name.append(
    639         distribution->GetShortcutName(BrowserDistribution::SHORTCUT_CHROME));
    640   }
    641   return shortcut_name + installer::kLnkExt;
    642 }
    643 
    644 base::string16 CreateProfileShortcutFlags(const base::FilePath& profile_path) {
    645   return base::StringPrintf(L"--%ls=\"%ls\"",
    646                             ASCIIToUTF16(switches::kProfileDirectory).c_str(),
    647                             profile_path.BaseName().value().c_str());
    648 }
    649 
    650 }  // namespace internal
    651 }  // namespace profiles
    652 
    653 // static
    654 bool ProfileShortcutManager::IsFeatureEnabled() {
    655   CommandLine* command_line = CommandLine::ForCurrentProcess();
    656   return command_line->HasSwitch(switches::kEnableProfileShortcutManager) ||
    657          (BrowserDistribution::GetDistribution()->CanCreateDesktopShortcuts() &&
    658           !command_line->HasSwitch(switches::kUserDataDir));
    659 }
    660 
    661 // static
    662 ProfileShortcutManager* ProfileShortcutManager::Create(
    663     ProfileManager* manager) {
    664   return new ProfileShortcutManagerWin(manager);
    665 }
    666 
    667 ProfileShortcutManagerWin::ProfileShortcutManagerWin(ProfileManager* manager)
    668     : profile_manager_(manager) {
    669   DCHECK_EQ(
    670       arraysize(kProfileAvatarIconResources2x),
    671       profile_manager_->GetProfileInfoCache().GetDefaultAvatarIconCount());
    672 
    673   registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CREATED,
    674                  content::NotificationService::AllSources());
    675 
    676   profile_manager_->GetProfileInfoCache().AddObserver(this);
    677 }
    678 
    679 ProfileShortcutManagerWin::~ProfileShortcutManagerWin() {
    680   profile_manager_->GetProfileInfoCache().RemoveObserver(this);
    681 }
    682 
    683 void ProfileShortcutManagerWin::CreateOrUpdateProfileIcon(
    684     const base::FilePath& profile_path) {
    685   CreateOrUpdateShortcutsForProfileAtPath(profile_path,
    686                                           CREATE_OR_UPDATE_ICON_ONLY,
    687                                           IGNORE_NON_PROFILE_SHORTCUTS);
    688 }
    689 
    690 void ProfileShortcutManagerWin::CreateProfileShortcut(
    691     const base::FilePath& profile_path) {
    692   CreateOrUpdateShortcutsForProfileAtPath(profile_path, CREATE_WHEN_NONE_FOUND,
    693                                           IGNORE_NON_PROFILE_SHORTCUTS);
    694 }
    695 
    696 void ProfileShortcutManagerWin::RemoveProfileShortcuts(
    697     const base::FilePath& profile_path) {
    698   BrowserThread::PostTask(
    699       BrowserThread::FILE, FROM_HERE,
    700       base::Bind(&DeleteDesktopShortcuts, profile_path, false));
    701 }
    702 
    703 void ProfileShortcutManagerWin::HasProfileShortcuts(
    704     const base::FilePath& profile_path,
    705     const base::Callback<void(bool)>& callback) {
    706   BrowserThread::PostTaskAndReplyWithResult(
    707       BrowserThread::FILE, FROM_HERE,
    708       base::Bind(&HasAnyProfileShortcuts, profile_path), callback);
    709 }
    710 
    711 void ProfileShortcutManagerWin::GetShortcutProperties(
    712     const base::FilePath& profile_path,
    713     CommandLine* command_line,
    714     base::string16* name,
    715     base::FilePath* icon_path) {
    716   base::FilePath chrome_exe;
    717   if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
    718     NOTREACHED();
    719     return;
    720   }
    721 
    722   const ProfileInfoCache& cache = profile_manager_->GetProfileInfoCache();
    723   size_t profile_index = cache.GetIndexOfProfileWithPath(profile_path);
    724   DCHECK_LT(profile_index, cache.GetNumberOfProfiles());
    725 
    726   // The used profile name should be empty if there is only 1 profile.
    727   base::string16 shortcut_profile_name;
    728   if (cache.GetNumberOfProfiles() > 1)
    729     shortcut_profile_name = cache.GetNameOfProfileAtIndex(profile_index);
    730 
    731   *name = base::FilePath(profiles::internal::GetShortcutFilenameForProfile(
    732       shortcut_profile_name,
    733       BrowserDistribution::GetDistribution())).RemoveExtension().value();
    734 
    735   command_line->ParseFromString(L"\"" + chrome_exe.value() + L"\" " +
    736       profiles::internal::CreateProfileShortcutFlags(profile_path));
    737 
    738   *icon_path = profiles::internal::GetProfileIconPath(profile_path);
    739 }
    740 
    741 void ProfileShortcutManagerWin::OnProfileAdded(
    742     const base::FilePath& profile_path) {
    743   CreateOrUpdateProfileIcon(profile_path);
    744   if (profile_manager_->GetProfileInfoCache().GetNumberOfProfiles() == 2) {
    745     // When the second profile is added, make existing non-profile shortcuts
    746     // point to the first profile and be badged/named appropriately.
    747     CreateOrUpdateShortcutsForProfileAtPath(GetOtherProfilePath(profile_path),
    748                                             UPDATE_EXISTING_ONLY,
    749                                             UPDATE_NON_PROFILE_SHORTCUTS);
    750   }
    751 }
    752 
    753 void ProfileShortcutManagerWin::OnProfileWasRemoved(
    754     const base::FilePath& profile_path,
    755     const base::string16& profile_name) {
    756   const ProfileInfoCache& cache = profile_manager_->GetProfileInfoCache();
    757   // If there is only one profile remaining, remove the badging information
    758   // from an existing shortcut.
    759   const bool deleting_down_to_last_profile = (cache.GetNumberOfProfiles() == 1);
    760   if (deleting_down_to_last_profile) {
    761     // This is needed to unbadge the icon.
    762     CreateOrUpdateShortcutsForProfileAtPath(cache.GetPathOfProfileAtIndex(0),
    763                                             UPDATE_EXISTING_ONLY,
    764                                             IGNORE_NON_PROFILE_SHORTCUTS);
    765   }
    766 
    767   BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
    768                           base::Bind(&DeleteDesktopShortcuts,
    769                                      profile_path,
    770                                      deleting_down_to_last_profile));
    771 }
    772 
    773 void ProfileShortcutManagerWin::OnProfileNameChanged(
    774     const base::FilePath& profile_path,
    775     const base::string16& old_profile_name) {
    776   CreateOrUpdateShortcutsForProfileAtPath(profile_path, UPDATE_EXISTING_ONLY,
    777                                           IGNORE_NON_PROFILE_SHORTCUTS);
    778 }
    779 
    780 void ProfileShortcutManagerWin::OnProfileAvatarChanged(
    781     const base::FilePath& profile_path) {
    782   CreateOrUpdateProfileIcon(profile_path);
    783 }
    784 
    785 base::FilePath ProfileShortcutManagerWin::GetOtherProfilePath(
    786     const base::FilePath& profile_path) {
    787   const ProfileInfoCache& cache = profile_manager_->GetProfileInfoCache();
    788   DCHECK_EQ(2U, cache.GetNumberOfProfiles());
    789   // Get the index of the current profile, in order to find the index of the
    790   // other profile.
    791   size_t current_profile_index = cache.GetIndexOfProfileWithPath(profile_path);
    792   size_t other_profile_index = (current_profile_index == 0) ? 1 : 0;
    793   return cache.GetPathOfProfileAtIndex(other_profile_index);
    794 }
    795 
    796 void ProfileShortcutManagerWin::CreateOrUpdateShortcutsForProfileAtPath(
    797     const base::FilePath& profile_path,
    798     CreateOrUpdateMode create_mode,
    799     NonProfileShortcutAction action) {
    800   DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::UI) ||
    801          BrowserThread::CurrentlyOn(BrowserThread::UI));
    802   CreateOrUpdateShortcutsParams params(profile_path, create_mode, action);
    803 
    804   ProfileInfoCache* cache = &profile_manager_->GetProfileInfoCache();
    805   size_t profile_index = cache->GetIndexOfProfileWithPath(profile_path);
    806   if (profile_index == std::string::npos)
    807     return;
    808   bool remove_badging = cache->GetNumberOfProfiles() == 1;
    809 
    810   params.old_profile_name =
    811       cache->GetShortcutNameOfProfileAtIndex(profile_index);
    812 
    813   // Exit early if the mode is to update existing profile shortcuts only and
    814   // none were ever created for this profile, per the shortcut name not being
    815   // set in the profile info cache.
    816   if (params.old_profile_name.empty() &&
    817       create_mode == UPDATE_EXISTING_ONLY &&
    818       action == IGNORE_NON_PROFILE_SHORTCUTS) {
    819     return;
    820   }
    821 
    822   if (!remove_badging) {
    823     params.profile_name = cache->GetNameOfProfileAtIndex(profile_index);
    824 
    825     const size_t icon_index =
    826         cache->GetAvatarIconIndexOfProfileAtIndex(profile_index);
    827     const int resource_id_1x =
    828         cache->GetDefaultAvatarIconResourceIDAtIndex(icon_index);
    829     const int resource_id_2x = kProfileAvatarIconResources2x[icon_index];
    830     // Make a copy of the SkBitmaps to ensure that we can safely use the image
    831     // data on the FILE thread.
    832     params.avatar_image_1x = GetImageResourceSkBitmapCopy(resource_id_1x);
    833     params.avatar_image_2x = GetImageResourceSkBitmapCopy(resource_id_2x);
    834   }
    835   BrowserThread::PostTask(
    836       BrowserThread::FILE, FROM_HERE,
    837       base::Bind(&CreateOrUpdateDesktopShortcutsAndIconForProfile, params));
    838 
    839   cache->SetShortcutNameOfProfileAtIndex(profile_index,
    840                                          params.profile_name);
    841 }
    842 
    843 void ProfileShortcutManagerWin::Observe(
    844     int type,
    845     const content::NotificationSource& source,
    846     const content::NotificationDetails& details) {
    847   switch (type) {
    848     // This notification is triggered when a profile is loaded.
    849     case chrome::NOTIFICATION_PROFILE_CREATED: {
    850       Profile* profile =
    851           content::Source<Profile>(source).ptr()->GetOriginalProfile();
    852       if (profile->GetPrefs()->GetInteger(prefs::kProfileIconVersion) <
    853           kCurrentProfileIconVersion) {
    854         // Ensure the profile's icon file has been created.
    855         CreateOrUpdateProfileIcon(profile->GetPath());
    856       }
    857       break;
    858     }
    859     default:
    860       NOTREACHED();
    861       break;
    862   }
    863 }
    864