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.h"
10 #include "base/command_line.h"
11 #include "base/file_util.h"
12 #include "base/path_service.h"
13 #include "base/string16.h"
14 #include "base/stringprintf.h"
15 #include "base/utf_string_conversions.h"
16 #include "chrome/browser/app_icon_win.h"
17 #include "chrome/browser/browser_process.h"
18 #include "chrome/browser/prefs/pref_service.h"
19 #include "chrome/browser/profiles/profile_info_cache_observer.h"
20 #include "chrome/browser/profiles/profile_info_util.h"
21 #include "chrome/browser/profiles/profile_manager.h"
22 #include "chrome/common/chrome_constants.h"
23 #include "chrome/common/chrome_switches.h"
24 #include "chrome/common/pref_names.h"
25 #include "chrome/installer/util/auto_launch_util.h"
26 #include "chrome/installer/util/browser_distribution.h"
27 #include "chrome/installer/util/product.h"
28 #include "chrome/installer/util/shell_util.h"
29 #include "content/public/browser/browser_thread.h"
30 #include "grit/generated_resources.h"
31 #include "skia/ext/image_operations.h"
32 #include "skia/ext/platform_canvas.h"
33 #include "ui/base/l10n/l10n_util.h"
34 #include "ui/base/resource/resource_bundle.h"
35 #include "ui/gfx/icon_util.h"
36 #include "ui/gfx/image/image.h"
38 using content::BrowserThread
;
42 const char kProfileIconFileName
[] = "Google Profile.ico";
43 const int kProfileAvatarShortcutBadgeWidth
= 28;
44 const int kProfileAvatarShortcutBadgeHeight
= 28;
45 const int kShortcutIconSize
= 48;
47 // Creates a desktop shortcut icon file (.ico) on the disk for a given profile,
48 // badging the browser distribution icon with the profile avatar.
49 // Returns a path to the shortcut icon file on disk, which is empty if this
50 // fails. Use index 0 when assigning the resulting file as the icon.
51 FilePath
CreateChromeDesktopShortcutIconForProfile(
52 const FilePath
& profile_path
,
53 const SkBitmap
& avatar_bitmap
) {
54 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
55 HICON app_icon_handle
= GetAppIconForSize(kShortcutIconSize
);
56 scoped_ptr
<SkBitmap
> app_icon_bitmap(
57 IconUtil::CreateSkBitmapFromHICON(app_icon_handle
));
58 DestroyIcon(app_icon_handle
);
59 if (!app_icon_bitmap
.get())
62 // TODO(rlp): Share this chunk of code with
63 // avatar_menu_button::DrawTaskBarDecoration.
64 const SkBitmap
* source_bitmap
= NULL
;
65 SkBitmap squarer_bitmap
;
66 if ((avatar_bitmap
.width() == profiles::kAvatarIconWidth
) &&
67 (avatar_bitmap
.height() == profiles::kAvatarIconHeight
)) {
68 // Shave a couple of columns so the bitmap is more square. So when
69 // resized to a square aspect ratio it looks pretty.
71 avatar_bitmap
.extractSubset(&squarer_bitmap
, SkIRect::MakeXYWH(x
, 0,
72 profiles::kAvatarIconWidth
- x
* 2, profiles::kAvatarIconHeight
));
73 source_bitmap
= &squarer_bitmap
;
75 source_bitmap
= &avatar_bitmap
;
77 SkBitmap sk_icon
= skia::ImageOperations::Resize(
79 skia::ImageOperations::RESIZE_LANCZOS3
,
80 kProfileAvatarShortcutBadgeWidth
,
81 kProfileAvatarShortcutBadgeHeight
);
83 // Overlay the avatar on the icon, anchoring it to the bottom-right of the
85 scoped_ptr
<SkCanvas
> offscreen_canvas(
86 skia::CreateBitmapCanvas(app_icon_bitmap
->width(),
87 app_icon_bitmap
->height(),
89 DCHECK(offscreen_canvas
.get());
90 offscreen_canvas
->drawBitmap(*app_icon_bitmap
, 0, 0);
91 offscreen_canvas
->drawBitmap(
93 app_icon_bitmap
->width() - kProfileAvatarShortcutBadgeWidth
,
94 app_icon_bitmap
->height() - kProfileAvatarShortcutBadgeHeight
);
95 const SkBitmap
& final_bitmap
=
96 offscreen_canvas
->getDevice()->accessBitmap(false);
98 // Finally, write the .ico file containing this new bitmap.
99 FilePath icon_path
= profile_path
.AppendASCII(kProfileIconFileName
);
100 if (!IconUtil::CreateIconFileFromSkBitmap(final_bitmap
, icon_path
))
106 string16
CreateProfileShortcutFlags(const FilePath
& profile_path
) {
107 return base::StringPrintf(L
"--%ls=\"%ls\"",
108 ASCIIToUTF16(switches::kProfileDirectory
).c_str(),
109 profile_path
.BaseName().value().c_str());
112 // Renames an existing Chrome desktop profile shortcut. Must be called on the
114 void RenameChromeDesktopShortcutForProfile(
115 const string16
& old_shortcut_file
,
116 const string16
& new_shortcut_file
) {
117 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
118 BrowserDistribution
* dist
= BrowserDistribution::GetDistribution();
119 FilePath shortcut_path
;
120 if (ShellUtil::GetShortcutPath(ShellUtil::SHORTCUT_LOCATION_DESKTOP
, dist
,
121 ShellUtil::CURRENT_USER
, &shortcut_path
)) {
122 FilePath old_shortcut_path
= shortcut_path
.Append(old_shortcut_file
);
123 FilePath new_shortcut_path
= shortcut_path
.Append(new_shortcut_file
);
124 if (!file_util::Move(old_shortcut_path
, new_shortcut_path
))
125 LOG(ERROR
) << "Could not rename Windows profile desktop shortcut.";
129 // Create or update a profile desktop shortcut. Must be called on the FILE
131 void CreateOrUpdateProfileDesktopShortcut(
132 const FilePath
& profile_path
,
133 const string16
& profile_name
,
134 const SkBitmap
& avatar_image
,
136 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
137 FilePath shortcut_icon
=
138 CreateChromeDesktopShortcutIconForProfile(profile_path
, avatar_image
);
141 if (!PathService::Get(base::FILE_EXE
, &chrome_exe
)) {
145 BrowserDistribution
* dist
= BrowserDistribution::GetDistribution();
146 installer::Product
product(dist
);
148 ShellUtil::ShortcutProperties
properties(ShellUtil::CURRENT_USER
);
149 product
.AddDefaultShortcutProperties(chrome_exe
, &properties
);
150 properties
.set_arguments(CreateProfileShortcutFlags(profile_path
));
151 if (!shortcut_icon
.empty())
152 properties
.set_icon(shortcut_icon
, 0);
153 properties
.set_shortcut_name(
154 ProfileShortcutManager::GetShortcutNameForProfile(profile_name
));
155 ShellUtil::ShortcutOperation operation
=
156 create
? ShellUtil::SHELL_SHORTCUT_CREATE_ALWAYS
:
157 ShellUtil::SHELL_SHORTCUT_REPLACE_EXISTING
;
158 ShellUtil::CreateOrUpdateShortcut(
159 ShellUtil::SHORTCUT_LOCATION_DESKTOP
, dist
, properties
, operation
);
162 // Deletes the specified desktop shortcut and corresponding icon file. Must be
163 // called on the FILE thread.
164 void DeleteDesktopShortcutAndIconFile(const string16
& shortcut_name
,
165 const FilePath
& icon_path
) {
166 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
169 if (!PathService::Get(base::FILE_EXE
, &chrome_exe
)) {
174 ShellUtil::RemoveShortcut(ShellUtil::SHORTCUT_LOCATION_DESKTOP
,
175 BrowserDistribution::GetDistribution(),
176 chrome_exe
.value(), ShellUtil::CURRENT_USER
,
178 file_util::Delete(icon_path
, false);
183 class ProfileShortcutManagerWin
: public ProfileShortcutManager
,
184 public ProfileInfoCacheObserver
{
186 explicit ProfileShortcutManagerWin(ProfileManager
* manager
);
187 virtual ~ProfileShortcutManagerWin();
189 virtual void CreateProfileShortcut(const FilePath
& profile_path
) OVERRIDE
;
191 // ProfileInfoCacheObserver:
192 virtual void OnProfileAdded(const FilePath
& profile_path
) OVERRIDE
;
193 virtual void OnProfileWillBeRemoved(const FilePath
& profile_path
) OVERRIDE
;
194 virtual void OnProfileWasRemoved(const FilePath
& profile_path
,
195 const string16
& profile_name
) OVERRIDE
;
196 virtual void OnProfileNameChanged(const FilePath
& profile_path
,
197 const string16
& old_profile_name
) OVERRIDE
;
198 virtual void OnProfileAvatarChanged(const FilePath
& profile_path
) OVERRIDE
;
201 void StartProfileShortcutNameChange(const FilePath
& profile_path
,
202 const string16
& old_profile_name
);
203 // Gives the profile path of an alternate profile than |profile_path|.
204 // Must only be called when the number profiles is 2.
205 FilePath
GetOtherProfilePath(const FilePath
& profile_path
);
206 void UpdateShortcutForProfileAtPath(const FilePath
& profile_path
,
209 ProfileManager
* profile_manager_
;
211 DISALLOW_COPY_AND_ASSIGN(ProfileShortcutManagerWin
);
215 bool ProfileShortcutManager::IsFeatureEnabled() {
220 ProfileShortcutManager
* ProfileShortcutManager::Create(
221 ProfileManager
* manager
) {
222 return new ProfileShortcutManagerWin(manager
);
226 string16
ProfileShortcutManager::GetShortcutNameForProfile(
227 const string16
& profile_name
) {
228 BrowserDistribution
* dist
= BrowserDistribution::GetDistribution();
229 string16
shortcut_name(dist
->GetAppShortCutName());
230 shortcut_name
.append(L
" (");
231 shortcut_name
.append(profile_name
);
232 shortcut_name
.append(L
")");
233 return shortcut_name
;
236 ProfileShortcutManagerWin::ProfileShortcutManagerWin(ProfileManager
* manager
)
237 : profile_manager_(manager
) {
238 profile_manager_
->GetProfileInfoCache().AddObserver(this);
241 ProfileShortcutManagerWin::~ProfileShortcutManagerWin() {
242 profile_manager_
->GetProfileInfoCache().RemoveObserver(this);
245 void ProfileShortcutManagerWin::CreateProfileShortcut(
246 const FilePath
& profile_path
) {
247 UpdateShortcutForProfileAtPath(profile_path
, true);
250 void ProfileShortcutManagerWin::OnProfileAdded(const FilePath
& profile_path
) {
251 const size_t profile_count
=
252 profile_manager_
->GetProfileInfoCache().GetNumberOfProfiles();
253 if (profile_count
== 1) {
254 UpdateShortcutForProfileAtPath(profile_path
, true);
255 } else if (profile_count
== 2) {
256 UpdateShortcutForProfileAtPath(GetOtherProfilePath(profile_path
), false);
260 void ProfileShortcutManagerWin::OnProfileWillBeRemoved(
261 const FilePath
& profile_path
) {
264 void ProfileShortcutManagerWin::OnProfileWasRemoved(
265 const FilePath
& profile_path
,
266 const string16
& profile_name
) {
267 const ProfileInfoCache
& cache
= profile_manager_
->GetProfileInfoCache();
268 // If there is only one profile remaining, remove the badging information
269 // from an existing shortcut.
270 if (cache
.GetNumberOfProfiles() == 1)
271 UpdateShortcutForProfileAtPath(cache
.GetPathOfProfileAtIndex(0), false);
273 string16 profile_name_updated
;
274 if (cache
.GetNumberOfProfiles() != 0)
275 profile_name_updated
= profile_name
;
277 string16 shortcut_name
= GetShortcutNameForProfile(profile_name_updated
);
278 FilePath icon_path
= profile_path
.AppendASCII(kProfileIconFileName
);
279 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE
,
280 base::Bind(&DeleteDesktopShortcutAndIconFile
,
281 shortcut_name
, icon_path
));
284 void ProfileShortcutManagerWin::OnProfileNameChanged(
285 const FilePath
& profile_path
,
286 const string16
& old_profile_name
) {
287 UpdateShortcutForProfileAtPath(profile_path
, false);
290 void ProfileShortcutManagerWin::OnProfileAvatarChanged(
291 const FilePath
& profile_path
) {
292 UpdateShortcutForProfileAtPath(profile_path
, false);
295 void ProfileShortcutManagerWin::StartProfileShortcutNameChange(
296 const FilePath
& profile_path
,
297 const string16
& old_profile_name
) {
298 const ProfileInfoCache
& cache
= profile_manager_
->GetProfileInfoCache();
299 size_t profile_index
= cache
.GetIndexOfProfileWithPath(profile_path
);
300 if (profile_index
== std::string::npos
)
302 // If the shortcut will have an appended name, get the profile name.
303 string16 new_profile_name
;
304 if (cache
.GetNumberOfProfiles() != 1)
305 new_profile_name
= cache
.GetNameOfProfileAtIndex(profile_index
);
307 string16
old_shortcut_file(GetShortcutNameForProfile(old_profile_name
));
308 string16
new_shortcut_file(GetShortcutNameForProfile(new_profile_name
));
309 BrowserThread::PostTask(
310 BrowserThread::FILE, FROM_HERE
,
311 base::Bind(&RenameChromeDesktopShortcutForProfile
,
316 FilePath
ProfileShortcutManagerWin::GetOtherProfilePath(
317 const FilePath
& profile_path
) {
318 const ProfileInfoCache
& cache
= profile_manager_
->GetProfileInfoCache();
319 DCHECK_EQ(2U, cache
.GetNumberOfProfiles());
320 // Get the index of the current profile, in order to find the index of the
322 size_t current_profile_index
= cache
.GetIndexOfProfileWithPath(profile_path
);
323 size_t other_profile_index
= (current_profile_index
== 0) ? 1 : 0;
324 return profile_manager_
->GetProfileInfoCache().
325 GetPathOfProfileAtIndex(other_profile_index
);
328 void ProfileShortcutManagerWin::UpdateShortcutForProfileAtPath(
329 const FilePath
& profile_path
,
330 bool create_always
) {
331 ProfileInfoCache
* cache
= &profile_manager_
->GetProfileInfoCache();
332 size_t profile_index
= cache
->GetIndexOfProfileWithPath(profile_path
);
333 if (profile_index
== std::string::npos
)
335 bool remove_badging
= cache
->GetNumberOfProfiles() == 1;
337 string16 old_shortcut_appended_name
=
338 cache
->GetShortcutNameOfProfileAtIndex(profile_index
);
340 string16 new_shortcut_appended_name
;
342 new_shortcut_appended_name
= cache
->GetNameOfProfileAtIndex(profile_index
);
344 if (!create_always
&&
345 new_shortcut_appended_name
!= old_shortcut_appended_name
) {
346 StartProfileShortcutNameChange(profile_path
, old_shortcut_appended_name
);
349 SkBitmap profile_avatar_bitmap_copy
;
350 if (!remove_badging
) {
351 size_t profile_icon_index
=
352 cache
->GetAvatarIconIndexOfProfileAtIndex(profile_index
);
353 gfx::Image profile_avatar_image
= ResourceBundle::GetSharedInstance().
355 cache
->GetDefaultAvatarIconResourceIDAtIndex(profile_icon_index
));
357 DCHECK(!profile_avatar_image
.IsEmpty());
358 const SkBitmap
* profile_avatar_bitmap
= profile_avatar_image
.ToSkBitmap();
359 // Make a copy of the SkBitmap to ensure that we can safely use the image
360 // data on the FILE thread.
361 profile_avatar_bitmap
->deepCopyTo(&profile_avatar_bitmap_copy
,
362 profile_avatar_bitmap
->getConfig());
364 BrowserThread::PostTask(
365 BrowserThread::FILE, FROM_HERE
,
366 base::Bind(&CreateOrUpdateProfileDesktopShortcut
,
367 profile_path
, new_shortcut_appended_name
,
368 profile_avatar_bitmap_copy
, create_always
));
370 cache
->SetShortcutNameOfProfileAtIndex(profile_index
,
371 new_shortcut_appended_name
);