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