• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/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"
48 
49 using content::BrowserThread;
50 
51 namespace {
52 
53 // Name of the badged icon file generated for a given profile.
54 const char kProfileIconFileName[] = "Google Profile.ico";
55 
56 // Characters that are not allowed in Windows filenames. Taken from
57 // http://msdn.microsoft.com/en-us/library/aa365247.aspx
58 const char16 kReservedCharacters[] = L"<>:\"/\\|?*\x01\x02\x03\x04\x05\x06\x07"
59     L"\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19"
60     L"\x1A\x1B\x1C\x1D\x1E\x1F";
61 
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;
67 
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;
74 
75 const int kCurrentProfileIconVersion = 2;
76 
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,
106 };
107 
108 // Badges |app_icon_bitmap| with |avatar_bitmap| at the bottom right corner and
109 // returns the resulting SkBitmap.
BadgeIcon(const SkBitmap & app_icon_bitmap,const SkBitmap & avatar_bitmap,int scale_factor)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();
126   }
127   int avatar_badge_size = kProfileAvatarBadgeSize;
128   if (app_icon_bitmap.width() != kShortcutIconSize) {
129     avatar_badge_size =
130         app_icon_bitmap.width() * kProfileAvatarBadgeSize / kShortcutIconSize;
131   }
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());
135 
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;
152 }
153 
154 // Updates the preferences with the current icon version on icon creation
155 // success.
OnProfileIconCreateSuccess(base::FilePath profile_path)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);
165   }
166 }
167 
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.
CreateOrUpdateShortcutIconForProfile(const base::FilePath & profile_path,const SkBitmap & avatar_bitmap_1x,const SkBitmap & avatar_bitmap_2x)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));
182 
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();
187   }
188 
189   scoped_ptr<SkBitmap> app_icon_bitmap(GetAppIconForSize(kShortcutIconSize));
190   if (!app_icon_bitmap)
191     return base::FilePath();
192 
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)));
197   }
198 
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)));
204   }
205 
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));
212     }
213   }
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);
218 
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();
226   }
227 
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);
235   }
236   BrowserThread::PostTask(
237       BrowserThread::UI, FROM_HERE,
238       base::Bind(&OnProfileIconCreateSuccess, profile_path));
239   return icon_path;
240 }
241 
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.
GetDesktopShortcutsDirectories(base::FilePath * user_shortcuts_directory,base::FilePath * system_shortcuts_directory)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;
254   }
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;
261   }
262   return true;
263 }
264 
265 // Returns the long form of |path|, which will expand any shortened components
266 // like "foo~2" to their full names.
ConvertToLongPath(const base::FilePath & path)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]);
273   }
274   return path;
275 }
276 
277 // Returns true if the file at |path| is a Chrome shortcut and returns its
278 // command line in output parameter |command_line|.
IsChromeShortcut(const base::FilePath & path,const base::FilePath & chrome_exe,base::string16 * 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));
283 
284   if (path.Extension() != installer::kLnkExt)
285     return false;
286 
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);
293 }
294 
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.
ListDesktopShortcutsWithCommandLine(const base::FilePath & chrome_exe,const base::string16 & command_line,bool include_empty_command_lines,std::vector<base::FilePath> * paths)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;
305 
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;
313 
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);
320     }
321   }
322 }
323 
324 // Renames the given desktop shortcut and informs the shell of this change.
RenameDesktopShortcut(const base::FilePath & old_shortcut_path,const base::FilePath & new_shortcut_path)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;
329 
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;
337 }
338 
339 // Renames an existing Chrome desktop profile shortcut. Must be called on the
340 // FILE thread.
RenameChromeDesktopShortcutForProfile(const base::string16 & old_shortcut_filename,const base::string16 & new_shortcut_filename)341 void RenameChromeDesktopShortcutForProfile(
342     const base::string16& old_shortcut_filename,
343     const base::string16& new_shortcut_filename) {
344   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
345 
346   base::FilePath user_shortcuts_directory;
347   base::FilePath system_shortcuts_directory;
348   if (!GetDesktopShortcutsDirectories(&user_shortcuts_directory,
349                                       &system_shortcuts_directory)) {
350     return;
351   }
352 
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);
357 
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);
379   }
380 }
381 
382 struct CreateOrUpdateShortcutsParams {
CreateOrUpdateShortcutsParams__anon265c980f0111::CreateOrUpdateShortcutsParams383   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) {}
~CreateOrUpdateShortcutsParams__anon265c980f0111::CreateOrUpdateShortcutsParams388   ~CreateOrUpdateShortcutsParams() {}
389 
390   ProfileShortcutManagerWin::CreateOrUpdateMode create_mode;
391   ProfileShortcutManagerWin::NonProfileShortcutAction action;
392 
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;
402 };
403 
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.
CreateOrUpdateDesktopShortcutsAndIconForProfile(const CreateOrUpdateShortcutsParams & params)409 void CreateOrUpdateDesktopShortcutsAndIconForProfile(
410     const CreateOrUpdateShortcutsParams& params) {
411   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
412 
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;
421   }
422 
423   base::FilePath chrome_exe;
424   if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
425     NOTREACHED();
426     return;
427   }
428 
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());
433 
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);
444   }
445 
446   ShellUtil::ShortcutProperties properties(ShellUtil::CURRENT_USER);
447   installer::Product product(distribution);
448   product.AddDefaultShortcutProperties(chrome_exe, &properties);
449 
450   const base::string16 command_line =
451       profiles::internal::CreateProfileShortcutFlags(params.profile_path);
452 
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());
463   }
464 
465   properties.set_app_id(
466       ShellIntegration::GetChromiumModelIdForProfile(params.profile_path));
467 
468   ShellUtil::ShortcutOperation operation =
469       ShellUtil::SHELL_SHORTCUT_REPLACE_EXISTING;
470 
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;
482   }
483 
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);
490   }
491 }
492 
493 // Returns true if any desktop shortcuts exist with target |chrome_exe|,
494 // regardless of their command line arguments.
ChromeDesktopShortcutsExist(const base::FilePath & chrome_exe)495 bool ChromeDesktopShortcutsExist(const base::FilePath& chrome_exe) {
496   base::FilePath user_shortcuts_directory;
497   if (!GetDesktopShortcutsDirectories(&user_shortcuts_directory, NULL))
498     return false;
499 
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;
506   }
507 
508   return false;
509 }
510 
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.
DeleteDesktopShortcuts(const base::FilePath & profile_path,bool ensure_shortcuts_remain)515 void DeleteDesktopShortcuts(const base::FilePath& profile_path,
516                             bool ensure_shortcuts_remain) {
517   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
518 
519   base::FilePath chrome_exe;
520   if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
521     NOTREACHED();
522     return;
523   }
524 
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);
530 
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);
541   }
542 
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);
553 
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);
562   }
563 }
564 
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.
HasAnyProfileShortcuts(const base::FilePath & profile_path)567 bool HasAnyProfileShortcuts(const base::FilePath& profile_path) {
568   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
569 
570   base::FilePath chrome_exe;
571   if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
572     NOTREACHED();
573     return false;
574   }
575 
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();
582 }
583 
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.
SanitizeShortcutProfileNameString(const base::string16 & profile_name)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);
597   }
598 
599   TrimWhitespace(sanitized, TRIM_LEADING, &sanitized);
600   if (sanitized.size() > kMaxProfileShortcutFileNameLength)
601     sanitized.erase(kMaxProfileShortcutFileNameLength);
602   TrimWhitespace(sanitized, TRIM_TRAILING, &sanitized);
603 
604   return sanitized;
605 }
606 
607 // Returns a copied SkBitmap for the given resource id that can be safely passed
608 // to another thread.
GetImageResourceSkBitmapCopy(int resource_id)609 SkBitmap GetImageResourceSkBitmapCopy(int resource_id) {
610   const gfx::Image image =
611       ResourceBundle::GetSharedInstance().GetNativeImageNamed(resource_id);
612   DCHECK(!image.IsEmpty());
613 
614   const SkBitmap* image_bitmap = image.ToSkBitmap();
615   SkBitmap bitmap_copy;
616   image_bitmap->deepCopyTo(&bitmap_copy, image_bitmap->getConfig());
617   return bitmap_copy;
618 }
619 
620 }  // namespace
621 
622 namespace profiles {
623 namespace internal {
624 
GetProfileIconPath(const base::FilePath & profile_path)625 base::FilePath GetProfileIconPath(const base::FilePath& profile_path) {
626   return profile_path.AppendASCII(kProfileIconFileName);
627 }
628 
GetShortcutFilenameForProfile(const base::string16 & profile_name,BrowserDistribution * distribution)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));
640   }
641   return shortcut_name + installer::kLnkExt;
642 }
643 
CreateProfileShortcutFlags(const base::FilePath & profile_path)644 base::string16 CreateProfileShortcutFlags(const base::FilePath& profile_path) {
645   return base::StringPrintf(L"--%ls=\"%ls\"",
646                             ASCIIToUTF16(switches::kProfileDirectory).c_str(),
647                             profile_path.BaseName().value().c_str());
648 }
649 
650 }  // namespace internal
651 }  // namespace profiles
652 
653 // static
IsFeatureEnabled()654 bool ProfileShortcutManager::IsFeatureEnabled() {
655   CommandLine* command_line = CommandLine::ForCurrentProcess();
656   return command_line->HasSwitch(switches::kEnableProfileShortcutManager) ||
657          (BrowserDistribution::GetDistribution()->CanCreateDesktopShortcuts() &&
658           !command_line->HasSwitch(switches::kUserDataDir));
659 }
660 
661 // static
Create(ProfileManager * manager)662 ProfileShortcutManager* ProfileShortcutManager::Create(
663     ProfileManager* manager) {
664   return new ProfileShortcutManagerWin(manager);
665 }
666 
ProfileShortcutManagerWin(ProfileManager * manager)667 ProfileShortcutManagerWin::ProfileShortcutManagerWin(ProfileManager* manager)
668     : profile_manager_(manager) {
669   DCHECK_EQ(
670       arraysize(kProfileAvatarIconResources2x),
671       profile_manager_->GetProfileInfoCache().GetDefaultAvatarIconCount());
672 
673   registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CREATED,
674                  content::NotificationService::AllSources());
675 
676   profile_manager_->GetProfileInfoCache().AddObserver(this);
677 }
678 
~ProfileShortcutManagerWin()679 ProfileShortcutManagerWin::~ProfileShortcutManagerWin() {
680   profile_manager_->GetProfileInfoCache().RemoveObserver(this);
681 }
682 
CreateOrUpdateProfileIcon(const base::FilePath & profile_path)683 void ProfileShortcutManagerWin::CreateOrUpdateProfileIcon(
684     const base::FilePath& profile_path) {
685   CreateOrUpdateShortcutsForProfileAtPath(profile_path,
686                                           CREATE_OR_UPDATE_ICON_ONLY,
687                                           IGNORE_NON_PROFILE_SHORTCUTS);
688 }
689 
CreateProfileShortcut(const base::FilePath & profile_path)690 void ProfileShortcutManagerWin::CreateProfileShortcut(
691     const base::FilePath& profile_path) {
692   CreateOrUpdateShortcutsForProfileAtPath(profile_path, CREATE_WHEN_NONE_FOUND,
693                                           IGNORE_NON_PROFILE_SHORTCUTS);
694 }
695 
RemoveProfileShortcuts(const base::FilePath & profile_path)696 void ProfileShortcutManagerWin::RemoveProfileShortcuts(
697     const base::FilePath& profile_path) {
698   BrowserThread::PostTask(
699       BrowserThread::FILE, FROM_HERE,
700       base::Bind(&DeleteDesktopShortcuts, profile_path, false));
701 }
702 
HasProfileShortcuts(const base::FilePath & profile_path,const base::Callback<void (bool)> & callback)703 void ProfileShortcutManagerWin::HasProfileShortcuts(
704     const base::FilePath& profile_path,
705     const base::Callback<void(bool)>& callback) {
706   BrowserThread::PostTaskAndReplyWithResult(
707       BrowserThread::FILE, FROM_HERE,
708       base::Bind(&HasAnyProfileShortcuts, profile_path), callback);
709 }
710 
GetShortcutProperties(const base::FilePath & profile_path,CommandLine * command_line,base::string16 * name,base::FilePath * icon_path)711 void ProfileShortcutManagerWin::GetShortcutProperties(
712     const base::FilePath& profile_path,
713     CommandLine* command_line,
714     base::string16* name,
715     base::FilePath* icon_path) {
716   base::FilePath chrome_exe;
717   if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
718     NOTREACHED();
719     return;
720   }
721 
722   const ProfileInfoCache& cache = profile_manager_->GetProfileInfoCache();
723   size_t profile_index = cache.GetIndexOfProfileWithPath(profile_path);
724   DCHECK_LT(profile_index, cache.GetNumberOfProfiles());
725 
726   // The used profile name should be empty if there is only 1 profile.
727   base::string16 shortcut_profile_name;
728   if (cache.GetNumberOfProfiles() > 1)
729     shortcut_profile_name = cache.GetNameOfProfileAtIndex(profile_index);
730 
731   *name = base::FilePath(profiles::internal::GetShortcutFilenameForProfile(
732       shortcut_profile_name,
733       BrowserDistribution::GetDistribution())).RemoveExtension().value();
734 
735   command_line->ParseFromString(L"\"" + chrome_exe.value() + L"\" " +
736       profiles::internal::CreateProfileShortcutFlags(profile_path));
737 
738   *icon_path = profiles::internal::GetProfileIconPath(profile_path);
739 }
740 
OnProfileAdded(const base::FilePath & profile_path)741 void ProfileShortcutManagerWin::OnProfileAdded(
742     const base::FilePath& profile_path) {
743   CreateOrUpdateProfileIcon(profile_path);
744   if (profile_manager_->GetProfileInfoCache().GetNumberOfProfiles() == 2) {
745     // When the second profile is added, make existing non-profile shortcuts
746     // point to the first profile and be badged/named appropriately.
747     CreateOrUpdateShortcutsForProfileAtPath(GetOtherProfilePath(profile_path),
748                                             UPDATE_EXISTING_ONLY,
749                                             UPDATE_NON_PROFILE_SHORTCUTS);
750   }
751 }
752 
OnProfileWasRemoved(const base::FilePath & profile_path,const base::string16 & profile_name)753 void ProfileShortcutManagerWin::OnProfileWasRemoved(
754     const base::FilePath& profile_path,
755     const base::string16& profile_name) {
756   const ProfileInfoCache& cache = profile_manager_->GetProfileInfoCache();
757   // If there is only one profile remaining, remove the badging information
758   // from an existing shortcut.
759   const bool deleting_down_to_last_profile = (cache.GetNumberOfProfiles() == 1);
760   if (deleting_down_to_last_profile) {
761     // This is needed to unbadge the icon.
762     CreateOrUpdateShortcutsForProfileAtPath(cache.GetPathOfProfileAtIndex(0),
763                                             UPDATE_EXISTING_ONLY,
764                                             IGNORE_NON_PROFILE_SHORTCUTS);
765   }
766 
767   BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
768                           base::Bind(&DeleteDesktopShortcuts,
769                                      profile_path,
770                                      deleting_down_to_last_profile));
771 }
772 
OnProfileNameChanged(const base::FilePath & profile_path,const base::string16 & old_profile_name)773 void ProfileShortcutManagerWin::OnProfileNameChanged(
774     const base::FilePath& profile_path,
775     const base::string16& old_profile_name) {
776   CreateOrUpdateShortcutsForProfileAtPath(profile_path, UPDATE_EXISTING_ONLY,
777                                           IGNORE_NON_PROFILE_SHORTCUTS);
778 }
779 
OnProfileAvatarChanged(const base::FilePath & profile_path)780 void ProfileShortcutManagerWin::OnProfileAvatarChanged(
781     const base::FilePath& profile_path) {
782   CreateOrUpdateProfileIcon(profile_path);
783 }
784 
GetOtherProfilePath(const base::FilePath & profile_path)785 base::FilePath ProfileShortcutManagerWin::GetOtherProfilePath(
786     const base::FilePath& profile_path) {
787   const ProfileInfoCache& cache = profile_manager_->GetProfileInfoCache();
788   DCHECK_EQ(2U, cache.GetNumberOfProfiles());
789   // Get the index of the current profile, in order to find the index of the
790   // other profile.
791   size_t current_profile_index = cache.GetIndexOfProfileWithPath(profile_path);
792   size_t other_profile_index = (current_profile_index == 0) ? 1 : 0;
793   return cache.GetPathOfProfileAtIndex(other_profile_index);
794 }
795 
CreateOrUpdateShortcutsForProfileAtPath(const base::FilePath & profile_path,CreateOrUpdateMode create_mode,NonProfileShortcutAction action)796 void ProfileShortcutManagerWin::CreateOrUpdateShortcutsForProfileAtPath(
797     const base::FilePath& profile_path,
798     CreateOrUpdateMode create_mode,
799     NonProfileShortcutAction action) {
800   DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::UI) ||
801          BrowserThread::CurrentlyOn(BrowserThread::UI));
802   CreateOrUpdateShortcutsParams params(profile_path, create_mode, action);
803 
804   ProfileInfoCache* cache = &profile_manager_->GetProfileInfoCache();
805   size_t profile_index = cache->GetIndexOfProfileWithPath(profile_path);
806   if (profile_index == std::string::npos)
807     return;
808   bool remove_badging = cache->GetNumberOfProfiles() == 1;
809 
810   params.old_profile_name =
811       cache->GetShortcutNameOfProfileAtIndex(profile_index);
812 
813   // Exit early if the mode is to update existing profile shortcuts only and
814   // none were ever created for this profile, per the shortcut name not being
815   // set in the profile info cache.
816   if (params.old_profile_name.empty() &&
817       create_mode == UPDATE_EXISTING_ONLY &&
818       action == IGNORE_NON_PROFILE_SHORTCUTS) {
819     return;
820   }
821 
822   if (!remove_badging) {
823     params.profile_name = cache->GetNameOfProfileAtIndex(profile_index);
824 
825     const size_t icon_index =
826         cache->GetAvatarIconIndexOfProfileAtIndex(profile_index);
827     const int resource_id_1x =
828         cache->GetDefaultAvatarIconResourceIDAtIndex(icon_index);
829     const int resource_id_2x = kProfileAvatarIconResources2x[icon_index];
830     // Make a copy of the SkBitmaps to ensure that we can safely use the image
831     // data on the FILE thread.
832     params.avatar_image_1x = GetImageResourceSkBitmapCopy(resource_id_1x);
833     params.avatar_image_2x = GetImageResourceSkBitmapCopy(resource_id_2x);
834   }
835   BrowserThread::PostTask(
836       BrowserThread::FILE, FROM_HERE,
837       base::Bind(&CreateOrUpdateDesktopShortcutsAndIconForProfile, params));
838 
839   cache->SetShortcutNameOfProfileAtIndex(profile_index,
840                                          params.profile_name);
841 }
842 
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)843 void ProfileShortcutManagerWin::Observe(
844     int type,
845     const content::NotificationSource& source,
846     const content::NotificationDetails& details) {
847   switch (type) {
848     // This notification is triggered when a profile is loaded.
849     case chrome::NOTIFICATION_PROFILE_CREATED: {
850       Profile* profile =
851           content::Source<Profile>(source).ptr()->GetOriginalProfile();
852       if (profile->GetPrefs()->GetInteger(prefs::kProfileIconVersion) <
853           kCurrentProfileIconVersion) {
854         // Ensure the profile's icon file has been created.
855         CreateOrUpdateProfileIcon(profile->GetPath());
856       }
857       break;
858     }
859     default:
860       NOTREACHED();
861       break;
862   }
863 }
864