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/files/file_enumerator.h"
16 #include "base/path_service.h"
17 #include "base/prefs/pref_service.h"
18 #include "base/strings/string16.h"
19 #include "base/strings/string_util.h"
20 #include "base/strings/stringprintf.h"
21 #include "base/strings/utf_string_conversions.h"
22 #include "base/win/shortcut.h"
23 #include "chrome/browser/app_icon_win.h"
24 #include "chrome/browser/browser_process.h"
25 #include "chrome/browser/chrome_notification_types.h"
26 #include "chrome/browser/profiles/profile_info_cache_observer.h"
27 #include "chrome/browser/profiles/profile_info_util.h"
28 #include "chrome/browser/profiles/profile_manager.h"
29 #include "chrome/browser/shell_integration.h"
30 #include "chrome/common/chrome_switches.h"
31 #include "chrome/common/pref_names.h"
32 #include "chrome/installer/util/browser_distribution.h"
33 #include "chrome/installer/util/product.h"
34 #include "chrome/installer/util/shell_util.h"
35 #include "content/public/browser/browser_thread.h"
36 #include "content/public/browser/notification_service.h"
37 #include "grit/chrome_unscaled_resources.h"
38 #include "grit/chromium_strings.h"
39 #include "skia/ext/image_operations.h"
40 #include "skia/ext/platform_canvas.h"
41 #include "ui/base/l10n/l10n_util.h"
42 #include "ui/base/resource/resource_bundle.h"
43 #include "ui/gfx/icon_util.h"
44 #include "ui/gfx/image/image.h"
45 #include "ui/gfx/image/image_family.h"
46 #include "ui/gfx/rect.h"
47 #include "ui/gfx/skia_util.h"
49 using content::BrowserThread
;
53 // Name of the badged icon file generated for a given profile.
54 const char kProfileIconFileName
[] = "Google Profile.ico";
56 // Characters that are not allowed in Windows filenames. Taken from
57 // http://msdn.microsoft.com/en-us/library/aa365247.aspx
58 const base::char16 kReservedCharacters
[] = L
"<>:\"/\\|?*\x01\x02\x03\x04\x05"
59 L
"\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17"
60 L
"\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F";
62 // The maximum number of characters allowed in profile shortcuts' file names.
63 // Warning: migration code will be needed if this is changed later, since
64 // existing shortcuts might no longer be found if the name is generated
65 // differently than it was when a shortcut was originally created.
66 const int kMaxProfileShortcutFileNameLength
= 64;
68 // The avatar badge size needs to be half of the shortcut icon size because
69 // the Windows taskbar icon is 32x32 and the avatar icon overlay is 16x16. So to
70 // get the shortcut avatar badge and the avatar icon overlay to match up, we
71 // need to preserve those ratios when creating the shortcut icon.
72 const int kShortcutIconSize
= 48;
73 const int kProfileAvatarBadgeSize
= kShortcutIconSize
/ 2;
75 const int kCurrentProfileIconVersion
= 2;
77 // 2x sized profile avatar icons. Mirrors |kDefaultAvatarIconResources| in
78 // profile_info_cache.cc.
79 const int kProfileAvatarIconResources2x
[] = {
80 IDR_PROFILE_AVATAR_2X_0
,
81 IDR_PROFILE_AVATAR_2X_1
,
82 IDR_PROFILE_AVATAR_2X_2
,
83 IDR_PROFILE_AVATAR_2X_3
,
84 IDR_PROFILE_AVATAR_2X_4
,
85 IDR_PROFILE_AVATAR_2X_5
,
86 IDR_PROFILE_AVATAR_2X_6
,
87 IDR_PROFILE_AVATAR_2X_7
,
88 IDR_PROFILE_AVATAR_2X_8
,
89 IDR_PROFILE_AVATAR_2X_9
,
90 IDR_PROFILE_AVATAR_2X_10
,
91 IDR_PROFILE_AVATAR_2X_11
,
92 IDR_PROFILE_AVATAR_2X_12
,
93 IDR_PROFILE_AVATAR_2X_13
,
94 IDR_PROFILE_AVATAR_2X_14
,
95 IDR_PROFILE_AVATAR_2X_15
,
96 IDR_PROFILE_AVATAR_2X_16
,
97 IDR_PROFILE_AVATAR_2X_17
,
98 IDR_PROFILE_AVATAR_2X_18
,
99 IDR_PROFILE_AVATAR_2X_19
,
100 IDR_PROFILE_AVATAR_2X_20
,
101 IDR_PROFILE_AVATAR_2X_21
,
102 IDR_PROFILE_AVATAR_2X_22
,
103 IDR_PROFILE_AVATAR_2X_23
,
104 IDR_PROFILE_AVATAR_2X_24
,
105 IDR_PROFILE_AVATAR_2X_25
,
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
,
113 // TODO(rlp): Share this chunk of code with
114 // avatar_menu_button::DrawTaskBarDecoration.
115 SkBitmap source_bitmap
= avatar_bitmap
;
116 if ((avatar_bitmap
.width() == scale_factor
* profiles::kAvatarIconWidth
) &&
117 (avatar_bitmap
.height() == scale_factor
* profiles::kAvatarIconHeight
)) {
118 // Shave a couple of columns so the bitmap is more square. So when
119 // resized to a square aspect ratio it looks pretty.
120 gfx::Rect
frame(scale_factor
* profiles::kAvatarIconWidth
,
121 scale_factor
* profiles::kAvatarIconHeight
);
122 frame
.Inset(scale_factor
* 2, 0, scale_factor
* 2, 0);
123 avatar_bitmap
.extractSubset(&source_bitmap
, gfx::RectToSkIRect(frame
));
127 int avatar_badge_size
= kProfileAvatarBadgeSize
;
128 if (app_icon_bitmap
.width() != kShortcutIconSize
) {
130 app_icon_bitmap
.width() * kProfileAvatarBadgeSize
/ kShortcutIconSize
;
132 SkBitmap sk_icon
= skia::ImageOperations::Resize(
133 source_bitmap
, skia::ImageOperations::RESIZE_LANCZOS3
, avatar_badge_size
,
134 source_bitmap
.height() * avatar_badge_size
/ source_bitmap
.width());
136 // Overlay the avatar on the icon, anchoring it to the bottom-right of the
138 scoped_ptr
<SkCanvas
> offscreen_canvas(
139 skia::CreateBitmapCanvas(app_icon_bitmap
.width(),
140 app_icon_bitmap
.height(),
142 DCHECK(offscreen_canvas
);
143 offscreen_canvas
->drawBitmap(app_icon_bitmap
, 0, 0);
144 offscreen_canvas
->drawBitmap(sk_icon
,
145 app_icon_bitmap
.width() - sk_icon
.width(),
146 app_icon_bitmap
.height() - sk_icon
.height());
147 const SkBitmap
& badged_bitmap
=
148 offscreen_canvas
->getDevice()->accessBitmap(false);
149 SkBitmap badged_bitmap_copy
;
150 badged_bitmap
.deepCopyTo(&badged_bitmap_copy
, badged_bitmap
.getConfig());
151 return badged_bitmap_copy
;
154 // Updates the preferences with the current icon version on icon creation
156 void OnProfileIconCreateSuccess(base::FilePath profile_path
) {
157 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
158 if (!g_browser_process
->profile_manager())
161 g_browser_process
->profile_manager()->GetProfileByPath(profile_path
);
163 profile
->GetPrefs()->SetInteger(prefs::kProfileIconVersion
,
164 kCurrentProfileIconVersion
);
168 // Creates a desktop shortcut icon file (.ico) on the disk for a given profile,
169 // badging the browser distribution icon with the profile avatar.
170 // Returns a path to the shortcut icon file on disk, which is empty if this
171 // fails. Use index 0 when assigning the resulting file as the icon. If both
172 // given bitmaps are empty, an unbadged icon is created.
173 // Returns the path to the created icon on success and an empty base::FilePath
175 // TODO(calamity): Ideally we'd just copy the app icon verbatim from the exe's
176 // resources in the case of an unbadged icon.
177 base::FilePath
CreateOrUpdateShortcutIconForProfile(
178 const base::FilePath
& profile_path
,
179 const SkBitmap
& avatar_bitmap_1x
,
180 const SkBitmap
& avatar_bitmap_2x
) {
181 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
183 if (!base::PathExists(profile_path
)) {
184 LOG(ERROR
) << "Profile directory " << profile_path
.value()
185 << " did not exist when trying to create profile icon";
186 return base::FilePath();
189 scoped_ptr
<SkBitmap
> app_icon_bitmap(GetAppIconForSize(kShortcutIconSize
));
190 if (!app_icon_bitmap
)
191 return base::FilePath();
193 gfx::ImageFamily badged_bitmaps
;
194 if (!avatar_bitmap_1x
.empty()) {
195 badged_bitmaps
.Add(gfx::Image::CreateFrom1xBitmap(
196 BadgeIcon(*app_icon_bitmap
, avatar_bitmap_1x
, 1)));
199 scoped_ptr
<SkBitmap
> large_app_icon_bitmap(
200 GetAppIconForSize(IconUtil::kLargeIconSize
));
201 if (large_app_icon_bitmap
&& !avatar_bitmap_2x
.empty()) {
202 badged_bitmaps
.Add(gfx::Image::CreateFrom1xBitmap(
203 BadgeIcon(*large_app_icon_bitmap
, avatar_bitmap_2x
, 2)));
206 // If we have no badged bitmaps, we should just use the default chrome icon.
207 if (badged_bitmaps
.empty()) {
208 badged_bitmaps
.Add(gfx::Image::CreateFrom1xBitmap(*app_icon_bitmap
));
209 if (large_app_icon_bitmap
) {
211 gfx::Image::CreateFrom1xBitmap(*large_app_icon_bitmap
));
214 // Finally, write the .ico file containing this new bitmap.
215 const base::FilePath icon_path
=
216 profiles::internal::GetProfileIconPath(profile_path
);
217 const bool had_icon
= base::PathExists(icon_path
);
219 if (!IconUtil::CreateIconFileFromImageFamily(badged_bitmaps
, icon_path
)) {
220 // This can happen in theory if the profile directory is deleted between the
221 // beginning of this function and here; however this is extremely unlikely
222 // and this check will help catch any regression where this call would start
223 // failing constantly.
225 return base::FilePath();
229 // This invalidates the Windows icon cache and causes the icon changes to
230 // register with the taskbar and desktop. SHCNE_ASSOCCHANGED will cause a
231 // desktop flash and we would like to avoid that if possible.
232 SHChangeNotify(SHCNE_ASSOCCHANGED
, SHCNF_IDLIST
, NULL
, NULL
);
234 SHChangeNotify(SHCNE_CREATE
, SHCNF_PATH
, icon_path
.value().c_str(), NULL
);
236 BrowserThread::PostTask(
237 BrowserThread::UI
, FROM_HERE
,
238 base::Bind(&OnProfileIconCreateSuccess
, profile_path
));
242 // Gets the user and system directories for desktop shortcuts. Parameters may
243 // be NULL if a directory type is not needed. Returns true on success.
244 bool GetDesktopShortcutsDirectories(
245 base::FilePath
* user_shortcuts_directory
,
246 base::FilePath
* system_shortcuts_directory
) {
247 BrowserDistribution
* distribution
= BrowserDistribution::GetDistribution();
248 if (user_shortcuts_directory
&&
249 !ShellUtil::GetShortcutPath(ShellUtil::SHORTCUT_LOCATION_DESKTOP
,
250 distribution
, ShellUtil::CURRENT_USER
,
251 user_shortcuts_directory
)) {
255 if (system_shortcuts_directory
&&
256 !ShellUtil::GetShortcutPath(ShellUtil::SHORTCUT_LOCATION_DESKTOP
,
257 distribution
, ShellUtil::SYSTEM_LEVEL
,
258 system_shortcuts_directory
)) {
265 // Returns the long form of |path|, which will expand any shortened components
266 // like "foo~2" to their full names.
267 base::FilePath
ConvertToLongPath(const base::FilePath
& path
) {
268 const size_t length
= GetLongPathName(path
.value().c_str(), NULL
, 0);
269 if (length
!= 0 && length
!= path
.value().length()) {
270 std::vector
<wchar_t> long_path(length
);
271 if (GetLongPathName(path
.value().c_str(), &long_path
[0], length
) != 0)
272 return base::FilePath(&long_path
[0]);
277 // Returns true if the file at |path| is a Chrome shortcut and returns its
278 // command line in output parameter |command_line|.
279 bool IsChromeShortcut(const base::FilePath
& path
,
280 const base::FilePath
& chrome_exe
,
281 base::string16
* command_line
) {
282 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
284 if (path
.Extension() != installer::kLnkExt
)
287 base::FilePath target_path
;
288 if (!base::win::ResolveShortcut(path
, &target_path
, command_line
))
290 // One of the paths may be in short (elided) form. Compare long paths to
291 // ensure these are still properly matched.
292 return ConvertToLongPath(target_path
) == ConvertToLongPath(chrome_exe
);
295 // Populates |paths| with the file paths of Chrome desktop shortcuts that have
296 // the specified |command_line|. If |include_empty_command_lines| is true,
297 // Chrome desktop shortcuts with empty command lines will also be included.
298 void ListDesktopShortcutsWithCommandLine(const base::FilePath
& chrome_exe
,
299 const base::string16
& command_line
,
300 bool include_empty_command_lines
,
301 std::vector
<base::FilePath
>* paths
) {
302 base::FilePath user_shortcuts_directory
;
303 if (!GetDesktopShortcutsDirectories(&user_shortcuts_directory
, NULL
))
306 base::FileEnumerator
enumerator(user_shortcuts_directory
, false,
307 base::FileEnumerator::FILES
);
308 for (base::FilePath path
= enumerator
.Next(); !path
.empty();
309 path
= enumerator
.Next()) {
310 base::string16 shortcut_command_line
;
311 if (!IsChromeShortcut(path
, chrome_exe
, &shortcut_command_line
))
314 // TODO(asvitkine): Change this to build a CommandLine object and ensure all
315 // args from |command_line| are present in the shortcut's CommandLine. This
316 // will be more robust when |command_line| contains multiple args.
317 if ((shortcut_command_line
.empty() && include_empty_command_lines
) ||
318 (shortcut_command_line
.find(command_line
) != base::string16::npos
)) {
319 paths
->push_back(path
);
324 // Renames the given desktop shortcut and informs the shell of this change.
325 bool RenameDesktopShortcut(const base::FilePath
& old_shortcut_path
,
326 const base::FilePath
& new_shortcut_path
) {
327 if (!base::Move(old_shortcut_path
, new_shortcut_path
))
330 // Notify the shell of the rename, which allows the icon to keep its position
331 // on the desktop when renamed. Note: This only works if either SHCNF_FLUSH or
332 // SHCNF_FLUSHNOWAIT is specified as a flag.
333 SHChangeNotify(SHCNE_RENAMEITEM
, SHCNF_PATH
| SHCNF_FLUSHNOWAIT
,
334 old_shortcut_path
.value().c_str(),
335 new_shortcut_path
.value().c_str());
339 // Renames an existing Chrome desktop profile shortcut. Must be called on the
341 void RenameChromeDesktopShortcutForProfile(
342 const base::string16
& old_shortcut_filename
,
343 const base::string16
& new_shortcut_filename
) {
344 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
346 base::FilePath user_shortcuts_directory
;
347 base::FilePath system_shortcuts_directory
;
348 if (!GetDesktopShortcutsDirectories(&user_shortcuts_directory
,
349 &system_shortcuts_directory
)) {
353 const base::FilePath old_shortcut_path
=
354 user_shortcuts_directory
.Append(old_shortcut_filename
);
355 const base::FilePath new_shortcut_path
=
356 user_shortcuts_directory
.Append(new_shortcut_filename
);
358 if (base::PathExists(old_shortcut_path
)) {
359 // Rename the old shortcut unless a system-level shortcut exists at the
360 // destination, in which case the old shortcut is simply deleted.
361 const base::FilePath possible_new_system_shortcut
=
362 system_shortcuts_directory
.Append(new_shortcut_filename
);
363 if (base::PathExists(possible_new_system_shortcut
))
364 base::DeleteFile(old_shortcut_path
, false);
365 else if (!RenameDesktopShortcut(old_shortcut_path
, new_shortcut_path
))
366 DLOG(ERROR
) << "Could not rename Windows profile desktop shortcut.";
368 // If the shortcut does not exist, it may have been renamed by the user. In
369 // that case, its name should not be changed.
370 // It's also possible that a system-level shortcut exists instead - this
371 // should only be the case for the original Chrome shortcut from an
372 // installation. If that's the case, copy that one over - it will get its
373 // properties updated by
374 // |CreateOrUpdateDesktopShortcutsAndIconForProfile()|.
375 const base::FilePath possible_old_system_shortcut
=
376 system_shortcuts_directory
.Append(old_shortcut_filename
);
377 if (base::PathExists(possible_old_system_shortcut
))
378 base::CopyFile(possible_old_system_shortcut
, new_shortcut_path
);
382 struct CreateOrUpdateShortcutsParams
{
383 CreateOrUpdateShortcutsParams(
384 base::FilePath profile_path
,
385 ProfileShortcutManagerWin::CreateOrUpdateMode create_mode
,
386 ProfileShortcutManagerWin::NonProfileShortcutAction action
)
387 : profile_path(profile_path
), create_mode(create_mode
), action(action
) {}
388 ~CreateOrUpdateShortcutsParams() {}
390 ProfileShortcutManagerWin::CreateOrUpdateMode create_mode
;
391 ProfileShortcutManagerWin::NonProfileShortcutAction action
;
393 // The path for this profile.
394 base::FilePath profile_path
;
395 // The profile name before this update. Empty on create.
396 base::string16 old_profile_name
;
397 // The new profile name.
398 base::string16 profile_name
;
399 // Avatar images for this profile.
400 SkBitmap avatar_image_1x
;
401 SkBitmap avatar_image_2x
;
404 // Updates all desktop shortcuts for the given profile to have the specified
405 // parameters. If |params.create_mode| is CREATE_WHEN_NONE_FOUND, a new shortcut
406 // is created if no existing ones were found. Whether non-profile shortcuts
407 // should be updated is specified by |params.action|. Must be called on the FILE
409 void CreateOrUpdateDesktopShortcutsAndIconForProfile(
410 const CreateOrUpdateShortcutsParams
& params
) {
411 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
413 const base::FilePath shortcut_icon
=
414 CreateOrUpdateShortcutIconForProfile(params
.profile_path
,
415 params
.avatar_image_1x
,
416 params
.avatar_image_2x
);
417 if (shortcut_icon
.empty() ||
418 params
.create_mode
==
419 ProfileShortcutManagerWin::CREATE_OR_UPDATE_ICON_ONLY
) {
423 base::FilePath chrome_exe
;
424 if (!PathService::Get(base::FILE_EXE
, &chrome_exe
)) {
429 BrowserDistribution
* distribution
= BrowserDistribution::GetDistribution();
430 // Ensure that the distribution supports creating shortcuts. If it doesn't,
431 // the following code may result in NOTREACHED() being hit.
432 DCHECK(distribution
->CanCreateDesktopShortcuts());
434 if (params
.old_profile_name
!= params
.profile_name
) {
435 const base::string16 old_shortcut_filename
=
436 profiles::internal::GetShortcutFilenameForProfile(
437 params
.old_profile_name
,
439 const base::string16 new_shortcut_filename
=
440 profiles::internal::GetShortcutFilenameForProfile(params
.profile_name
,
442 RenameChromeDesktopShortcutForProfile(old_shortcut_filename
,
443 new_shortcut_filename
);
446 ShellUtil::ShortcutProperties
properties(ShellUtil::CURRENT_USER
);
447 installer::Product
product(distribution
);
448 product
.AddDefaultShortcutProperties(chrome_exe
, &properties
);
450 const base::string16 command_line
=
451 profiles::internal::CreateProfileShortcutFlags(params
.profile_path
);
453 // Only set the profile-specific properties when |profile_name| is non empty.
454 // If it is empty, it means the shortcut being created should be a regular,
455 // non-profile Chrome shortcut.
456 if (!params
.profile_name
.empty()) {
457 properties
.set_arguments(command_line
);
458 properties
.set_icon(shortcut_icon
, 0);
460 // Set the arguments explicitly to the empty string to ensure that
461 // |ShellUtil::CreateOrUpdateShortcut| updates that part of the shortcut.
462 properties
.set_arguments(base::string16());
465 properties
.set_app_id(
466 ShellIntegration::GetChromiumModelIdForProfile(params
.profile_path
));
468 ShellUtil::ShortcutOperation operation
=
469 ShellUtil::SHELL_SHORTCUT_REPLACE_EXISTING
;
471 std::vector
<base::FilePath
> shortcuts
;
472 ListDesktopShortcutsWithCommandLine(chrome_exe
, command_line
,
473 params
.action
== ProfileShortcutManagerWin::UPDATE_NON_PROFILE_SHORTCUTS
,
475 if (params
.create_mode
== ProfileShortcutManagerWin::CREATE_WHEN_NONE_FOUND
&&
477 const base::string16 shortcut_name
=
478 profiles::internal::GetShortcutFilenameForProfile(params
.profile_name
,
480 shortcuts
.push_back(base::FilePath(shortcut_name
));
481 operation
= ShellUtil::SHELL_SHORTCUT_CREATE_IF_NO_SYSTEM_LEVEL
;
484 for (size_t i
= 0; i
< shortcuts
.size(); ++i
) {
485 const base::FilePath shortcut_name
=
486 shortcuts
[i
].BaseName().RemoveExtension();
487 properties
.set_shortcut_name(shortcut_name
.value());
488 ShellUtil::CreateOrUpdateShortcut(ShellUtil::SHORTCUT_LOCATION_DESKTOP
,
489 distribution
, properties
, operation
);
493 // Returns true if any desktop shortcuts exist with target |chrome_exe|,
494 // regardless of their command line arguments.
495 bool ChromeDesktopShortcutsExist(const base::FilePath
& chrome_exe
) {
496 base::FilePath user_shortcuts_directory
;
497 if (!GetDesktopShortcutsDirectories(&user_shortcuts_directory
, NULL
))
500 base::FileEnumerator
enumerator(user_shortcuts_directory
, false,
501 base::FileEnumerator::FILES
);
502 for (base::FilePath path
= enumerator
.Next(); !path
.empty();
503 path
= enumerator
.Next()) {
504 if (IsChromeShortcut(path
, chrome_exe
, NULL
))
511 // Deletes all desktop shortcuts for the specified profile. If
512 // |ensure_shortcuts_remain| is true, then a regular non-profile shortcut will
513 // be created if this function would otherwise delete the last Chrome desktop
514 // shortcut(s). Must be called on the FILE thread.
515 void DeleteDesktopShortcuts(const base::FilePath
& profile_path
,
516 bool ensure_shortcuts_remain
) {
517 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
519 base::FilePath chrome_exe
;
520 if (!PathService::Get(base::FILE_EXE
, &chrome_exe
)) {
525 const base::string16 command_line
=
526 profiles::internal::CreateProfileShortcutFlags(profile_path
);
527 std::vector
<base::FilePath
> shortcuts
;
528 ListDesktopShortcutsWithCommandLine(chrome_exe
, command_line
, false,
531 for (size_t i
= 0; i
< shortcuts
.size(); ++i
) {
532 // Use base::DeleteFile() instead of ShellUtil::RemoveShortcuts(), as the
533 // latter causes non-profile taskbar shortcuts to be removed since it
534 // doesn't consider the command-line of the shortcuts it deletes.
535 // TODO(huangs): Refactor with ShellUtil::RemoveShortcuts().
536 base::win::TaskbarUnpinShortcutLink(shortcuts
[i
].value().c_str());
537 base::DeleteFile(shortcuts
[i
], false);
538 // Notify the shell that the shortcut was deleted to ensure desktop refresh.
539 SHChangeNotify(SHCNE_DELETE
, SHCNF_PATH
, shortcuts
[i
].value().c_str(),
543 // If |ensure_shortcuts_remain| is true and deleting this profile caused the
544 // last shortcuts to be removed, re-create a regular non-profile shortcut.
545 const bool had_shortcuts
= !shortcuts
.empty();
546 if (ensure_shortcuts_remain
&& had_shortcuts
&&
547 !ChromeDesktopShortcutsExist(chrome_exe
)) {
548 BrowserDistribution
* distribution
= BrowserDistribution::GetDistribution();
549 // Ensure that the distribution supports creating shortcuts. If it doesn't,
550 // the following code may result in NOTREACHED() being hit.
551 DCHECK(distribution
->CanCreateDesktopShortcuts());
552 installer::Product
product(distribution
);
554 ShellUtil::ShortcutProperties
properties(ShellUtil::CURRENT_USER
);
555 product
.AddDefaultShortcutProperties(chrome_exe
, &properties
);
556 properties
.set_shortcut_name(
557 profiles::internal::GetShortcutFilenameForProfile(base::string16(),
559 ShellUtil::CreateOrUpdateShortcut(
560 ShellUtil::SHORTCUT_LOCATION_DESKTOP
, distribution
, properties
,
561 ShellUtil::SHELL_SHORTCUT_CREATE_IF_NO_SYSTEM_LEVEL
);
565 // Returns true if profile at |profile_path| has any shortcuts. Does not
566 // consider non-profile shortcuts. Must be called on the FILE thread.
567 bool HasAnyProfileShortcuts(const base::FilePath
& profile_path
) {
568 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
570 base::FilePath chrome_exe
;
571 if (!PathService::Get(base::FILE_EXE
, &chrome_exe
)) {
576 const base::string16 command_line
=
577 profiles::internal::CreateProfileShortcutFlags(profile_path
);
578 std::vector
<base::FilePath
> shortcuts
;
579 ListDesktopShortcutsWithCommandLine(chrome_exe
, command_line
, false,
581 return !shortcuts
.empty();
584 // Replaces any reserved characters with spaces, and trims the resulting string
585 // to prevent any leading and trailing spaces. Also makes sure that the
586 // resulting filename doesn't exceed |kMaxProfileShortcutFileNameLength|.
587 // TODO(macourteau): find a way to limit the total path's length to MAX_PATH
588 // instead of limiting the profile's name to |kMaxProfileShortcutFileNameLength|
590 base::string16
SanitizeShortcutProfileNameString(
591 const base::string16
& profile_name
) {
592 base::string16 sanitized
= profile_name
;
593 size_t pos
= sanitized
.find_first_of(kReservedCharacters
);
594 while (pos
!= base::string16::npos
) {
595 sanitized
[pos
] = L
' ';
596 pos
= sanitized
.find_first_of(kReservedCharacters
, pos
+ 1);
599 TrimWhitespace(sanitized
, TRIM_LEADING
, &sanitized
);
600 if (sanitized
.size() > kMaxProfileShortcutFileNameLength
)
601 sanitized
.erase(kMaxProfileShortcutFileNameLength
);
602 TrimWhitespace(sanitized
, TRIM_TRAILING
, &sanitized
);
607 // Returns a copied SkBitmap for the given resource id that can be safely passed
608 // to another thread.
609 SkBitmap
GetImageResourceSkBitmapCopy(int resource_id
) {
610 const gfx::Image image
=
611 ResourceBundle::GetSharedInstance().GetNativeImageNamed(resource_id
);
612 DCHECK(!image
.IsEmpty());
614 const SkBitmap
* image_bitmap
= image
.ToSkBitmap();
615 SkBitmap bitmap_copy
;
616 image_bitmap
->deepCopyTo(&bitmap_copy
, image_bitmap
->getConfig());
625 base::FilePath
GetProfileIconPath(const base::FilePath
& profile_path
) {
626 return profile_path
.AppendASCII(kProfileIconFileName
);
629 base::string16
GetShortcutFilenameForProfile(
630 const base::string16
& profile_name
,
631 BrowserDistribution
* distribution
) {
632 base::string16 shortcut_name
;
633 if (!profile_name
.empty()) {
634 shortcut_name
.append(SanitizeShortcutProfileNameString(profile_name
));
635 shortcut_name
.append(L
" - ");
636 shortcut_name
.append(l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME
));
638 shortcut_name
.append(
639 distribution
->GetShortcutName(BrowserDistribution::SHORTCUT_CHROME
));
641 return shortcut_name
+ installer::kLnkExt
;
644 base::string16
CreateProfileShortcutFlags(const base::FilePath
& profile_path
) {
645 return base::StringPrintf(L
"--%ls=\"%ls\"",
647 switches::kProfileDirectory
).c_str(),
648 profile_path
.BaseName().value().c_str());
651 } // namespace internal
652 } // namespace profiles
655 bool ProfileShortcutManager::IsFeatureEnabled() {
656 CommandLine
* command_line
= CommandLine::ForCurrentProcess();
657 return command_line
->HasSwitch(switches::kEnableProfileShortcutManager
) ||
658 (BrowserDistribution::GetDistribution()->CanCreateDesktopShortcuts() &&
659 !command_line
->HasSwitch(switches::kUserDataDir
));
663 ProfileShortcutManager
* ProfileShortcutManager::Create(
664 ProfileManager
* manager
) {
665 return new ProfileShortcutManagerWin(manager
);
668 ProfileShortcutManagerWin::ProfileShortcutManagerWin(ProfileManager
* manager
)
669 : profile_manager_(manager
) {
671 arraysize(kProfileAvatarIconResources2x
),
672 profile_manager_
->GetProfileInfoCache().GetDefaultAvatarIconCount());
674 registrar_
.Add(this, chrome::NOTIFICATION_PROFILE_CREATED
,
675 content::NotificationService::AllSources());
677 profile_manager_
->GetProfileInfoCache().AddObserver(this);
680 ProfileShortcutManagerWin::~ProfileShortcutManagerWin() {
681 profile_manager_
->GetProfileInfoCache().RemoveObserver(this);
684 void ProfileShortcutManagerWin::CreateOrUpdateProfileIcon(
685 const base::FilePath
& profile_path
) {
686 CreateOrUpdateShortcutsForProfileAtPath(profile_path
,
687 CREATE_OR_UPDATE_ICON_ONLY
,
688 IGNORE_NON_PROFILE_SHORTCUTS
);
691 void ProfileShortcutManagerWin::CreateProfileShortcut(
692 const base::FilePath
& profile_path
) {
693 CreateOrUpdateShortcutsForProfileAtPath(profile_path
, CREATE_WHEN_NONE_FOUND
,
694 IGNORE_NON_PROFILE_SHORTCUTS
);
697 void ProfileShortcutManagerWin::RemoveProfileShortcuts(
698 const base::FilePath
& profile_path
) {
699 BrowserThread::PostTask(
700 BrowserThread::FILE, FROM_HERE
,
701 base::Bind(&DeleteDesktopShortcuts
, profile_path
, false));
704 void ProfileShortcutManagerWin::HasProfileShortcuts(
705 const base::FilePath
& profile_path
,
706 const base::Callback
<void(bool)>& callback
) {
707 BrowserThread::PostTaskAndReplyWithResult(
708 BrowserThread::FILE, FROM_HERE
,
709 base::Bind(&HasAnyProfileShortcuts
, profile_path
), callback
);
712 void ProfileShortcutManagerWin::GetShortcutProperties(
713 const base::FilePath
& profile_path
,
714 CommandLine
* command_line
,
715 base::string16
* name
,
716 base::FilePath
* icon_path
) {
717 base::FilePath chrome_exe
;
718 if (!PathService::Get(base::FILE_EXE
, &chrome_exe
)) {
723 const ProfileInfoCache
& cache
= profile_manager_
->GetProfileInfoCache();
724 size_t profile_index
= cache
.GetIndexOfProfileWithPath(profile_path
);
725 DCHECK_LT(profile_index
, cache
.GetNumberOfProfiles());
727 // The used profile name should be empty if there is only 1 profile.
728 base::string16 shortcut_profile_name
;
729 if (cache
.GetNumberOfProfiles() > 1)
730 shortcut_profile_name
= cache
.GetNameOfProfileAtIndex(profile_index
);
732 *name
= base::FilePath(profiles::internal::GetShortcutFilenameForProfile(
733 shortcut_profile_name
,
734 BrowserDistribution::GetDistribution())).RemoveExtension().value();
736 command_line
->ParseFromString(L
"\"" + chrome_exe
.value() + L
"\" " +
737 profiles::internal::CreateProfileShortcutFlags(profile_path
));
739 *icon_path
= profiles::internal::GetProfileIconPath(profile_path
);
742 void ProfileShortcutManagerWin::OnProfileAdded(
743 const base::FilePath
& profile_path
) {
744 CreateOrUpdateProfileIcon(profile_path
);
745 if (profile_manager_
->GetProfileInfoCache().GetNumberOfProfiles() == 2) {
746 // When the second profile is added, make existing non-profile shortcuts
747 // point to the first profile and be badged/named appropriately.
748 CreateOrUpdateShortcutsForProfileAtPath(GetOtherProfilePath(profile_path
),
749 UPDATE_EXISTING_ONLY
,
750 UPDATE_NON_PROFILE_SHORTCUTS
);
754 void ProfileShortcutManagerWin::OnProfileWasRemoved(
755 const base::FilePath
& profile_path
,
756 const base::string16
& profile_name
) {
757 const ProfileInfoCache
& cache
= profile_manager_
->GetProfileInfoCache();
758 // If there is only one profile remaining, remove the badging information
759 // from an existing shortcut.
760 const bool deleting_down_to_last_profile
= (cache
.GetNumberOfProfiles() == 1);
761 if (deleting_down_to_last_profile
) {
762 // This is needed to unbadge the icon.
763 CreateOrUpdateShortcutsForProfileAtPath(cache
.GetPathOfProfileAtIndex(0),
764 UPDATE_EXISTING_ONLY
,
765 IGNORE_NON_PROFILE_SHORTCUTS
);
768 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE
,
769 base::Bind(&DeleteDesktopShortcuts
,
771 deleting_down_to_last_profile
));
774 void ProfileShortcutManagerWin::OnProfileNameChanged(
775 const base::FilePath
& profile_path
,
776 const base::string16
& old_profile_name
) {
777 CreateOrUpdateShortcutsForProfileAtPath(profile_path
, UPDATE_EXISTING_ONLY
,
778 IGNORE_NON_PROFILE_SHORTCUTS
);
781 void ProfileShortcutManagerWin::OnProfileAvatarChanged(
782 const base::FilePath
& profile_path
) {
783 CreateOrUpdateProfileIcon(profile_path
);
786 base::FilePath
ProfileShortcutManagerWin::GetOtherProfilePath(
787 const base::FilePath
& profile_path
) {
788 const ProfileInfoCache
& cache
= profile_manager_
->GetProfileInfoCache();
789 DCHECK_EQ(2U, cache
.GetNumberOfProfiles());
790 // Get the index of the current profile, in order to find the index of the
792 size_t current_profile_index
= cache
.GetIndexOfProfileWithPath(profile_path
);
793 size_t other_profile_index
= (current_profile_index
== 0) ? 1 : 0;
794 return cache
.GetPathOfProfileAtIndex(other_profile_index
);
797 void ProfileShortcutManagerWin::CreateOrUpdateShortcutsForProfileAtPath(
798 const base::FilePath
& profile_path
,
799 CreateOrUpdateMode create_mode
,
800 NonProfileShortcutAction action
) {
801 DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::UI
) ||
802 BrowserThread::CurrentlyOn(BrowserThread::UI
));
803 CreateOrUpdateShortcutsParams
params(profile_path
, create_mode
, action
);
805 ProfileInfoCache
* cache
= &profile_manager_
->GetProfileInfoCache();
806 size_t profile_index
= cache
->GetIndexOfProfileWithPath(profile_path
);
807 if (profile_index
== std::string::npos
)
809 bool remove_badging
= cache
->GetNumberOfProfiles() == 1;
811 params
.old_profile_name
=
812 cache
->GetShortcutNameOfProfileAtIndex(profile_index
);
814 // Exit early if the mode is to update existing profile shortcuts only and
815 // none were ever created for this profile, per the shortcut name not being
816 // set in the profile info cache.
817 if (params
.old_profile_name
.empty() &&
818 create_mode
== UPDATE_EXISTING_ONLY
&&
819 action
== IGNORE_NON_PROFILE_SHORTCUTS
) {
823 if (!remove_badging
) {
824 params
.profile_name
= cache
->GetNameOfProfileAtIndex(profile_index
);
826 const size_t icon_index
=
827 cache
->GetAvatarIconIndexOfProfileAtIndex(profile_index
);
828 const int resource_id_1x
=
829 cache
->GetDefaultAvatarIconResourceIDAtIndex(icon_index
);
830 const int resource_id_2x
= kProfileAvatarIconResources2x
[icon_index
];
831 // Make a copy of the SkBitmaps to ensure that we can safely use the image
832 // data on the FILE thread.
833 params
.avatar_image_1x
= GetImageResourceSkBitmapCopy(resource_id_1x
);
834 params
.avatar_image_2x
= GetImageResourceSkBitmapCopy(resource_id_2x
);
836 BrowserThread::PostTask(
837 BrowserThread::FILE, FROM_HERE
,
838 base::Bind(&CreateOrUpdateDesktopShortcutsAndIconForProfile
, params
));
840 cache
->SetShortcutNameOfProfileAtIndex(profile_index
,
841 params
.profile_name
);
844 void ProfileShortcutManagerWin::Observe(
846 const content::NotificationSource
& source
,
847 const content::NotificationDetails
& details
) {
849 // This notification is triggered when a profile is loaded.
850 case chrome::NOTIFICATION_PROFILE_CREATED
: {
852 content::Source
<Profile
>(source
).ptr()->GetOriginalProfile();
853 if (profile
->GetPrefs()->GetInteger(prefs::kProfileIconVersion
) <
854 kCurrentProfileIconVersion
) {
855 // Ensure the profile's icon file has been created.
856 CreateOrUpdateProfileIcon(profile
->GetPath());