1 // Copyright (c) 2013 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/notifications/message_center_settings_controller.h"
9 #include "base/command_line.h"
10 #include "base/i18n/string_compare.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/task/cancelable_task_tracker.h"
13 #include "base/thread_task_runner_handle.h"
14 #include "chrome/browser/browser_process.h"
15 #include "chrome/browser/chrome_notification_types.h"
16 #include "chrome/browser/extensions/app_icon_loader_impl.h"
17 #include "chrome/browser/favicon/favicon_service_factory.h"
18 #include "chrome/browser/notifications/desktop_notification_profile_util.h"
19 #include "chrome/browser/notifications/notifier_state_tracker.h"
20 #include "chrome/browser/notifications/notifier_state_tracker_factory.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/profiles/profile_info_cache.h"
23 #include "chrome/browser/profiles/profile_manager.h"
24 #include "chrome/common/extensions/api/notifications.h"
25 #include "chrome/common/extensions/extension_constants.h"
26 #include "components/content_settings/core/browser/host_content_settings_map.h"
27 #include "components/favicon/core/favicon_service.h"
28 #include "components/favicon_base/favicon_types.h"
29 #include "components/history/core/browser/history_types.h"
30 #include "content/public/browser/notification_service.h"
31 #include "content/public/browser/notification_source.h"
32 #include "extensions/browser/event_router.h"
33 #include "extensions/browser/extension_registry.h"
34 #include "extensions/common/constants.h"
35 #include "extensions/common/extension.h"
36 #include "extensions/common/permissions/permissions_data.h"
37 #include "grit/theme_resources.h"
38 #include "ui/base/l10n/l10n_util.h"
39 #include "ui/base/resource/resource_bundle.h"
40 #include "ui/gfx/image/image.h"
41 #include "ui/message_center/message_center_style.h"
42 #include "ui/strings/grit/ui_strings.h"
44 #if defined(OS_CHROMEOS)
45 #include "ash/system/system_notifier.h"
46 #include "chrome/browser/chromeos/profiles/profile_helper.h"
49 using message_center::Notifier
;
50 using message_center::NotifierId
;
52 namespace message_center
{
54 class ProfileNotifierGroup
: public message_center::NotifierGroup
{
56 ProfileNotifierGroup(const gfx::Image
& icon
,
57 const base::string16
& display_name
,
58 const base::string16
& login_info
,
60 const base::FilePath
& profile_path
);
61 ProfileNotifierGroup(const gfx::Image
& icon
,
62 const base::string16
& display_name
,
63 const base::string16
& login_info
,
66 virtual ~ProfileNotifierGroup() {}
68 Profile
* profile() const { return profile_
; }
74 ProfileNotifierGroup::ProfileNotifierGroup(const gfx::Image
& icon
,
75 const base::string16
& display_name
,
76 const base::string16
& login_info
,
78 const base::FilePath
& profile_path
)
79 : message_center::NotifierGroup(icon
, display_name
, login_info
, index
),
81 // Try to get the profile
83 g_browser_process
->profile_manager()->GetProfileByPath(profile_path
);
86 ProfileNotifierGroup::ProfileNotifierGroup(const gfx::Image
& icon
,
87 const base::string16
& display_name
,
88 const base::string16
& login_info
,
91 : message_center::NotifierGroup(icon
, display_name
, login_info
, index
),
95 } // namespace message_center
98 class NotifierComparator
{
100 explicit NotifierComparator(icu::Collator
* collator
) : collator_(collator
) {}
102 bool operator() (Notifier
* n1
, Notifier
* n2
) {
103 if (n1
->notifier_id
.type
!= n2
->notifier_id
.type
)
104 return n1
->notifier_id
.type
< n2
->notifier_id
.type
;
107 return base::i18n::CompareString16WithCollator(*collator_
, n1
->name
,
108 n2
->name
) == UCOL_LESS
;
110 return n1
->name
< n2
->name
;
114 icu::Collator
* collator_
;
119 MessageCenterSettingsController::MessageCenterSettingsController(
120 ProfileInfoCache
* profile_info_cache
)
121 : current_notifier_group_(0),
122 profile_info_cache_(profile_info_cache
),
123 weak_factory_(this) {
124 DCHECK(profile_info_cache_
);
125 // The following events all represent changes that may need to be reflected in
126 // the profile selector context menu, so listen for them all. We'll just
127 // rebuild the list when we get any of them.
129 chrome::NOTIFICATION_PROFILE_CREATED
,
130 content::NotificationService::AllBrowserContextsAndSources());
132 chrome::NOTIFICATION_PROFILE_ADDED
,
133 content::NotificationService::AllBrowserContextsAndSources());
135 chrome::NOTIFICATION_PROFILE_DESTROYED
,
136 content::NotificationService::AllBrowserContextsAndSources());
137 g_browser_process
->profile_manager()->GetProfileInfoCache().AddObserver(this);
138 RebuildNotifierGroups(false);
140 #if defined(OS_CHROMEOS)
141 // UserManager may not exist in some tests.
142 if (user_manager::UserManager::IsInitialized())
143 user_manager::UserManager::Get()->AddSessionStateObserver(this);
147 MessageCenterSettingsController::~MessageCenterSettingsController() {
148 g_browser_process
->profile_manager()->
149 GetProfileInfoCache().RemoveObserver(this);
150 #if defined(OS_CHROMEOS)
151 // UserManager may not exist in some tests.
152 if (user_manager::UserManager::IsInitialized())
153 user_manager::UserManager::Get()->RemoveSessionStateObserver(this);
157 void MessageCenterSettingsController::AddObserver(
158 message_center::NotifierSettingsObserver
* observer
) {
159 observers_
.AddObserver(observer
);
162 void MessageCenterSettingsController::RemoveObserver(
163 message_center::NotifierSettingsObserver
* observer
) {
164 observers_
.RemoveObserver(observer
);
167 size_t MessageCenterSettingsController::GetNotifierGroupCount() const {
168 return notifier_groups_
.size();
171 const message_center::NotifierGroup
&
172 MessageCenterSettingsController::GetNotifierGroupAt(size_t index
) const {
173 DCHECK_LT(index
, notifier_groups_
.size());
174 return *(notifier_groups_
[index
]);
177 bool MessageCenterSettingsController::IsNotifierGroupActiveAt(
178 size_t index
) const {
179 return current_notifier_group_
== index
;
182 const message_center::NotifierGroup
&
183 MessageCenterSettingsController::GetActiveNotifierGroup() const {
184 DCHECK_LT(current_notifier_group_
, notifier_groups_
.size());
185 return *(notifier_groups_
[current_notifier_group_
]);
188 void MessageCenterSettingsController::SwitchToNotifierGroup(size_t index
) {
189 DCHECK_LT(index
, notifier_groups_
.size());
190 if (current_notifier_group_
== index
)
193 current_notifier_group_
= index
;
194 FOR_EACH_OBSERVER(message_center::NotifierSettingsObserver
,
196 NotifierGroupChanged());
199 void MessageCenterSettingsController::GetNotifierList(
200 std::vector
<Notifier
*>* notifiers
) {
202 if (notifier_groups_
.size() <= current_notifier_group_
)
204 // Temporarily use the last used profile to prevent chrome from crashing when
205 // the default profile is not loaded.
206 Profile
* profile
= notifier_groups_
[current_notifier_group_
]->profile();
208 NotifierStateTracker
* notifier_state_tracker
=
209 NotifierStateTrackerFactory::GetForProfile(profile
);
211 const extensions::ExtensionSet
& extension_set
=
212 extensions::ExtensionRegistry::Get(profile
)->enabled_extensions();
213 // The extension icon size has to be 32x32 at least to load bigger icons if
214 // the icon doesn't exist for the specified size, and in that case it falls
215 // back to the default icon. The fetched icon will be resized in the settings
216 // dialog. See chrome/browser/extensions/extension_icon_image.cc and
218 app_icon_loader_
.reset(new extensions::AppIconLoaderImpl(
219 profile
, extension_misc::EXTENSION_ICON_SMALL
, this));
220 for (extensions::ExtensionSet::const_iterator iter
= extension_set
.begin();
221 iter
!= extension_set
.end();
223 const extensions::Extension
* extension
= iter
->get();
224 if (!extension
->permissions_data()->HasAPIPermission(
225 extensions::APIPermission::kNotifications
)) {
229 // Hosted apps are no longer able to affect the notifications permission
230 // state for web notifications.
231 // TODO(dewittj): Deprecate the 'notifications' permission for hosted apps.
232 if (extension
->is_hosted_app())
235 NotifierId
notifier_id(NotifierId::APPLICATION
, extension
->id());
236 notifiers
->push_back(new Notifier(
238 base::UTF8ToUTF16(extension
->name()),
239 notifier_state_tracker
->IsNotifierEnabled(notifier_id
)));
240 app_icon_loader_
->FetchImage(extension
->id());
243 ContentSettingsForOneType settings
;
244 DesktopNotificationProfileUtil::GetNotificationsSettings(profile
, &settings
);
246 favicon::FaviconService
* favicon_service
=
247 FaviconServiceFactory::GetForProfile(profile
,
248 ServiceAccessType::EXPLICIT_ACCESS
);
249 favicon_tracker_
.reset(new base::CancelableTaskTracker());
251 for (ContentSettingsForOneType::const_iterator iter
= settings
.begin();
252 iter
!= settings
.end(); ++iter
) {
253 if (iter
->primary_pattern
== ContentSettingsPattern::Wildcard() &&
254 iter
->secondary_pattern
== ContentSettingsPattern::Wildcard() &&
255 iter
->source
!= "preference") {
259 std::string url_pattern
= iter
->primary_pattern
.ToString();
260 base::string16 name
= base::UTF8ToUTF16(url_pattern
);
261 GURL
url(url_pattern
);
262 NotifierId
notifier_id(url
);
263 notifiers
->push_back(new Notifier(
266 notifier_state_tracker
->IsNotifierEnabled(notifier_id
)));
267 patterns_
[name
] = iter
->primary_pattern
;
268 // Note that favicon service obtains the favicon from history. This means
269 // that it will fail to obtain the image if there are no history data for
271 favicon_service
->GetFaviconImageForPageURL(
273 base::Bind(&MessageCenterSettingsController::OnFaviconLoaded
,
274 base::Unretained(this),
276 favicon_tracker_
.get());
279 // Screenshot notification feature is only for ChromeOS. See crbug.com/238358
280 #if defined(OS_CHROMEOS)
281 const base::string16 screenshot_name
=
282 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_NOTIFIER_SCREENSHOT_NAME
);
283 NotifierId
screenshot_notifier_id(
284 NotifierId::SYSTEM_COMPONENT
, ash::system_notifier::kNotifierScreenshot
);
285 Notifier
* const screenshot_notifier
= new Notifier(
286 screenshot_notifier_id
,
288 notifier_state_tracker
->IsNotifierEnabled(screenshot_notifier_id
));
289 screenshot_notifier
->icon
=
290 ui::ResourceBundle::GetSharedInstance().GetImageNamed(
291 IDR_SCREENSHOT_NOTIFICATION_ICON
);
292 notifiers
->push_back(screenshot_notifier
);
295 UErrorCode error
= U_ZERO_ERROR
;
296 scoped_ptr
<icu::Collator
> collator(icu::Collator::createInstance(error
));
297 scoped_ptr
<NotifierComparator
> comparator(
298 new NotifierComparator(U_SUCCESS(error
) ? collator
.get() : NULL
));
300 std::sort(notifiers
->begin(), notifiers
->end(), *comparator
);
303 void MessageCenterSettingsController::SetNotifierEnabled(
304 const Notifier
& notifier
,
306 DCHECK_LT(current_notifier_group_
, notifier_groups_
.size());
307 Profile
* profile
= notifier_groups_
[current_notifier_group_
]->profile();
309 if (notifier
.notifier_id
.type
== NotifierId::WEB_PAGE
) {
310 // WEB_PAGE notifier cannot handle in DesktopNotificationService
311 // since it has the exact URL pattern.
312 // TODO(mukai): fix this.
313 ContentSetting default_setting
=
314 profile
->GetHostContentSettingsMap()->GetDefaultContentSetting(
315 CONTENT_SETTINGS_TYPE_NOTIFICATIONS
, NULL
);
317 DCHECK(default_setting
== CONTENT_SETTING_ALLOW
||
318 default_setting
== CONTENT_SETTING_BLOCK
||
319 default_setting
== CONTENT_SETTING_ASK
);
321 // The content setting for notifications needs to clear when it changes to
322 // the default value or get explicitly set when it differs from the default.
323 bool differs_from_default_value
=
324 (default_setting
!= CONTENT_SETTING_ALLOW
&& enabled
) ||
325 (default_setting
== CONTENT_SETTING_ALLOW
&& !enabled
);
327 if (differs_from_default_value
) {
328 if (notifier
.notifier_id
.url
.is_valid()) {
330 DesktopNotificationProfileUtil::GrantPermission(
331 profile
, notifier
.notifier_id
.url
);
333 DesktopNotificationProfileUtil::DenyPermission(
334 profile
, notifier
.notifier_id
.url
);
337 LOG(ERROR
) << "Invalid url pattern: "
338 << notifier
.notifier_id
.url
.spec();
341 ContentSettingsPattern pattern
;
343 const auto& iter
= patterns_
.find(notifier
.name
);
344 if (iter
!= patterns_
.end()) {
345 pattern
= iter
->second
;
346 } else if (notifier
.notifier_id
.url
.is_valid()) {
348 ContentSettingsPattern::FromURLNoWildcard(notifier
.notifier_id
.url
);
350 LOG(ERROR
) << "Invalid url pattern: "
351 << notifier
.notifier_id
.url
.spec();
354 if (pattern
.IsValid())
355 DesktopNotificationProfileUtil::ClearSetting(profile
, pattern
);
358 NotifierStateTrackerFactory::GetForProfile(profile
)
359 ->SetNotifierEnabled(notifier
.notifier_id
, enabled
);
361 FOR_EACH_OBSERVER(message_center::NotifierSettingsObserver
,
363 NotifierEnabledChanged(notifier
.notifier_id
, enabled
));
366 void MessageCenterSettingsController::OnNotifierSettingsClosing() {
367 DCHECK(favicon_tracker_
.get());
368 favicon_tracker_
->TryCancelAll();
372 bool MessageCenterSettingsController::NotifierHasAdvancedSettings(
373 const NotifierId
& notifier_id
) const {
374 // TODO(dewittj): Refactor this so that notifiers have a delegate that can
375 // handle this in a more appropriate location.
376 if (notifier_id
.type
!= NotifierId::APPLICATION
)
379 const std::string
& extension_id
= notifier_id
.id
;
381 if (notifier_groups_
.size() < current_notifier_group_
)
383 Profile
* profile
= notifier_groups_
[current_notifier_group_
]->profile();
385 extensions::EventRouter
* event_router
= extensions::EventRouter::Get(profile
);
387 return event_router
->ExtensionHasEventListener(
388 extension_id
, extensions::api::notifications::OnShowSettings::kEventName
);
391 void MessageCenterSettingsController::OnNotifierAdvancedSettingsRequested(
392 const NotifierId
& notifier_id
,
393 const std::string
* notification_id
) {
394 // TODO(dewittj): Refactor this so that notifiers have a delegate that can
395 // handle this in a more appropriate location.
396 if (notifier_id
.type
!= NotifierId::APPLICATION
)
399 const std::string
& extension_id
= notifier_id
.id
;
401 if (notifier_groups_
.size() < current_notifier_group_
)
403 Profile
* profile
= notifier_groups_
[current_notifier_group_
]->profile();
405 extensions::EventRouter
* event_router
= extensions::EventRouter::Get(profile
);
406 scoped_ptr
<base::ListValue
> args(new base::ListValue());
408 scoped_ptr
<extensions::Event
> event(new extensions::Event(
409 extensions::events::NOTIFICATIONS_ON_SHOW_SETTINGS
,
410 extensions::api::notifications::OnShowSettings::kEventName
, args
.Pass()));
411 event_router
->DispatchEventToExtension(extension_id
, event
.Pass());
414 void MessageCenterSettingsController::OnFaviconLoaded(
416 const favicon_base::FaviconImageResult
& favicon_result
) {
417 FOR_EACH_OBSERVER(message_center::NotifierSettingsObserver
,
419 UpdateIconImage(NotifierId(url
), favicon_result
.image
));
423 #if defined(OS_CHROMEOS)
424 void MessageCenterSettingsController::ActiveUserChanged(
425 const user_manager::User
* active_user
) {
426 RebuildNotifierGroups(false);
430 void MessageCenterSettingsController::SetAppImage(const std::string
& id
,
431 const gfx::ImageSkia
& image
) {
432 FOR_EACH_OBSERVER(message_center::NotifierSettingsObserver
,
434 UpdateIconImage(NotifierId(NotifierId::APPLICATION
, id
),
438 void MessageCenterSettingsController::Observe(
440 const content::NotificationSource
& source
,
441 const content::NotificationDetails
& details
) {
442 // GetOffTheRecordProfile() may create a new off-the-record profile, but that
443 // doesn't need to rebuild the groups.
444 if (type
== chrome::NOTIFICATION_PROFILE_CREATED
&&
445 content::Source
<Profile
>(source
).ptr()->IsOffTheRecord()) {
449 RebuildNotifierGroups(true);
452 void MessageCenterSettingsController::OnProfileAdded(
453 const base::FilePath
& profile_path
) {
454 RebuildNotifierGroups(true);
456 void MessageCenterSettingsController::OnProfileWasRemoved(
457 const base::FilePath
& profile_path
,
458 const base::string16
& profile_name
) {
459 RebuildNotifierGroups(true);
461 void MessageCenterSettingsController::OnProfileNameChanged(
462 const base::FilePath
& profile_path
,
463 const base::string16
& old_profile_name
) {
464 RebuildNotifierGroups(true);
466 void MessageCenterSettingsController::OnProfileAuthInfoChanged(
467 const base::FilePath
& profile_path
) {
468 RebuildNotifierGroups(true);
471 #if defined(OS_CHROMEOS)
472 void MessageCenterSettingsController::CreateNotifierGroupForGuestLogin() {
474 if (!notifier_groups_
.empty())
477 user_manager::UserManager
* user_manager
= user_manager::UserManager::Get();
478 // |notifier_groups_| can be empty in login screen too.
479 if (!user_manager
->IsLoggedInAsGuest())
482 user_manager::User
* user
= user_manager
->GetActiveUser();
484 chromeos::ProfileHelper::Get()->GetProfileByUserUnsafe(user
);
486 notifier_groups_
.push_back(
487 new message_center::ProfileNotifierGroup(gfx::Image(user
->GetImage()),
488 user
->GetDisplayName(),
489 user
->GetDisplayName(),
493 FOR_EACH_OBSERVER(message_center::NotifierSettingsObserver
,
495 NotifierGroupChanged());
499 void MessageCenterSettingsController::RebuildNotifierGroups(bool notify
) {
500 notifier_groups_
.clear();
501 current_notifier_group_
= 0;
503 const size_t count
= profile_info_cache_
->GetNumberOfProfiles();
504 for (size_t i
= 0; i
< count
; ++i
) {
505 scoped_ptr
<message_center::ProfileNotifierGroup
> group(
506 new message_center::ProfileNotifierGroup(
507 profile_info_cache_
->GetAvatarIconOfProfileAtIndex(i
),
508 profile_info_cache_
->GetNameOfProfileAtIndex(i
),
509 profile_info_cache_
->GetUserNameOfProfileAtIndex(i
),
511 profile_info_cache_
->GetPathOfProfileAtIndex(i
)));
512 if (group
->profile() == NULL
)
515 #if defined(OS_CHROMEOS)
516 // Allows the active user only.
517 // UserManager may not exist in some tests.
518 if (user_manager::UserManager::IsInitialized()) {
519 user_manager::UserManager
* user_manager
=
520 user_manager::UserManager::Get();
521 if (chromeos::ProfileHelper::Get()->GetUserByProfile(group
->profile()) !=
522 user_manager
->GetActiveUser()) {
527 // In ChromeOS, the login screen first creates a dummy profile which is not
528 // actually used, and then the real profile for the user is created when
529 // login (or turns into kiosk mode). This profile should be skipped.
530 if (chromeos::ProfileHelper::IsSigninProfile(group
->profile()))
533 notifier_groups_
.push_back(group
.release());
536 #if defined(OS_CHROMEOS)
537 // ChromeOS guest login cannot get the profile from the for-loop above, so
538 // get the group here.
539 if (notifier_groups_
.empty() && user_manager::UserManager::IsInitialized() &&
540 user_manager::UserManager::Get()->IsLoggedInAsGuest()) {
541 // Do not invoke CreateNotifierGroupForGuestLogin() directly. In some tests,
542 // this method may be called before the primary profile is created, which
543 // means ProfileHelper::Get()->GetProfileByUser() will create a new primary
544 // profile. But creating a primary profile causes an Observe() before
545 // registering it as the primary one, which causes this method which causes
546 // another creating a primary profile, and causes an infinite loop.
547 // Thus, it would be better to delay creating group for guest login.
548 base::ThreadTaskRunnerHandle::Get()->PostTask(
551 &MessageCenterSettingsController::CreateNotifierGroupForGuestLogin
,
552 weak_factory_
.GetWeakPtr()));
557 FOR_EACH_OBSERVER(message_center::NotifierSettingsObserver
,
559 NotifierGroupChanged());