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 const int kProfileAvatarBadgeSize = 28;
     69 const int kShortcutIconSize = 48;
     70 
     71 const int kCurrentProfileIconVersion = 1;
     72 
     73 // 2x sized profile avatar icons. Mirrors |kDefaultAvatarIconResources| in
     74 // profile_info_cache.cc.
     75 const int kProfileAvatarIconResources2x[] = {
     76   IDR_PROFILE_AVATAR_2X_0,
     77   IDR_PROFILE_AVATAR_2X_1,
     78   IDR_PROFILE_AVATAR_2X_2,
     79   IDR_PROFILE_AVATAR_2X_3,
     80   IDR_PROFILE_AVATAR_2X_4,
     81   IDR_PROFILE_AVATAR_2X_5,
     82   IDR_PROFILE_AVATAR_2X_6,
     83   IDR_PROFILE_AVATAR_2X_7,
     84   IDR_PROFILE_AVATAR_2X_8,
     85   IDR_PROFILE_AVATAR_2X_9,
     86   IDR_PROFILE_AVATAR_2X_10,
     87   IDR_PROFILE_AVATAR_2X_11,
     88   IDR_PROFILE_AVATAR_2X_12,
     89   IDR_PROFILE_AVATAR_2X_13,
     90   IDR_PROFILE_AVATAR_2X_14,
     91   IDR_PROFILE_AVATAR_2X_15,
     92   IDR_PROFILE_AVATAR_2X_16,
     93   IDR_PROFILE_AVATAR_2X_17,
     94   IDR_PROFILE_AVATAR_2X_18,
     95   IDR_PROFILE_AVATAR_2X_19,
     96   IDR_PROFILE_AVATAR_2X_20,
     97   IDR_PROFILE_AVATAR_2X_21,
     98   IDR_PROFILE_AVATAR_2X_22,
     99   IDR_PROFILE_AVATAR_2X_23,
    100   IDR_PROFILE_AVATAR_2X_24,
    101   IDR_PROFILE_AVATAR_2X_25,
    102 };
    103 
    104 // Badges |app_icon_bitmap| with |avatar_bitmap| at the bottom right corner and
    105 // returns the resulting SkBitmap.
    106 SkBitmap BadgeIcon(const SkBitmap& app_icon_bitmap,
    107                    const SkBitmap& avatar_bitmap,
    108                    int scale_factor) {
    109   // TODO(rlp): Share this chunk of code with
    110   // avatar_menu_button::DrawTaskBarDecoration.
    111   SkBitmap source_bitmap = avatar_bitmap;
    112   if ((avatar_bitmap.width() == scale_factor * profiles::kAvatarIconWidth) &&
    113       (avatar_bitmap.height() == scale_factor * profiles::kAvatarIconHeight)) {
    114     // Shave a couple of columns so the bitmap is more square. So when
    115     // resized to a square aspect ratio it looks pretty.
    116     gfx::Rect frame(scale_factor * profiles::kAvatarIconWidth,
    117                     scale_factor * profiles::kAvatarIconHeight);
    118     frame.Inset(scale_factor * 2, 0, scale_factor * 2, 0);
    119     avatar_bitmap.extractSubset(&source_bitmap, gfx::RectToSkIRect(frame));
    120   } else {
    121     NOTREACHED();
    122   }
    123   int avatar_badge_size = kProfileAvatarBadgeSize;
    124   if (app_icon_bitmap.width() != kShortcutIconSize) {
    125     avatar_badge_size =
    126         app_icon_bitmap.width() * kProfileAvatarBadgeSize / kShortcutIconSize;
    127   }
    128   SkBitmap sk_icon = skia::ImageOperations::Resize(
    129       source_bitmap, skia::ImageOperations::RESIZE_LANCZOS3, avatar_badge_size,
    130       source_bitmap.height() * avatar_badge_size / source_bitmap.width());
    131 
    132   // Overlay the avatar on the icon, anchoring it to the bottom-right of the
    133   // icon.
    134   scoped_ptr<SkCanvas> offscreen_canvas(
    135       skia::CreateBitmapCanvas(app_icon_bitmap.width(),
    136                                app_icon_bitmap.height(),
    137                                false));
    138   DCHECK(offscreen_canvas);
    139   offscreen_canvas->drawBitmap(app_icon_bitmap, 0, 0);
    140   offscreen_canvas->drawBitmap(sk_icon,
    141                                app_icon_bitmap.width() - sk_icon.width(),
    142                                app_icon_bitmap.height() - sk_icon.height());
    143   const SkBitmap& badged_bitmap =
    144       offscreen_canvas->getDevice()->accessBitmap(false);
    145   SkBitmap badged_bitmap_copy;
    146   badged_bitmap.deepCopyTo(&badged_bitmap_copy, badged_bitmap.getConfig());
    147   return badged_bitmap_copy;
    148 }
    149 
    150 // Creates a desktop shortcut icon file (.ico) on the disk for a given profile,
    151 // badging the browser distribution icon with the profile avatar.
    152 // Returns a path to the shortcut icon file on disk, which is empty if this
    153 // fails. Use index 0 when assigning the resulting file as the icon. If both
    154 // given bitmaps are empty, an unbadged icon is created.
    155 // |callback| will be run on successful icon creation.
    156 // Returns the path to the created icon on success and an empty base::FilePath
    157 // on failure.
    158 // TODO(calamity): Ideally we'd just copy the app icon verbatim from the exe's
    159 // resources in the case of an unbadged icon.
    160 base::FilePath CreateOrUpdateShortcutIconForProfile(
    161     const base::FilePath& profile_path,
    162     const SkBitmap& avatar_bitmap_1x,
    163     const SkBitmap& avatar_bitmap_2x,
    164     const base::Closure& callback) {
    165   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    166 
    167   if (!base::PathExists(profile_path)) {
    168     LOG(ERROR) << "Profile directory " << profile_path.value()
    169                << " did not exist when trying to create profile icon";
    170     return base::FilePath();
    171   }
    172 
    173   scoped_ptr<SkBitmap> app_icon_bitmap(GetAppIconForSize(kShortcutIconSize));
    174   if (!app_icon_bitmap)
    175     return base::FilePath();
    176 
    177   gfx::ImageFamily badged_bitmaps;
    178   if (!avatar_bitmap_1x.empty()) {
    179     badged_bitmaps.Add(gfx::Image::CreateFrom1xBitmap(
    180         BadgeIcon(*app_icon_bitmap, avatar_bitmap_1x, 1)));
    181   }
    182 
    183   scoped_ptr<SkBitmap> large_app_icon_bitmap(
    184       GetAppIconForSize(IconUtil::kLargeIconSize));
    185   if (large_app_icon_bitmap && !avatar_bitmap_2x.empty()) {
    186     badged_bitmaps.Add(gfx::Image::CreateFrom1xBitmap(
    187         BadgeIcon(*large_app_icon_bitmap, avatar_bitmap_2x, 2)));
    188   }
    189 
    190   // If we have no badged bitmaps, we should just use the default chrome icon.
    191   if (badged_bitmaps.empty()) {
    192     badged_bitmaps.Add(gfx::Image::CreateFrom1xBitmap(*app_icon_bitmap));
    193     if (large_app_icon_bitmap) {
    194       badged_bitmaps.Add(
    195           gfx::Image::CreateFrom1xBitmap(*large_app_icon_bitmap));
    196     }
    197   }
    198   // Finally, write the .ico file containing this new bitmap.
    199   const base::FilePath icon_path =
    200       profiles::internal::GetProfileIconPath(profile_path);
    201   const bool had_icon = base::PathExists(icon_path);
    202 
    203   if (!IconUtil::CreateIconFileFromImageFamily(badged_bitmaps, icon_path)) {
    204     // This can happen in theory if the profile directory is deleted between the
    205     // beginning of this function and here; however this is extremely unlikely
    206     // and this check will help catch any regression where this call would start
    207     // failing constantly.
    208     NOTREACHED();
    209     return base::FilePath();
    210   }
    211 
    212   if (had_icon) {
    213     // This invalidates the Windows icon cache and causes the icon changes to
    214     // register with the taskbar and desktop. SHCNE_ASSOCCHANGED will cause a
    215     // desktop flash and we would like to avoid that if possible.
    216     SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
    217   } else {
    218     SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, icon_path.value().c_str(), NULL);
    219   }
    220   if (!callback.is_null())
    221     BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, callback);
    222   return icon_path;
    223 }
    224 
    225 // Updates the preferences with the current icon version on icon creation
    226 // success.
    227 void OnProfileIconCreateSuccess(base::FilePath profile_path) {
    228   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    229   if (!g_browser_process->profile_manager())
    230     return;
    231   Profile* profile =
    232       g_browser_process->profile_manager()->GetProfileByPath(profile_path);
    233   if (profile) {
    234     profile->GetPrefs()->SetInteger(prefs::kProfileIconVersion,
    235                                     kCurrentProfileIconVersion);
    236   }
    237 }
    238 
    239 // Gets the user and system directories for desktop shortcuts. Parameters may
    240 // be NULL if a directory type is not needed. Returns true on success.
    241 bool GetDesktopShortcutsDirectories(
    242     base::FilePath* user_shortcuts_directory,
    243     base::FilePath* system_shortcuts_directory) {
    244   BrowserDistribution* distribution = BrowserDistribution::GetDistribution();
    245   if (user_shortcuts_directory &&
    246       !ShellUtil::GetShortcutPath(ShellUtil::SHORTCUT_LOCATION_DESKTOP,
    247                                   distribution, ShellUtil::CURRENT_USER,
    248                                   user_shortcuts_directory)) {
    249     NOTREACHED();
    250     return false;
    251   }
    252   if (system_shortcuts_directory &&
    253       !ShellUtil::GetShortcutPath(ShellUtil::SHORTCUT_LOCATION_DESKTOP,
    254                                   distribution, ShellUtil::SYSTEM_LEVEL,
    255                                   system_shortcuts_directory)) {
    256     NOTREACHED();
    257     return false;
    258   }
    259   return true;
    260 }
    261 
    262 // Returns the long form of |path|, which will expand any shortened components
    263 // like "foo~2" to their full names.
    264 base::FilePath ConvertToLongPath(const base::FilePath& path) {
    265   const size_t length = GetLongPathName(path.value().c_str(), NULL, 0);
    266   if (length != 0 && length != path.value().length()) {
    267     std::vector<wchar_t> long_path(length);
    268     if (GetLongPathName(path.value().c_str(), &long_path[0], length) != 0)
    269       return base::FilePath(&long_path[0]);
    270   }
    271   return path;
    272 }
    273 
    274 // Returns true if the file at |path| is a Chrome shortcut and returns its
    275 // command line in output parameter |command_line|.
    276 bool IsChromeShortcut(const base::FilePath& path,
    277                       const base::FilePath& chrome_exe,
    278                       string16* command_line) {
    279   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    280 
    281   if (path.Extension() != installer::kLnkExt)
    282     return false;
    283 
    284   base::FilePath target_path;
    285   if (!base::win::ResolveShortcut(path, &target_path, command_line))
    286     return false;
    287   // One of the paths may be in short (elided) form. Compare long paths to
    288   // ensure these are still properly matched.
    289   return ConvertToLongPath(target_path) == ConvertToLongPath(chrome_exe);
    290 }
    291 
    292 // Populates |paths| with the file paths of Chrome desktop shortcuts that have
    293 // the specified |command_line|. If |include_empty_command_lines| is true,
    294 // Chrome desktop shortcuts with empty command lines will also be included.
    295 void ListDesktopShortcutsWithCommandLine(const base::FilePath& chrome_exe,
    296                                          const string16& command_line,
    297                                          bool include_empty_command_lines,
    298                                          std::vector<base::FilePath>* paths) {
    299   base::FilePath user_shortcuts_directory;
    300   if (!GetDesktopShortcutsDirectories(&user_shortcuts_directory, NULL))
    301     return;
    302 
    303   base::FileEnumerator enumerator(user_shortcuts_directory, false,
    304                                   base::FileEnumerator::FILES);
    305   for (base::FilePath path = enumerator.Next(); !path.empty();
    306        path = enumerator.Next()) {
    307     string16 shortcut_command_line;
    308     if (!IsChromeShortcut(path, chrome_exe, &shortcut_command_line))
    309       continue;
    310 
    311     // TODO(asvitkine): Change this to build a CommandLine object and ensure all
    312     // args from |command_line| are present in the shortcut's CommandLine. This
    313     // will be more robust when |command_line| contains multiple args.
    314     if ((shortcut_command_line.empty() && include_empty_command_lines) ||
    315         (shortcut_command_line.find(command_line) != string16::npos)) {
    316       paths->push_back(path);
    317     }
    318   }
    319 }
    320 
    321 // Renames the given desktop shortcut and informs the shell of this change.
    322 bool RenameDesktopShortcut(const base::FilePath& old_shortcut_path,
    323                            const base::FilePath& new_shortcut_path) {
    324   if (!base::Move(old_shortcut_path, new_shortcut_path))
    325     return false;
    326 
    327   // Notify the shell of the rename, which allows the icon to keep its position
    328   // on the desktop when renamed. Note: This only works if either SHCNF_FLUSH or
    329   // SHCNF_FLUSHNOWAIT is specified as a flag.
    330   SHChangeNotify(SHCNE_RENAMEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT,
    331                  old_shortcut_path.value().c_str(),
    332                  new_shortcut_path.value().c_str());
    333   return true;
    334 }
    335 
    336 // Renames an existing Chrome desktop profile shortcut. Must be called on the
    337 // FILE thread.
    338 void RenameChromeDesktopShortcutForProfile(
    339     const string16& old_shortcut_filename,
    340     const string16& new_shortcut_filename) {
    341   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    342 
    343   base::FilePath user_shortcuts_directory;
    344   base::FilePath system_shortcuts_directory;
    345   if (!GetDesktopShortcutsDirectories(&user_shortcuts_directory,
    346                                       &system_shortcuts_directory)) {
    347     return;
    348   }
    349 
    350   const base::FilePath old_shortcut_path =
    351       user_shortcuts_directory.Append(old_shortcut_filename);
    352   const base::FilePath new_shortcut_path =
    353       user_shortcuts_directory.Append(new_shortcut_filename);
    354 
    355   if (base::PathExists(old_shortcut_path)) {
    356     // Rename the old shortcut unless a system-level shortcut exists at the
    357     // destination, in which case the old shortcut is simply deleted.
    358     const base::FilePath possible_new_system_shortcut =
    359         system_shortcuts_directory.Append(new_shortcut_filename);
    360     if (base::PathExists(possible_new_system_shortcut))
    361       base::DeleteFile(old_shortcut_path, false);
    362     else if (!RenameDesktopShortcut(old_shortcut_path, new_shortcut_path))
    363       DLOG(ERROR) << "Could not rename Windows profile desktop shortcut.";
    364   } else {
    365     // If the shortcut does not exist, it may have been renamed by the user. In
    366     // that case, its name should not be changed.
    367     // It's also possible that a system-level shortcut exists instead - this
    368     // should only be the case for the original Chrome shortcut from an
    369     // installation. If that's the case, copy that one over - it will get its
    370     // properties updated by
    371     // |CreateOrUpdateDesktopShortcutsAndIconForProfile()|.
    372     const base::FilePath possible_old_system_shortcut =
    373         system_shortcuts_directory.Append(old_shortcut_filename);
    374     if (base::PathExists(possible_old_system_shortcut))
    375       base::CopyFile(possible_old_system_shortcut, new_shortcut_path);
    376   }
    377 }
    378 
    379 struct CreateOrUpdateShortcutsParams {
    380   CreateOrUpdateShortcutsParams(
    381       base::FilePath profile_path,
    382       ProfileShortcutManagerWin::CreateOrUpdateMode create_mode,
    383       ProfileShortcutManagerWin::NonProfileShortcutAction action)
    384       : profile_path(profile_path), create_mode(create_mode), action(action) {}
    385   ~CreateOrUpdateShortcutsParams() {}
    386 
    387   ProfileShortcutManagerWin::CreateOrUpdateMode create_mode;
    388   ProfileShortcutManagerWin::NonProfileShortcutAction action;
    389 
    390   // The path for this profile.
    391   base::FilePath profile_path;
    392   // The profile name before this update. Empty on create.
    393   string16 old_profile_name;
    394   // The new profile name.
    395   string16 profile_name;
    396   // Avatar images for this profile.
    397   SkBitmap avatar_image_1x;
    398   SkBitmap avatar_image_2x;
    399 };
    400 
    401 // Updates all desktop shortcuts for the given profile to have the specified
    402 // parameters. If |params.create_mode| is CREATE_WHEN_NONE_FOUND, a new shortcut
    403 // is created if no existing ones were found. Whether non-profile shortcuts
    404 // should be updated is specified by |params.action|. Must be called on the FILE
    405 // thread. |callback| is called on successful icon creation.
    406 void CreateOrUpdateDesktopShortcutsAndIconForProfile(
    407     const CreateOrUpdateShortcutsParams& params,
    408     const base::Closure& callback) {
    409   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    410 
    411   const base::FilePath shortcut_icon =
    412       CreateOrUpdateShortcutIconForProfile(params.profile_path,
    413                                            params.avatar_image_1x,
    414                                            params.avatar_image_2x,
    415                                            callback);
    416   if (shortcut_icon.empty() ||
    417       params.create_mode ==
    418           ProfileShortcutManagerWin::CREATE_OR_UPDATE_ICON_ONLY) {
    419     return;
    420   }
    421 
    422   base::FilePath chrome_exe;
    423   if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
    424     NOTREACHED();
    425     return;
    426   }
    427 
    428   BrowserDistribution* distribution = BrowserDistribution::GetDistribution();
    429   // Ensure that the distribution supports creating shortcuts. If it doesn't,
    430   // the following code may result in NOTREACHED() being hit.
    431   DCHECK(distribution->CanCreateDesktopShortcuts());
    432 
    433   if (params.old_profile_name != params.profile_name) {
    434     const string16 old_shortcut_filename =
    435         profiles::internal::GetShortcutFilenameForProfile(
    436             params.old_profile_name,
    437             distribution);
    438     const string16 new_shortcut_filename =
    439         profiles::internal::GetShortcutFilenameForProfile(params.profile_name,
    440                                                           distribution);
    441     RenameChromeDesktopShortcutForProfile(old_shortcut_filename,
    442                                           new_shortcut_filename);
    443   }
    444 
    445   ShellUtil::ShortcutProperties properties(ShellUtil::CURRENT_USER);
    446   installer::Product product(distribution);
    447   product.AddDefaultShortcutProperties(chrome_exe, &properties);
    448 
    449   const string16 command_line =
    450       profiles::internal::CreateProfileShortcutFlags(params.profile_path);
    451 
    452   // Only set the profile-specific properties when |profile_name| is non empty.
    453   // If it is empty, it means the shortcut being created should be a regular,
    454   // non-profile Chrome shortcut.
    455   if (!params.profile_name.empty()) {
    456     properties.set_arguments(command_line);
    457     properties.set_icon(shortcut_icon, 0);
    458   } else {
    459     // Set the arguments explicitly to the empty string to ensure that
    460     // |ShellUtil::CreateOrUpdateShortcut| updates that part of the shortcut.
    461     properties.set_arguments(string16());
    462   }
    463 
    464   properties.set_app_id(
    465       ShellIntegration::GetChromiumModelIdForProfile(params.profile_path));
    466 
    467   ShellUtil::ShortcutOperation operation =
    468       ShellUtil::SHELL_SHORTCUT_REPLACE_EXISTING;
    469 
    470   std::vector<base::FilePath> shortcuts;
    471   ListDesktopShortcutsWithCommandLine(chrome_exe, command_line,
    472       params.action == ProfileShortcutManagerWin::UPDATE_NON_PROFILE_SHORTCUTS,
    473       &shortcuts);
    474   if (params.create_mode == ProfileShortcutManagerWin::CREATE_WHEN_NONE_FOUND &&
    475       shortcuts.empty()) {
    476     const string16 shortcut_name =
    477         profiles::internal::GetShortcutFilenameForProfile(params.profile_name,
    478                                                           distribution);
    479     shortcuts.push_back(base::FilePath(shortcut_name));
    480     operation = ShellUtil::SHELL_SHORTCUT_CREATE_IF_NO_SYSTEM_LEVEL;
    481   }
    482 
    483   for (size_t i = 0; i < shortcuts.size(); ++i) {
    484     const base::FilePath shortcut_name =
    485         shortcuts[i].BaseName().RemoveExtension();
    486     properties.set_shortcut_name(shortcut_name.value());
    487     ShellUtil::CreateOrUpdateShortcut(ShellUtil::SHORTCUT_LOCATION_DESKTOP,
    488         distribution, properties, operation);
    489   }
    490 }
    491 
    492 // Returns true if any desktop shortcuts exist with target |chrome_exe|,
    493 // regardless of their command line arguments.
    494 bool ChromeDesktopShortcutsExist(const base::FilePath& chrome_exe) {
    495   base::FilePath user_shortcuts_directory;
    496   if (!GetDesktopShortcutsDirectories(&user_shortcuts_directory, NULL))
    497     return false;
    498 
    499   base::FileEnumerator enumerator(user_shortcuts_directory, false,
    500                                   base::FileEnumerator::FILES);
    501   for (base::FilePath path = enumerator.Next(); !path.empty();
    502        path = enumerator.Next()) {
    503     if (IsChromeShortcut(path, chrome_exe, NULL))
    504       return true;
    505   }
    506 
    507   return false;
    508 }
    509 
    510 // Deletes all desktop shortcuts for the specified profile. If
    511 // |ensure_shortcuts_remain| is true, then a regular non-profile shortcut will
    512 // be created if this function would otherwise delete the last Chrome desktop
    513 // shortcut(s). Must be called on the FILE thread.
    514 void DeleteDesktopShortcuts(const base::FilePath& profile_path,
    515                             bool ensure_shortcuts_remain) {
    516   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    517 
    518   base::FilePath chrome_exe;
    519   if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
    520     NOTREACHED();
    521     return;
    522   }
    523 
    524   const string16 command_line =
    525       profiles::internal::CreateProfileShortcutFlags(profile_path);
    526   std::vector<base::FilePath> shortcuts;
    527   ListDesktopShortcutsWithCommandLine(chrome_exe, command_line, false,
    528                                       &shortcuts);
    529 
    530   for (size_t i = 0; i < shortcuts.size(); ++i) {
    531     // Use base::DeleteFile() instead of ShellUtil::RemoveShortcut(), as the
    532     // latter causes non-profile taskbar shortcuts to be unpinned.
    533     base::DeleteFile(shortcuts[i], false);
    534     // Notify the shell that the shortcut was deleted to ensure desktop refresh.
    535     SHChangeNotify(SHCNE_DELETE, SHCNF_PATH, shortcuts[i].value().c_str(),
    536                    NULL);
    537   }
    538 
    539   // If |ensure_shortcuts_remain| is true and deleting this profile caused the
    540   // last shortcuts to be removed, re-create a regular non-profile shortcut.
    541   const bool had_shortcuts = !shortcuts.empty();
    542   if (ensure_shortcuts_remain && had_shortcuts &&
    543       !ChromeDesktopShortcutsExist(chrome_exe)) {
    544     BrowserDistribution* distribution = BrowserDistribution::GetDistribution();
    545     // Ensure that the distribution supports creating shortcuts. If it doesn't,
    546     // the following code may result in NOTREACHED() being hit.
    547     DCHECK(distribution->CanCreateDesktopShortcuts());
    548     installer::Product product(distribution);
    549 
    550     ShellUtil::ShortcutProperties properties(ShellUtil::CURRENT_USER);
    551     product.AddDefaultShortcutProperties(chrome_exe, &properties);
    552     properties.set_shortcut_name(
    553         profiles::internal::GetShortcutFilenameForProfile(string16(),
    554                                                           distribution));
    555     ShellUtil::CreateOrUpdateShortcut(
    556         ShellUtil::SHORTCUT_LOCATION_DESKTOP, distribution, properties,
    557         ShellUtil::SHELL_SHORTCUT_CREATE_IF_NO_SYSTEM_LEVEL);
    558   }
    559 }
    560 
    561 // Returns true if profile at |profile_path| has any shortcuts. Does not
    562 // consider non-profile shortcuts. Must be called on the FILE thread.
    563 bool HasAnyProfileShortcuts(const base::FilePath& profile_path) {
    564   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    565 
    566   base::FilePath chrome_exe;
    567   if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
    568     NOTREACHED();
    569     return false;
    570   }
    571 
    572   const string16 command_line =
    573       profiles::internal::CreateProfileShortcutFlags(profile_path);
    574   std::vector<base::FilePath> shortcuts;
    575   ListDesktopShortcutsWithCommandLine(chrome_exe, command_line, false,
    576                                       &shortcuts);
    577   return !shortcuts.empty();
    578 }
    579 
    580 // Replaces any reserved characters with spaces, and trims the resulting string
    581 // to prevent any leading and trailing spaces. Also makes sure that the
    582 // resulting filename doesn't exceed |kMaxProfileShortcutFileNameLength|.
    583 // TODO(macourteau): find a way to limit the total path's length to MAX_PATH
    584 // instead of limiting the profile's name to |kMaxProfileShortcutFileNameLength|
    585 // characters.
    586 string16 SanitizeShortcutProfileNameString(const string16& profile_name) {
    587   string16 sanitized = profile_name;
    588   size_t pos = sanitized.find_first_of(kReservedCharacters);
    589   while (pos != string16::npos) {
    590     sanitized[pos] = L' ';
    591     pos = sanitized.find_first_of(kReservedCharacters, pos + 1);
    592   }
    593 
    594   TrimWhitespace(sanitized, TRIM_LEADING, &sanitized);
    595   if (sanitized.size() > kMaxProfileShortcutFileNameLength)
    596     sanitized.erase(kMaxProfileShortcutFileNameLength);
    597   TrimWhitespace(sanitized, TRIM_TRAILING, &sanitized);
    598 
    599   return sanitized;
    600 }
    601 
    602 // Returns a copied SkBitmap for the given resource id that can be safely passed
    603 // to another thread.
    604 SkBitmap GetImageResourceSkBitmapCopy(int resource_id) {
    605   const gfx::Image image =
    606       ResourceBundle::GetSharedInstance().GetNativeImageNamed(resource_id);
    607   DCHECK(!image.IsEmpty());
    608 
    609   const SkBitmap* image_bitmap = image.ToSkBitmap();
    610   SkBitmap bitmap_copy;
    611   image_bitmap->deepCopyTo(&bitmap_copy, image_bitmap->getConfig());
    612   return bitmap_copy;
    613 }
    614 
    615 }  // namespace
    616 
    617 namespace profiles {
    618 namespace internal {
    619 
    620 base::FilePath GetProfileIconPath(const base::FilePath& profile_path) {
    621   return profile_path.AppendASCII(kProfileIconFileName);
    622 }
    623 
    624 string16 GetShortcutFilenameForProfile(const string16& profile_name,
    625                                        BrowserDistribution* distribution) {
    626   string16 shortcut_name;
    627   if (!profile_name.empty()) {
    628     shortcut_name.append(SanitizeShortcutProfileNameString(profile_name));
    629     shortcut_name.append(L" - ");
    630     shortcut_name.append(l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME));
    631   } else {
    632     shortcut_name.append(distribution->GetAppShortCutName());
    633   }
    634   return shortcut_name + installer::kLnkExt;
    635 }
    636 
    637 string16 CreateProfileShortcutFlags(const base::FilePath& profile_path) {
    638   return base::StringPrintf(L"--%ls=\"%ls\"",
    639                             ASCIIToUTF16(switches::kProfileDirectory).c_str(),
    640                             profile_path.BaseName().value().c_str());
    641 }
    642 
    643 }  // namespace internal
    644 }  // namespace profiles
    645 
    646 // static
    647 bool ProfileShortcutManager::IsFeatureEnabled() {
    648   return BrowserDistribution::GetDistribution()->CanCreateDesktopShortcuts() &&
    649       !CommandLine::ForCurrentProcess()->HasSwitch(switches::kUserDataDir);
    650 }
    651 
    652 // static
    653 ProfileShortcutManager* ProfileShortcutManager::Create(
    654     ProfileManager* manager) {
    655   return new ProfileShortcutManagerWin(manager);
    656 }
    657 
    658 ProfileShortcutManagerWin::ProfileShortcutManagerWin(ProfileManager* manager)
    659     : profile_manager_(manager) {
    660   DCHECK_EQ(
    661       arraysize(kProfileAvatarIconResources2x),
    662       profile_manager_->GetProfileInfoCache().GetDefaultAvatarIconCount());
    663 
    664   registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CREATED,
    665                  content::NotificationService::AllSources());
    666 
    667   profile_manager_->GetProfileInfoCache().AddObserver(this);
    668 }
    669 
    670 ProfileShortcutManagerWin::~ProfileShortcutManagerWin() {
    671   profile_manager_->GetProfileInfoCache().RemoveObserver(this);
    672 }
    673 
    674 void ProfileShortcutManagerWin::CreateOrUpdateProfileIcon(
    675     const base::FilePath& profile_path,
    676     const base::Closure& callback) {
    677   CreateOrUpdateShortcutsForProfileAtPath(profile_path,
    678                                           CREATE_OR_UPDATE_ICON_ONLY,
    679                                           IGNORE_NON_PROFILE_SHORTCUTS,
    680                                           callback);
    681 }
    682 
    683 void ProfileShortcutManagerWin::CreateProfileShortcut(
    684     const base::FilePath& profile_path) {
    685   CreateOrUpdateShortcutsForProfileAtPath(profile_path, CREATE_WHEN_NONE_FOUND,
    686                                           IGNORE_NON_PROFILE_SHORTCUTS,
    687                                           base::Closure());
    688 }
    689 
    690 void ProfileShortcutManagerWin::RemoveProfileShortcuts(
    691     const base::FilePath& profile_path) {
    692   BrowserThread::PostTask(
    693       BrowserThread::FILE, FROM_HERE,
    694       base::Bind(&DeleteDesktopShortcuts, profile_path, false));
    695 }
    696 
    697 void ProfileShortcutManagerWin::HasProfileShortcuts(
    698     const base::FilePath& profile_path,
    699     const base::Callback<void(bool)>& callback) {
    700   BrowserThread::PostTaskAndReplyWithResult(
    701       BrowserThread::FILE, FROM_HERE,
    702       base::Bind(&HasAnyProfileShortcuts, profile_path), callback);
    703 }
    704 
    705 void ProfileShortcutManagerWin::OnProfileAdded(
    706     const base::FilePath& profile_path) {
    707   CreateOrUpdateProfileIcon(profile_path, base::Closure());
    708   if (profile_manager_->GetProfileInfoCache().GetNumberOfProfiles() == 2) {
    709     // When the second profile is added, make existing non-profile shortcuts
    710     // point to the first profile and be badged/named appropriately.
    711     CreateOrUpdateShortcutsForProfileAtPath(GetOtherProfilePath(profile_path),
    712                                             UPDATE_EXISTING_ONLY,
    713                                             UPDATE_NON_PROFILE_SHORTCUTS,
    714                                             base::Closure());
    715   }
    716 }
    717 
    718 void ProfileShortcutManagerWin::OnProfileWasRemoved(
    719     const base::FilePath& profile_path,
    720     const string16& profile_name) {
    721   const ProfileInfoCache& cache = profile_manager_->GetProfileInfoCache();
    722   // If there is only one profile remaining, remove the badging information
    723   // from an existing shortcut.
    724   const bool deleting_down_to_last_profile = (cache.GetNumberOfProfiles() == 1);
    725   if (deleting_down_to_last_profile) {
    726     // This is needed to unbadge the icon.
    727     CreateOrUpdateShortcutsForProfileAtPath(cache.GetPathOfProfileAtIndex(0),
    728                                             UPDATE_EXISTING_ONLY,
    729                                             IGNORE_NON_PROFILE_SHORTCUTS,
    730                                             base::Closure());
    731   }
    732 
    733   BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
    734                           base::Bind(&DeleteDesktopShortcuts,
    735                                      profile_path,
    736                                      deleting_down_to_last_profile));
    737 }
    738 
    739 void ProfileShortcutManagerWin::OnProfileNameChanged(
    740     const base::FilePath& profile_path,
    741     const string16& old_profile_name) {
    742   CreateOrUpdateShortcutsForProfileAtPath(profile_path, UPDATE_EXISTING_ONLY,
    743                                           IGNORE_NON_PROFILE_SHORTCUTS,
    744                                           base::Closure());
    745 }
    746 
    747 void ProfileShortcutManagerWin::OnProfileAvatarChanged(
    748     const base::FilePath& profile_path) {
    749   CreateOrUpdateProfileIcon(profile_path, base::Closure());
    750 }
    751 
    752 base::FilePath ProfileShortcutManagerWin::GetOtherProfilePath(
    753     const base::FilePath& profile_path) {
    754   const ProfileInfoCache& cache = profile_manager_->GetProfileInfoCache();
    755   DCHECK_EQ(2U, cache.GetNumberOfProfiles());
    756   // Get the index of the current profile, in order to find the index of the
    757   // other profile.
    758   size_t current_profile_index = cache.GetIndexOfProfileWithPath(profile_path);
    759   size_t other_profile_index = (current_profile_index == 0) ? 1 : 0;
    760   return cache.GetPathOfProfileAtIndex(other_profile_index);
    761 }
    762 
    763 void ProfileShortcutManagerWin::CreateOrUpdateShortcutsForProfileAtPath(
    764     const base::FilePath& profile_path,
    765     CreateOrUpdateMode create_mode,
    766     NonProfileShortcutAction action,
    767     const base::Closure& callback) {
    768   DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::UI) ||
    769          BrowserThread::CurrentlyOn(BrowserThread::UI));
    770   CreateOrUpdateShortcutsParams params(profile_path, create_mode, action);
    771 
    772   ProfileInfoCache* cache = &profile_manager_->GetProfileInfoCache();
    773   size_t profile_index = cache->GetIndexOfProfileWithPath(profile_path);
    774   if (profile_index == std::string::npos)
    775     return;
    776   bool remove_badging = cache->GetNumberOfProfiles() == 1;
    777 
    778   params.old_profile_name =
    779       cache->GetShortcutNameOfProfileAtIndex(profile_index);
    780 
    781   // Exit early if the mode is to update existing profile shortcuts only and
    782   // none were ever created for this profile, per the shortcut name not being
    783   // set in the profile info cache.
    784   if (params.old_profile_name.empty() &&
    785       create_mode == UPDATE_EXISTING_ONLY &&
    786       action == IGNORE_NON_PROFILE_SHORTCUTS) {
    787     return;
    788   }
    789 
    790   if (!remove_badging) {
    791     params.profile_name = cache->GetNameOfProfileAtIndex(profile_index);
    792 
    793     const size_t icon_index =
    794         cache->GetAvatarIconIndexOfProfileAtIndex(profile_index);
    795     const int resource_id_1x =
    796         cache->GetDefaultAvatarIconResourceIDAtIndex(icon_index);
    797     const int resource_id_2x = kProfileAvatarIconResources2x[icon_index];
    798     // Make a copy of the SkBitmaps to ensure that we can safely use the image
    799     // data on the FILE thread.
    800     params.avatar_image_1x = GetImageResourceSkBitmapCopy(resource_id_1x);
    801     params.avatar_image_2x = GetImageResourceSkBitmapCopy(resource_id_2x);
    802   }
    803   BrowserThread::PostTask(
    804       BrowserThread::FILE, FROM_HERE,
    805       base::Bind(&CreateOrUpdateDesktopShortcutsAndIconForProfile, params,
    806                  callback));
    807 
    808   cache->SetShortcutNameOfProfileAtIndex(profile_index,
    809                                          params.profile_name);
    810 }
    811 
    812 void ProfileShortcutManagerWin::Observe(
    813     int type,
    814     const content::NotificationSource& source,
    815     const content::NotificationDetails& details) {
    816   switch (type) {
    817     // This notification is triggered when a profile is loaded.
    818     case chrome::NOTIFICATION_PROFILE_CREATED: {
    819       Profile* profile =
    820           content::Source<Profile>(source).ptr()->GetOriginalProfile();
    821       if (profile->GetPrefs()->GetInteger(prefs::kProfileIconVersion) <
    822           kCurrentProfileIconVersion) {
    823         // Ensure the profile's icon file has been created.
    824         CreateOrUpdateProfileIcon(
    825             profile->GetPath(),
    826             base::Bind(&OnProfileIconCreateSuccess, profile->GetPath()));
    827       }
    828       break;
    829     }
    830     default:
    831       NOTREACHED();
    832       break;
    833   }
    834 }
    835