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