Home | History | Annotate | Download | only in notifications
      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.
      4 
      5 #include "chrome/browser/notifications/message_center_settings_controller.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/command_line.h"
     10 #include "base/i18n/string_compare.h"
     11 #include "base/strings/utf_string_conversions.h"
     12 #include "chrome/browser/browser_process.h"
     13 #include "chrome/browser/chrome_notification_types.h"
     14 #include "chrome/browser/extensions/app_icon_loader_impl.h"
     15 #include "chrome/browser/extensions/extension_service.h"
     16 #include "chrome/browser/favicon/favicon_service.h"
     17 #include "chrome/browser/favicon/favicon_service_factory.h"
     18 #include "chrome/browser/history/history_types.h"
     19 #include "chrome/browser/notifications/desktop_notification_service.h"
     20 #include "chrome/browser/notifications/desktop_notification_service_factory.h"
     21 #include "chrome/browser/notifications/sync_notifier/chrome_notifier_service.h"
     22 #include "chrome/browser/notifications/sync_notifier/chrome_notifier_service_factory.h"
     23 #include "chrome/browser/profiles/profile.h"
     24 #include "chrome/browser/profiles/profile_info_cache.h"
     25 #include "chrome/browser/profiles/profile_manager.h"
     26 #include "chrome/common/cancelable_task_tracker.h"
     27 #include "chrome/common/extensions/extension_constants.h"
     28 #include "chrome/common/favicon/favicon_types.h"
     29 #include "content/public/browser/notification_service.h"
     30 #include "content/public/browser/notification_source.h"
     31 #include "grit/theme_resources.h"
     32 #include "grit/ui_strings.h"
     33 #include "ui/base/l10n/l10n_util.h"
     34 #include "ui/base/resource/resource_bundle.h"
     35 #include "ui/gfx/image/image.h"
     36 #include "ui/message_center/message_center_style.h"
     37 
     38 #if defined(OS_CHROMEOS)
     39 #include "chrome/browser/chromeos/profiles/profile_helper.h"
     40 #endif
     41 
     42 using message_center::Notifier;
     43 using message_center::NotifierId;
     44 
     45 namespace message_center {
     46 class ProfileNotifierGroup : public message_center::NotifierGroup {
     47  public:
     48   ProfileNotifierGroup(const gfx::Image& icon,
     49                        const string16& display_name,
     50                        const string16& login_info,
     51                        size_t index,
     52                        const base::FilePath& profile_path);
     53   virtual ~ProfileNotifierGroup() {}
     54 
     55   Profile* profile() const { return profile_; }
     56 
     57  private:
     58   Profile* profile_;
     59 };
     60 
     61 ProfileNotifierGroup::ProfileNotifierGroup(const gfx::Image& icon,
     62                                            const string16& display_name,
     63                                            const string16& login_info,
     64                                            size_t index,
     65                                            const base::FilePath& profile_path)
     66     : message_center::NotifierGroup(icon, display_name, login_info, index),
     67       profile_(NULL) {
     68   // Try to get the profile
     69   profile_ =
     70       g_browser_process->profile_manager()->GetProfileByPath(profile_path);
     71 }
     72 }  // namespace message_center
     73 
     74 namespace {
     75 class NotifierComparator {
     76  public:
     77   explicit NotifierComparator(icu::Collator* collator) : collator_(collator) {}
     78 
     79   bool operator() (Notifier* n1, Notifier* n2) {
     80     return base::i18n::CompareString16WithCollator(
     81         collator_, n1->name, n2->name) == UCOL_LESS;
     82   }
     83 
     84  private:
     85   icu::Collator* collator_;
     86 };
     87 
     88 bool SimpleCompareNotifiers(Notifier* n1, Notifier* n2) {
     89   return n1->name < n2->name;
     90 }
     91 
     92 }  // namespace
     93 
     94 MessageCenterSettingsController::MessageCenterSettingsController(
     95     ProfileInfoCache* profile_info_cache)
     96     : current_notifier_group_(0), profile_info_cache_(profile_info_cache) {
     97   DCHECK(profile_info_cache_);
     98   // The following events all represent changes that may need to be reflected in
     99   // the profile selector context menu, so listen for them all.  We'll just
    100   // rebuild the list when we get any of them.
    101   registrar_.Add(this,
    102                  chrome::NOTIFICATION_PROFILE_CREATED,
    103                  content::NotificationService::AllBrowserContextsAndSources());
    104   registrar_.Add(this,
    105                  chrome::NOTIFICATION_PROFILE_ADDED,
    106                  content::NotificationService::AllBrowserContextsAndSources());
    107   registrar_.Add(this,
    108                  chrome::NOTIFICATION_PROFILE_DESTROYED,
    109                  content::NotificationService::AllBrowserContextsAndSources());
    110   registrar_.Add(this,
    111                  chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED,
    112                  content::NotificationService::AllBrowserContextsAndSources());
    113   RebuildNotifierGroups();
    114 }
    115 
    116 MessageCenterSettingsController::~MessageCenterSettingsController() {
    117 }
    118 
    119 void MessageCenterSettingsController::AddObserver(
    120     message_center::NotifierSettingsObserver* observer) {
    121   observers_.AddObserver(observer);
    122 }
    123 
    124 void MessageCenterSettingsController::RemoveObserver(
    125     message_center::NotifierSettingsObserver* observer) {
    126   observers_.RemoveObserver(observer);
    127 }
    128 
    129 size_t MessageCenterSettingsController::GetNotifierGroupCount() const {
    130   return notifier_groups_.size();
    131 }
    132 
    133 const message_center::NotifierGroup&
    134 MessageCenterSettingsController::GetNotifierGroupAt(size_t index) const {
    135   DCHECK_LT(index, notifier_groups_.size());
    136   return *(notifier_groups_[index]);
    137 }
    138 
    139 bool MessageCenterSettingsController::IsNotifierGroupActiveAt(
    140     size_t index) const {
    141   return current_notifier_group_ == index;
    142 }
    143 
    144 const message_center::NotifierGroup&
    145 MessageCenterSettingsController::GetActiveNotifierGroup() const {
    146   DCHECK_LT(current_notifier_group_, notifier_groups_.size());
    147   return *(notifier_groups_[current_notifier_group_]);
    148 }
    149 
    150 void MessageCenterSettingsController::SwitchToNotifierGroup(size_t index) {
    151   DCHECK_LT(index, notifier_groups_.size());
    152   if (current_notifier_group_ == index)
    153     return;
    154 
    155   current_notifier_group_ = index;
    156   FOR_EACH_OBSERVER(message_center::NotifierSettingsObserver,
    157                     observers_,
    158                     NotifierGroupChanged());
    159 }
    160 
    161 void MessageCenterSettingsController::GetNotifierList(
    162     std::vector<Notifier*>* notifiers) {
    163   DCHECK(notifiers);
    164   // TODO(mukai): Fix this for multi-profile.
    165   // Temporarily use the last used profile to prevent chrome from crashing when
    166   // the default profile is not loaded.
    167   message_center::ProfileNotifierGroup* group =
    168       notifier_groups_[current_notifier_group_];
    169   Profile* profile = group->profile();
    170   if (!profile)
    171     return;
    172 
    173   DesktopNotificationService* notification_service =
    174       DesktopNotificationServiceFactory::GetForProfile(profile);
    175 
    176   UErrorCode error;
    177   scoped_ptr<icu::Collator> collator(icu::Collator::createInstance(error));
    178   scoped_ptr<NotifierComparator> comparator;
    179   if (!U_FAILURE(error))
    180     comparator.reset(new NotifierComparator(collator.get()));
    181 
    182   ExtensionService* extension_service = profile->GetExtensionService();
    183   const ExtensionSet* extension_set = extension_service->extensions();
    184   // The extension icon size has to be 32x32 at least to load bigger icons if
    185   // the icon doesn't exist for the specified size, and in that case it falls
    186   // back to the default icon. The fetched icon will be resized in the settings
    187   // dialog. See chrome/browser/extensions/extension_icon_image.cc and
    188   // crbug.com/222931
    189   app_icon_loader_.reset(new extensions::AppIconLoaderImpl(
    190       profile, extension_misc::EXTENSION_ICON_SMALL, this));
    191   for (ExtensionSet::const_iterator iter = extension_set->begin();
    192        iter != extension_set->end();
    193        ++iter) {
    194     const extensions::Extension* extension = iter->get();
    195     if (!extension->HasAPIPermission(
    196             extensions::APIPermission::kNotification)) {
    197       continue;
    198     }
    199 
    200     NotifierId notifier_id(NotifierId::APPLICATION, extension->id());
    201     notifiers->push_back(new Notifier(
    202         notifier_id,
    203         UTF8ToUTF16(extension->name()),
    204         notification_service->IsNotifierEnabled(notifier_id)));
    205     app_icon_loader_->FetchImage(extension->id());
    206   }
    207 
    208   if (notifier::ChromeNotifierServiceFactory::UseSyncedNotifications(
    209           CommandLine::ForCurrentProcess())) {
    210     notifier::ChromeNotifierService* sync_notifier_service =
    211         notifier::ChromeNotifierServiceFactory::GetInstance()->GetForProfile(
    212             profile, Profile::EXPLICIT_ACCESS);
    213     sync_notifier_service->GetSyncedNotificationServices(notifiers);
    214 
    215     if (comparator)
    216       std::sort(notifiers->begin(), notifiers->end(), *comparator);
    217     else
    218       std::sort(notifiers->begin(), notifiers->end(), SimpleCompareNotifiers);
    219   }
    220 
    221   int app_count = notifiers->size();
    222 
    223   ContentSettingsForOneType settings;
    224   notification_service->GetNotificationsSettings(&settings);
    225   FaviconService* favicon_service =
    226       FaviconServiceFactory::GetForProfile(profile, Profile::EXPLICIT_ACCESS);
    227   favicon_tracker_.reset(new CancelableTaskTracker());
    228   patterns_.clear();
    229   for (ContentSettingsForOneType::const_iterator iter = settings.begin();
    230        iter != settings.end(); ++iter) {
    231     if (iter->primary_pattern == ContentSettingsPattern::Wildcard() &&
    232         iter->secondary_pattern == ContentSettingsPattern::Wildcard() &&
    233         iter->source != "preference") {
    234       continue;
    235     }
    236 
    237     std::string url_pattern = iter->primary_pattern.ToString();
    238     string16 name = UTF8ToUTF16(url_pattern);
    239     GURL url(url_pattern);
    240     NotifierId notifier_id(url);
    241     notifiers->push_back(new Notifier(
    242         notifier_id,
    243         name,
    244         notification_service->IsNotifierEnabled(notifier_id)));
    245     patterns_[name] = iter->primary_pattern;
    246     FaviconService::FaviconForURLParams favicon_params(
    247         profile,
    248         url,
    249         chrome::FAVICON | chrome::TOUCH_ICON,
    250         message_center::kSettingsIconSize);
    251     // Note that favicon service obtains the favicon from history. This means
    252     // that it will fail to obtain the image if there are no history data for
    253     // that URL.
    254     favicon_service->GetFaviconImageForURL(
    255         favicon_params,
    256         base::Bind(&MessageCenterSettingsController::OnFaviconLoaded,
    257                    base::Unretained(this), url),
    258         favicon_tracker_.get());
    259   }
    260 
    261   // Screenshot notification feature is only for ChromeOS. See crbug.com/238358
    262 #if defined(OS_CHROMEOS)
    263   const string16 screenshot_name =
    264       l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_NOTIFIER_SCREENSHOT_NAME);
    265   NotifierId screenshot_notifier_id(NotifierId::SCREENSHOT);
    266   Notifier* const screenshot_notifier = new Notifier(
    267       screenshot_notifier_id,
    268       screenshot_name,
    269       notification_service->IsNotifierEnabled(screenshot_notifier_id));
    270   screenshot_notifier->icon =
    271       ui::ResourceBundle::GetSharedInstance().GetImageNamed(
    272           IDR_SCREENSHOT_NOTIFICATION_ICON);
    273   notifiers->push_back(screenshot_notifier);
    274 #endif
    275 
    276   if (comparator) {
    277     std::sort(notifiers->begin() + app_count, notifiers->end(), *comparator);
    278   } else {
    279     std::sort(notifiers->begin() + app_count, notifiers->end(),
    280               SimpleCompareNotifiers);
    281   }
    282 }
    283 
    284 void MessageCenterSettingsController::SetNotifierEnabled(
    285     const Notifier& notifier,
    286     bool enabled) {
    287   Profile* profile = notifier_groups_[current_notifier_group_]->profile();
    288   DCHECK(profile);
    289 
    290   DesktopNotificationService* notification_service =
    291       DesktopNotificationServiceFactory::GetForProfile(profile);
    292 
    293   if (notifier.notifier_id.type == NotifierId::WEB_PAGE) {
    294     // WEB_PAGE notifier cannot handle in DesktopNotificationService
    295     // since it has the exact URL pattern.
    296     // TODO(mukai): fix this.
    297     ContentSetting default_setting =
    298         notification_service->GetDefaultContentSetting(NULL);
    299     DCHECK(default_setting == CONTENT_SETTING_ALLOW ||
    300            default_setting == CONTENT_SETTING_BLOCK ||
    301            default_setting == CONTENT_SETTING_ASK);
    302     if ((enabled && default_setting != CONTENT_SETTING_ALLOW) ||
    303         (!enabled && default_setting == CONTENT_SETTING_ALLOW)) {
    304       if (notifier.notifier_id.url.is_valid()) {
    305         if (enabled)
    306           notification_service->GrantPermission(notifier.notifier_id.url);
    307         else
    308           notification_service->DenyPermission(notifier.notifier_id.url);
    309       } else {
    310         LOG(ERROR) << "Invalid url pattern: "
    311                    << notifier.notifier_id.url.spec();
    312       }
    313     } else {
    314       std::map<string16, ContentSettingsPattern>::const_iterator iter =
    315           patterns_.find(notifier.name);
    316       if (iter != patterns_.end()) {
    317         notification_service->ClearSetting(iter->second);
    318       } else {
    319         LOG(ERROR) << "Invalid url pattern: "
    320                    << notifier.notifier_id.url.spec();
    321       }
    322     }
    323   } else {
    324     notification_service->SetNotifierEnabled(notifier.notifier_id, enabled);
    325     if (notifier.notifier_id.type == NotifierId::SYNCED_NOTIFICATION_SERVICE) {
    326       notifier::ChromeNotifierService* notifier_service =
    327           notifier::ChromeNotifierServiceFactory::GetInstance()->GetForProfile(
    328               profile, Profile::EXPLICIT_ACCESS);
    329       notifier_service->OnSyncedNotificationServiceEnabled(
    330           notifier.notifier_id.id, enabled);
    331     }
    332   }
    333 }
    334 
    335 void MessageCenterSettingsController::OnNotifierSettingsClosing() {
    336   DCHECK(favicon_tracker_.get());
    337   favicon_tracker_->TryCancelAll();
    338   patterns_.clear();
    339 }
    340 
    341 void MessageCenterSettingsController::OnFaviconLoaded(
    342     const GURL& url,
    343     const chrome::FaviconImageResult& favicon_result) {
    344   FOR_EACH_OBSERVER(message_center::NotifierSettingsObserver,
    345                     observers_,
    346                     UpdateIconImage(NotifierId(url), favicon_result.image));
    347 }
    348 
    349 
    350 void MessageCenterSettingsController::SetAppImage(const std::string& id,
    351                                                   const gfx::ImageSkia& image) {
    352   FOR_EACH_OBSERVER(message_center::NotifierSettingsObserver,
    353                     observers_,
    354                     UpdateIconImage(NotifierId(NotifierId::APPLICATION, id),
    355                                     gfx::Image(image)));
    356 }
    357 
    358 void MessageCenterSettingsController::Observe(
    359     int type,
    360     const content::NotificationSource& source,
    361     const content::NotificationDetails& details) {
    362   RebuildNotifierGroups();
    363   FOR_EACH_OBSERVER(message_center::NotifierSettingsObserver,
    364                     observers_,
    365                     NotifierGroupChanged());
    366 }
    367 
    368 void MessageCenterSettingsController::RebuildNotifierGroups() {
    369   notifier_groups_.clear();
    370   current_notifier_group_ = 0;
    371 
    372   const size_t count = profile_info_cache_->GetNumberOfProfiles();
    373 
    374   for (size_t i = 0; i < count; ++i) {
    375     scoped_ptr<message_center::ProfileNotifierGroup> group(
    376         new message_center::ProfileNotifierGroup(
    377             profile_info_cache_->GetAvatarIconOfProfileAtIndex(i),
    378             profile_info_cache_->GetNameOfProfileAtIndex(i),
    379             profile_info_cache_->GetUserNameOfProfileAtIndex(i),
    380             i,
    381             profile_info_cache_->GetPathOfProfileAtIndex(i)));
    382     if (group->profile() == NULL)
    383       continue;
    384 #if defined(OS_CHROMEOS)
    385     // In ChromeOS, the login screen first creates a dummy profile which is not
    386     // actually used, and then the real profile for the user is created when
    387     // login (or turns into kiosk mode). This profile should be skipped.
    388     if (chromeos::ProfileHelper::IsSigninProfile(group->profile()))
    389       continue;
    390 #endif
    391     notifier_groups_.push_back(group.release());
    392   }
    393 }
    394