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