Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / profiles / profile_shortcut_manager_win.cc
blob7b29dc112ad5c5c48cc45605cbd05d5a8b4843bc
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().
9 #include <string>
10 #include <vector>
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;
51 namespace {
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,
112 int scale_factor) {
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));
124 } else {
125 NOTREACHED();
127 int avatar_badge_size = kProfileAvatarBadgeSize;
128 if (app_icon_bitmap.width() != kShortcutIconSize) {
129 avatar_badge_size =
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
137 // icon.
138 scoped_ptr<SkCanvas> offscreen_canvas(
139 skia::CreateBitmapCanvas(app_icon_bitmap.width(),
140 app_icon_bitmap.height(),
141 false));
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
155 // success.
156 void OnProfileIconCreateSuccess(base::FilePath profile_path) {
157 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
158 if (!g_browser_process->profile_manager())
159 return;
160 Profile* profile =
161 g_browser_process->profile_manager()->GetProfileByPath(profile_path);
162 if (profile) {
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
174 // on failure.
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) {
210 badged_bitmaps.Add(
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.
224 NOTREACHED();
225 return base::FilePath();
228 if (had_icon) {
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);
233 } else {
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));
239 return icon_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)) {
252 NOTREACHED();
253 return false;
255 if (system_shortcuts_directory &&
256 !ShellUtil::GetShortcutPath(ShellUtil::SHORTCUT_LOCATION_DESKTOP,
257 distribution, ShellUtil::SYSTEM_LEVEL,
258 system_shortcuts_directory)) {
259 NOTREACHED();
260 return false;
262 return true;
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]);
274 return path;
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)
285 return false;
287 base::FilePath target_path;
288 if (!base::win::ResolveShortcut(path, &target_path, command_line))
289 return false;
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))
304 return;
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))
312 continue;
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))
328 return false;
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());
336 return true;
339 // Renames an existing Chrome desktop profile shortcut. Must be called on the
340 // FILE thread.
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)) {
350 return;
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.";
367 } else {
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
408 // thread.
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) {
420 return;
423 base::FilePath chrome_exe;
424 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
425 NOTREACHED();
426 return;
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,
438 distribution);
439 const base::string16 new_shortcut_filename =
440 profiles::internal::GetShortcutFilenameForProfile(params.profile_name,
441 distribution);
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);
459 } else {
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,
474 &shortcuts);
475 if (params.create_mode == ProfileShortcutManagerWin::CREATE_WHEN_NONE_FOUND &&
476 shortcuts.empty()) {
477 const base::string16 shortcut_name =
478 profiles::internal::GetShortcutFilenameForProfile(params.profile_name,
479 distribution);
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))
498 return false;
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))
505 return true;
508 return false;
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)) {
521 NOTREACHED();
522 return;
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,
529 &shortcuts);
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(),
540 NULL);
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(),
558 distribution));
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)) {
572 NOTREACHED();
573 return false;
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,
580 &shortcuts);
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|
589 // characters.
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);
604 return 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());
617 return bitmap_copy;
620 } // namespace
622 namespace profiles {
623 namespace internal {
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));
637 } else {
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\"",
646 base::ASCIIToUTF16(
647 switches::kProfileDirectory).c_str(),
648 profile_path.BaseName().value().c_str());
651 } // namespace internal
652 } // namespace profiles
654 // static
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));
662 // static
663 ProfileShortcutManager* ProfileShortcutManager::Create(
664 ProfileManager* manager) {
665 return new ProfileShortcutManagerWin(manager);
668 ProfileShortcutManagerWin::ProfileShortcutManagerWin(ProfileManager* manager)
669 : profile_manager_(manager) {
670 DCHECK_EQ(
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)) {
719 NOTREACHED();
720 return;
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,
770 profile_path,
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
791 // other profile.
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)
808 return;
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) {
820 return;
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(
845 int type,
846 const content::NotificationSource& source,
847 const content::NotificationDetails& details) {
848 switch (type) {
849 // This notification is triggered when a profile is loaded.
850 case chrome::NOTIFICATION_PROFILE_CREATED: {
851 Profile* profile =
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());
858 break;
860 default:
861 NOTREACHED();
862 break;