Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / notifications / message_center_settings_controller.cc
blob995b3872e63439b1520ad0ea72069d8010e4201c
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"
7 #include <algorithm>
9 #include "base/command_line.h"
10 #include "base/i18n/string_compare.h"
11 #include "base/message_loop/message_loop_proxy.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "chrome/browser/browser_process.h"
14 #include "chrome/browser/chrome_notification_types.h"
15 #include "chrome/browser/extensions/app_icon_loader_impl.h"
16 #include "chrome/browser/extensions/extension_service.h"
17 #include "chrome/browser/extensions/extension_system.h"
18 #include "chrome/browser/favicon/favicon_service.h"
19 #include "chrome/browser/favicon/favicon_service_factory.h"
20 #include "chrome/browser/history/history_types.h"
21 #include "chrome/browser/notifications/desktop_notification_service.h"
22 #include "chrome/browser/notifications/desktop_notification_service_factory.h"
23 #include "chrome/browser/notifications/sync_notifier/chrome_notifier_service.h"
24 #include "chrome/browser/notifications/sync_notifier/chrome_notifier_service_factory.h"
25 #include "chrome/browser/profiles/profile.h"
26 #include "chrome/browser/profiles/profile_info_cache.h"
27 #include "chrome/browser/profiles/profile_manager.h"
28 #include "chrome/common/cancelable_task_tracker.h"
29 #include "chrome/common/extensions/api/notifications.h"
30 #include "chrome/common/extensions/extension_constants.h"
31 #include "chrome/common/favicon/favicon_types.h"
32 #include "content/public/browser/notification_service.h"
33 #include "content/public/browser/notification_source.h"
34 #include "extensions/browser/event_router.h"
35 #include "grit/theme_resources.h"
36 #include "grit/ui_strings.h"
37 #include "ui/base/l10n/l10n_util.h"
38 #include "ui/base/resource/resource_bundle.h"
39 #include "ui/gfx/image/image.h"
40 #include "ui/message_center/message_center_style.h"
42 #if defined(OS_CHROMEOS)
43 #include "ash/system/system_notifier.h"
44 #include "chrome/browser/chromeos/profiles/profile_helper.h"
45 #endif
47 using message_center::Notifier;
48 using message_center::NotifierId;
50 namespace message_center {
52 class ProfileNotifierGroup : public message_center::NotifierGroup {
53 public:
54 ProfileNotifierGroup(const gfx::Image& icon,
55 const base::string16& display_name,
56 const base::string16& login_info,
57 size_t index,
58 const base::FilePath& profile_path);
59 ProfileNotifierGroup(const gfx::Image& icon,
60 const base::string16& display_name,
61 const base::string16& login_info,
62 size_t index,
63 Profile* profile);
64 virtual ~ProfileNotifierGroup() {}
66 Profile* profile() const { return profile_; }
68 private:
69 Profile* profile_;
72 ProfileNotifierGroup::ProfileNotifierGroup(const gfx::Image& icon,
73 const base::string16& display_name,
74 const base::string16& login_info,
75 size_t index,
76 const base::FilePath& profile_path)
77 : message_center::NotifierGroup(icon, display_name, login_info, index),
78 profile_(NULL) {
79 // Try to get the profile
80 profile_ =
81 g_browser_process->profile_manager()->GetProfileByPath(profile_path);
84 ProfileNotifierGroup::ProfileNotifierGroup(const gfx::Image& icon,
85 const base::string16& display_name,
86 const base::string16& login_info,
87 size_t index,
88 Profile* profile)
89 : message_center::NotifierGroup(icon, display_name, login_info, index),
90 profile_(profile) {
93 } // namespace message_center
95 namespace {
96 class NotifierComparator {
97 public:
98 explicit NotifierComparator(icu::Collator* collator) : collator_(collator) {}
100 bool operator() (Notifier* n1, Notifier* n2) {
101 return base::i18n::CompareString16WithCollator(
102 collator_, n1->name, n2->name) == UCOL_LESS;
105 private:
106 icu::Collator* collator_;
109 bool SimpleCompareNotifiers(Notifier* n1, Notifier* n2) {
110 return n1->name < n2->name;
113 } // namespace
115 MessageCenterSettingsController::MessageCenterSettingsController(
116 ProfileInfoCache* profile_info_cache)
117 : current_notifier_group_(0),
118 profile_info_cache_(profile_info_cache),
119 weak_factory_(this) {
120 DCHECK(profile_info_cache_);
121 // The following events all represent changes that may need to be reflected in
122 // the profile selector context menu, so listen for them all. We'll just
123 // rebuild the list when we get any of them.
124 registrar_.Add(this,
125 chrome::NOTIFICATION_PROFILE_CREATED,
126 content::NotificationService::AllBrowserContextsAndSources());
127 registrar_.Add(this,
128 chrome::NOTIFICATION_PROFILE_ADDED,
129 content::NotificationService::AllBrowserContextsAndSources());
130 registrar_.Add(this,
131 chrome::NOTIFICATION_PROFILE_DESTROYED,
132 content::NotificationService::AllBrowserContextsAndSources());
133 registrar_.Add(this,
134 chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED,
135 content::NotificationService::AllBrowserContextsAndSources());
136 RebuildNotifierGroups();
138 #if defined(OS_CHROMEOS)
139 // UserManager may not exist in some tests.
140 if (chromeos::UserManager::IsInitialized())
141 chromeos::UserManager::Get()->AddSessionStateObserver(this);
142 #endif
145 MessageCenterSettingsController::~MessageCenterSettingsController() {
146 #if defined(OS_CHROMEOS)
147 // UserManager may not exist in some tests.
148 if (chromeos::UserManager::IsInitialized())
149 chromeos::UserManager::Get()->RemoveSessionStateObserver(this);
150 #endif
153 void MessageCenterSettingsController::AddObserver(
154 message_center::NotifierSettingsObserver* observer) {
155 observers_.AddObserver(observer);
158 void MessageCenterSettingsController::RemoveObserver(
159 message_center::NotifierSettingsObserver* observer) {
160 observers_.RemoveObserver(observer);
163 size_t MessageCenterSettingsController::GetNotifierGroupCount() const {
164 return notifier_groups_.size();
167 const message_center::NotifierGroup&
168 MessageCenterSettingsController::GetNotifierGroupAt(size_t index) const {
169 DCHECK_LT(index, notifier_groups_.size());
170 return *(notifier_groups_[index]);
173 bool MessageCenterSettingsController::IsNotifierGroupActiveAt(
174 size_t index) const {
175 return current_notifier_group_ == index;
178 const message_center::NotifierGroup&
179 MessageCenterSettingsController::GetActiveNotifierGroup() const {
180 DCHECK_LT(current_notifier_group_, notifier_groups_.size());
181 return *(notifier_groups_[current_notifier_group_]);
184 void MessageCenterSettingsController::SwitchToNotifierGroup(size_t index) {
185 DCHECK_LT(index, notifier_groups_.size());
186 if (current_notifier_group_ == index)
187 return;
189 current_notifier_group_ = index;
190 FOR_EACH_OBSERVER(message_center::NotifierSettingsObserver,
191 observers_,
192 NotifierGroupChanged());
195 void MessageCenterSettingsController::GetNotifierList(
196 std::vector<Notifier*>* notifiers) {
197 DCHECK(notifiers);
198 if (notifier_groups_.size() <= current_notifier_group_)
199 return;
200 // Temporarily use the last used profile to prevent chrome from crashing when
201 // the default profile is not loaded.
202 Profile* profile = notifier_groups_[current_notifier_group_]->profile();
204 DesktopNotificationService* notification_service =
205 DesktopNotificationServiceFactory::GetForProfile(profile);
207 UErrorCode error;
208 scoped_ptr<icu::Collator> collator(icu::Collator::createInstance(error));
209 scoped_ptr<NotifierComparator> comparator;
210 if (!U_FAILURE(error))
211 comparator.reset(new NotifierComparator(collator.get()));
213 ExtensionService* extension_service = profile->GetExtensionService();
214 const extensions::ExtensionSet* extension_set =
215 extension_service->extensions();
216 // The extension icon size has to be 32x32 at least to load bigger icons if
217 // the icon doesn't exist for the specified size, and in that case it falls
218 // back to the default icon. The fetched icon will be resized in the settings
219 // dialog. See chrome/browser/extensions/extension_icon_image.cc and
220 // crbug.com/222931
221 app_icon_loader_.reset(new extensions::AppIconLoaderImpl(
222 profile, extension_misc::EXTENSION_ICON_SMALL, this));
223 for (extensions::ExtensionSet::const_iterator iter = extension_set->begin();
224 iter != extension_set->end();
225 ++iter) {
226 const extensions::Extension* extension = iter->get();
227 if (!extension->HasAPIPermission(
228 extensions::APIPermission::kNotification)) {
229 continue;
232 NotifierId notifier_id(NotifierId::APPLICATION, extension->id());
233 notifiers->push_back(new Notifier(
234 notifier_id,
235 base::UTF8ToUTF16(extension->name()),
236 notification_service->IsNotifierEnabled(notifier_id)));
237 app_icon_loader_->FetchImage(extension->id());
240 if (notifier::ChromeNotifierServiceFactory::UseSyncedNotifications(
241 CommandLine::ForCurrentProcess())) {
242 notifier::ChromeNotifierService* sync_notifier_service =
243 notifier::ChromeNotifierServiceFactory::GetInstance()->GetForProfile(
244 profile, Profile::EXPLICIT_ACCESS);
245 if (sync_notifier_service) {
246 sync_notifier_service->GetSyncedNotificationServices(notifiers);
248 if (comparator)
249 std::sort(notifiers->begin(), notifiers->end(), *comparator);
250 else
251 std::sort(notifiers->begin(), notifiers->end(), SimpleCompareNotifiers);
255 int app_count = notifiers->size();
257 ContentSettingsForOneType settings;
258 notification_service->GetNotificationsSettings(&settings);
259 FaviconService* favicon_service =
260 FaviconServiceFactory::GetForProfile(profile, Profile::EXPLICIT_ACCESS);
261 favicon_tracker_.reset(new CancelableTaskTracker());
262 patterns_.clear();
263 for (ContentSettingsForOneType::const_iterator iter = settings.begin();
264 iter != settings.end(); ++iter) {
265 if (iter->primary_pattern == ContentSettingsPattern::Wildcard() &&
266 iter->secondary_pattern == ContentSettingsPattern::Wildcard() &&
267 iter->source != "preference") {
268 continue;
271 std::string url_pattern = iter->primary_pattern.ToString();
272 base::string16 name = base::UTF8ToUTF16(url_pattern);
273 GURL url(url_pattern);
274 NotifierId notifier_id(url);
275 notifiers->push_back(new Notifier(
276 notifier_id,
277 name,
278 notification_service->IsNotifierEnabled(notifier_id)));
279 patterns_[name] = iter->primary_pattern;
280 FaviconService::FaviconForURLParams favicon_params(
281 url,
282 chrome::FAVICON | chrome::TOUCH_ICON,
283 message_center::kSettingsIconSize);
284 // Note that favicon service obtains the favicon from history. This means
285 // that it will fail to obtain the image if there are no history data for
286 // that URL.
287 favicon_service->GetFaviconImageForURL(
288 favicon_params,
289 base::Bind(&MessageCenterSettingsController::OnFaviconLoaded,
290 base::Unretained(this), url),
291 favicon_tracker_.get());
294 // Screenshot notification feature is only for ChromeOS. See crbug.com/238358
295 #if defined(OS_CHROMEOS)
296 const base::string16 screenshot_name =
297 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_NOTIFIER_SCREENSHOT_NAME);
298 NotifierId screenshot_notifier_id(
299 NotifierId::SYSTEM_COMPONENT, ash::system_notifier::kNotifierScreenshot);
300 Notifier* const screenshot_notifier = new Notifier(
301 screenshot_notifier_id,
302 screenshot_name,
303 notification_service->IsNotifierEnabled(screenshot_notifier_id));
304 screenshot_notifier->icon =
305 ui::ResourceBundle::GetSharedInstance().GetImageNamed(
306 IDR_SCREENSHOT_NOTIFICATION_ICON);
307 notifiers->push_back(screenshot_notifier);
308 #endif
310 if (comparator) {
311 std::sort(notifiers->begin() + app_count, notifiers->end(), *comparator);
312 } else {
313 std::sort(notifiers->begin() + app_count, notifiers->end(),
314 SimpleCompareNotifiers);
318 void MessageCenterSettingsController::SetNotifierEnabled(
319 const Notifier& notifier,
320 bool enabled) {
321 DCHECK_LT(current_notifier_group_, notifier_groups_.size());
322 Profile* profile = notifier_groups_[current_notifier_group_]->profile();
324 DesktopNotificationService* notification_service =
325 DesktopNotificationServiceFactory::GetForProfile(profile);
327 if (notifier.notifier_id.type == NotifierId::WEB_PAGE) {
328 // WEB_PAGE notifier cannot handle in DesktopNotificationService
329 // since it has the exact URL pattern.
330 // TODO(mukai): fix this.
331 ContentSetting default_setting =
332 notification_service->GetDefaultContentSetting(NULL);
333 DCHECK(default_setting == CONTENT_SETTING_ALLOW ||
334 default_setting == CONTENT_SETTING_BLOCK ||
335 default_setting == CONTENT_SETTING_ASK);
336 if ((enabled && default_setting != CONTENT_SETTING_ALLOW) ||
337 (!enabled && default_setting == CONTENT_SETTING_ALLOW)) {
338 if (notifier.notifier_id.url.is_valid()) {
339 if (enabled)
340 notification_service->GrantPermission(notifier.notifier_id.url);
341 else
342 notification_service->DenyPermission(notifier.notifier_id.url);
343 } else {
344 LOG(ERROR) << "Invalid url pattern: "
345 << notifier.notifier_id.url.spec();
347 } else {
348 std::map<base::string16, ContentSettingsPattern>::const_iterator iter =
349 patterns_.find(notifier.name);
350 if (iter != patterns_.end()) {
351 notification_service->ClearSetting(iter->second);
352 } else {
353 LOG(ERROR) << "Invalid url pattern: "
354 << notifier.notifier_id.url.spec();
357 } else {
358 notification_service->SetNotifierEnabled(notifier.notifier_id, enabled);
359 if (notifier.notifier_id.type == NotifierId::SYNCED_NOTIFICATION_SERVICE) {
360 notifier::ChromeNotifierService* notifier_service =
361 notifier::ChromeNotifierServiceFactory::GetInstance()->GetForProfile(
362 profile, Profile::EXPLICIT_ACCESS);
363 notifier_service->OnSyncedNotificationServiceEnabled(
364 notifier.notifier_id.id, enabled);
369 void MessageCenterSettingsController::OnNotifierSettingsClosing() {
370 DCHECK(favicon_tracker_.get());
371 favicon_tracker_->TryCancelAll();
372 patterns_.clear();
375 bool MessageCenterSettingsController::NotifierHasAdvancedSettings(
376 const NotifierId& notifier_id) const {
377 // TODO(dewittj): Refactor this so that notifiers have a delegate that can
378 // handle this in a more appropriate location.
379 if (notifier_id.type != NotifierId::APPLICATION)
380 return false;
382 const std::string& extension_id = notifier_id.id;
384 if (notifier_groups_.size() < current_notifier_group_)
385 return false;
386 Profile* profile = notifier_groups_[current_notifier_group_]->profile();
388 extensions::EventRouter* event_router =
389 extensions::ExtensionSystem::Get(profile)->event_router();
391 return event_router->ExtensionHasEventListener(
392 extension_id, extensions::api::notifications::OnShowSettings::kEventName);
395 void MessageCenterSettingsController::OnNotifierAdvancedSettingsRequested(
396 const NotifierId& notifier_id,
397 const std::string* notification_id) {
398 // TODO(dewittj): Refactor this so that notifiers have a delegate that can
399 // handle this in a more appropriate location.
400 if (notifier_id.type != NotifierId::APPLICATION)
401 return;
403 const std::string& extension_id = notifier_id.id;
405 if (notifier_groups_.size() < current_notifier_group_)
406 return;
407 Profile* profile = notifier_groups_[current_notifier_group_]->profile();
409 extensions::EventRouter* event_router =
410 extensions::ExtensionSystem::Get(profile)->event_router();
411 scoped_ptr<base::ListValue> args(new base::ListValue());
413 scoped_ptr<extensions::Event> event(new extensions::Event(
414 extensions::api::notifications::OnShowSettings::kEventName, args.Pass()));
415 event_router->DispatchEventToExtension(extension_id, event.Pass());
418 void MessageCenterSettingsController::OnFaviconLoaded(
419 const GURL& url,
420 const chrome::FaviconImageResult& favicon_result) {
421 FOR_EACH_OBSERVER(message_center::NotifierSettingsObserver,
422 observers_,
423 UpdateIconImage(NotifierId(url), favicon_result.image));
427 #if defined(OS_CHROMEOS)
428 void MessageCenterSettingsController::ActiveUserChanged(
429 const chromeos::User* active_user) {
430 RebuildNotifierGroups();
432 #endif
434 void MessageCenterSettingsController::SetAppImage(const std::string& id,
435 const gfx::ImageSkia& image) {
436 FOR_EACH_OBSERVER(message_center::NotifierSettingsObserver,
437 observers_,
438 UpdateIconImage(NotifierId(NotifierId::APPLICATION, id),
439 gfx::Image(image)));
442 void MessageCenterSettingsController::Observe(
443 int type,
444 const content::NotificationSource& source,
445 const content::NotificationDetails& details) {
446 // GetOffTheRecordProfile() may create a new off-the-record profile, but that
447 // doesn't need to rebuild the groups.
448 if (type == chrome::NOTIFICATION_PROFILE_CREATED &&
449 content::Source<Profile>(source).ptr()->IsOffTheRecord()) {
450 return;
453 RebuildNotifierGroups();
454 FOR_EACH_OBSERVER(message_center::NotifierSettingsObserver,
455 observers_,
456 NotifierGroupChanged());
459 #if defined(OS_CHROMEOS)
460 void MessageCenterSettingsController::CreateNotifierGroupForGuestLogin() {
461 // Already created.
462 if (!notifier_groups_.empty())
463 return;
465 chromeos::UserManager* user_manager = chromeos::UserManager::Get();
466 // |notifier_groups_| can be empty in login screen too.
467 if (!user_manager->IsLoggedInAsGuest())
468 return;
470 chromeos::User* user = user_manager->GetActiveUser();
471 Profile* profile = user_manager->GetProfileByUser(user);
472 DCHECK(profile);
473 notifier_groups_.push_back(new message_center::ProfileNotifierGroup(
474 gfx::Image(user->image()),
475 user->GetDisplayName(),
476 user->GetDisplayName(),
478 profile));
480 FOR_EACH_OBSERVER(message_center::NotifierSettingsObserver,
481 observers_,
482 NotifierGroupChanged());
484 #endif
486 void MessageCenterSettingsController::RebuildNotifierGroups() {
487 notifier_groups_.clear();
488 current_notifier_group_ = 0;
490 const size_t count = profile_info_cache_->GetNumberOfProfiles();
492 for (size_t i = 0; i < count; ++i) {
493 scoped_ptr<message_center::ProfileNotifierGroup> group(
494 new message_center::ProfileNotifierGroup(
495 profile_info_cache_->GetAvatarIconOfProfileAtIndex(i),
496 profile_info_cache_->GetNameOfProfileAtIndex(i),
497 profile_info_cache_->GetUserNameOfProfileAtIndex(i),
499 profile_info_cache_->GetPathOfProfileAtIndex(i)));
500 if (group->profile() == NULL)
501 continue;
503 #if defined(OS_CHROMEOS)
504 // Allows the active user only.
505 // UserManager may not exist in some tests.
506 if (chromeos::UserManager::IsInitialized()) {
507 chromeos::UserManager* user_manager = chromeos::UserManager::Get();
508 if (user_manager->GetUserByProfile(group->profile()) !=
509 user_manager->GetActiveUser()) {
510 continue;
514 // In ChromeOS, the login screen first creates a dummy profile which is not
515 // actually used, and then the real profile for the user is created when
516 // login (or turns into kiosk mode). This profile should be skipped.
517 if (chromeos::ProfileHelper::IsSigninProfile(group->profile()))
518 continue;
519 #endif
520 notifier_groups_.push_back(group.release());
523 #if defined(OS_CHROMEOS)
524 // ChromeOS guest login cannot get the profile from the for-loop above, so
525 // get the group here.
526 if (notifier_groups_.empty() && chromeos::UserManager::IsInitialized() &&
527 chromeos::UserManager::Get()->IsLoggedInAsGuest()) {
528 // Do not invoke CreateNotifierGroupForGuestLogin() directly. In some tests,
529 // this method may be called before the primary profile is created, which
530 // means user_manager->GetProfileByUser() will create a new primary profile.
531 // But creating a primary profile causes an Observe() before registreing it
532 // as the primary one, which causes this method which causes another
533 // creating a primary profile, and causes an infinite loop.
534 // Thus, it would be better to delay creating group for guest login.
535 base::MessageLoopProxy::current()->PostTask(
536 FROM_HERE,
537 base::Bind(
538 &MessageCenterSettingsController::CreateNotifierGroupForGuestLogin,
539 weak_factory_.GetWeakPtr()));
541 #endif