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