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