Only grant permissions to new extensions from sync if they have the expected version
[chromium-blink-merge.git] / chrome / browser / notifications / message_center_settings_controller.cc
blobc3108cc0028d9deb23a6b5a3f5b786f1b90ea61e
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/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"
47 #endif
49 using message_center::Notifier;
50 using message_center::NotifierId;
52 namespace message_center {
54 class ProfileNotifierGroup : public message_center::NotifierGroup {
55 public:
56 ProfileNotifierGroup(const gfx::Image& icon,
57 const base::string16& display_name,
58 const base::string16& login_info,
59 size_t index,
60 const base::FilePath& profile_path);
61 ProfileNotifierGroup(const gfx::Image& icon,
62 const base::string16& display_name,
63 const base::string16& login_info,
64 size_t index,
65 Profile* profile);
66 virtual ~ProfileNotifierGroup() {}
68 Profile* profile() const { return profile_; }
70 private:
71 Profile* profile_;
74 ProfileNotifierGroup::ProfileNotifierGroup(const gfx::Image& icon,
75 const base::string16& display_name,
76 const base::string16& login_info,
77 size_t index,
78 const base::FilePath& profile_path)
79 : message_center::NotifierGroup(icon, display_name, login_info, index),
80 profile_(NULL) {
81 // Try to get the profile
82 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,
89 size_t index,
90 Profile* profile)
91 : message_center::NotifierGroup(icon, display_name, login_info, index),
92 profile_(profile) {
95 } // namespace message_center
97 namespace {
98 class NotifierComparator {
99 public:
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;
106 if (collator_) {
107 return base::i18n::CompareString16WithCollator(*collator_, n1->name,
108 n2->name) == UCOL_LESS;
110 return n1->name < n2->name;
113 private:
114 icu::Collator* collator_;
117 } // namespace
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.
128 registrar_.Add(this,
129 chrome::NOTIFICATION_PROFILE_CREATED,
130 content::NotificationService::AllBrowserContextsAndSources());
131 registrar_.Add(this,
132 chrome::NOTIFICATION_PROFILE_ADDED,
133 content::NotificationService::AllBrowserContextsAndSources());
134 registrar_.Add(this,
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);
144 #endif
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);
154 #endif
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)
191 return;
193 current_notifier_group_ = index;
194 FOR_EACH_OBSERVER(message_center::NotifierSettingsObserver,
195 observers_,
196 NotifierGroupChanged());
199 void MessageCenterSettingsController::GetNotifierList(
200 std::vector<Notifier*>* notifiers) {
201 DCHECK(notifiers);
202 if (notifier_groups_.size() <= current_notifier_group_)
203 return;
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
217 // crbug.com/222931
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();
222 ++iter) {
223 const extensions::Extension* extension = iter->get();
224 if (!extension->permissions_data()->HasAPIPermission(
225 extensions::APIPermission::kNotifications)) {
226 continue;
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())
233 continue;
235 NotifierId notifier_id(NotifierId::APPLICATION, extension->id());
236 notifiers->push_back(new Notifier(
237 notifier_id,
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());
250 patterns_.clear();
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") {
256 continue;
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(
264 notifier_id,
265 name,
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
270 // that URL.
271 favicon_service->GetFaviconImageForPageURL(
272 url,
273 base::Bind(&MessageCenterSettingsController::OnFaviconLoaded,
274 base::Unretained(this),
275 url),
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,
287 screenshot_name,
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);
293 #endif
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,
305 bool enabled) {
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()) {
329 if (enabled) {
330 DesktopNotificationProfileUtil::GrantPermission(
331 profile, notifier.notifier_id.url);
332 } else {
333 DesktopNotificationProfileUtil::DenyPermission(
334 profile, notifier.notifier_id.url);
336 } else {
337 LOG(ERROR) << "Invalid url pattern: "
338 << notifier.notifier_id.url.spec();
340 } else {
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()) {
347 pattern =
348 ContentSettingsPattern::FromURLNoWildcard(notifier.notifier_id.url);
349 } else {
350 LOG(ERROR) << "Invalid url pattern: "
351 << notifier.notifier_id.url.spec();
354 if (pattern.IsValid())
355 DesktopNotificationProfileUtil::ClearSetting(profile, pattern);
357 } else {
358 NotifierStateTrackerFactory::GetForProfile(profile)
359 ->SetNotifierEnabled(notifier.notifier_id, enabled);
361 FOR_EACH_OBSERVER(message_center::NotifierSettingsObserver,
362 observers_,
363 NotifierEnabledChanged(notifier.notifier_id, enabled));
366 void MessageCenterSettingsController::OnNotifierSettingsClosing() {
367 DCHECK(favicon_tracker_.get());
368 favicon_tracker_->TryCancelAll();
369 patterns_.clear();
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)
377 return false;
379 const std::string& extension_id = notifier_id.id;
381 if (notifier_groups_.size() < current_notifier_group_)
382 return false;
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)
397 return;
399 const std::string& extension_id = notifier_id.id;
401 if (notifier_groups_.size() < current_notifier_group_)
402 return;
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(
415 const GURL& url,
416 const favicon_base::FaviconImageResult& favicon_result) {
417 FOR_EACH_OBSERVER(message_center::NotifierSettingsObserver,
418 observers_,
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);
428 #endif
430 void MessageCenterSettingsController::SetAppImage(const std::string& id,
431 const gfx::ImageSkia& image) {
432 FOR_EACH_OBSERVER(message_center::NotifierSettingsObserver,
433 observers_,
434 UpdateIconImage(NotifierId(NotifierId::APPLICATION, id),
435 gfx::Image(image)));
438 void MessageCenterSettingsController::Observe(
439 int type,
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()) {
446 return;
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() {
473 // Already created.
474 if (!notifier_groups_.empty())
475 return;
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())
480 return;
482 user_manager::User* user = user_manager->GetActiveUser();
483 Profile* profile =
484 chromeos::ProfileHelper::Get()->GetProfileByUserUnsafe(user);
485 DCHECK(profile);
486 notifier_groups_.push_back(
487 new message_center::ProfileNotifierGroup(gfx::Image(user->GetImage()),
488 user->GetDisplayName(),
489 user->GetDisplayName(),
491 profile));
493 FOR_EACH_OBSERVER(message_center::NotifierSettingsObserver,
494 observers_,
495 NotifierGroupChanged());
497 #endif
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)
513 continue;
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()) {
523 continue;
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()))
531 continue;
532 #endif
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(
549 FROM_HERE,
550 base::Bind(
551 &MessageCenterSettingsController::CreateNotifierGroupForGuestLogin,
552 weak_factory_.GetWeakPtr()));
554 #endif
556 if (notify) {
557 FOR_EACH_OBSERVER(message_center::NotifierSettingsObserver,
558 observers_,
559 NotifierGroupChanged());