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