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