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