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