Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / notifications / message_center_settings_controller.cc
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
51 using message_center::Notifier;
52 using message_center::NotifierId;
53
54 namespace message_center {
55
56 class 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
76 ProfileNotifierGroup::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
88 ProfileNotifierGroup::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
99 namespace {
100 class 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
113 bool SimpleCompareNotifiers(Notifier* n1, Notifier* n2) {
114   return n1->name < n2->name;
115 }
116
117 }  // namespace
118
119 MessageCenterSettingsController::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
149 MessageCenterSettingsController::~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
157 void MessageCenterSettingsController::AddObserver(
158     message_center::NotifierSettingsObserver* observer) {
159   observers_.AddObserver(observer);
160 }
161
162 void MessageCenterSettingsController::RemoveObserver(
163     message_center::NotifierSettingsObserver* observer) {
164   observers_.RemoveObserver(observer);
165 }
166
167 size_t MessageCenterSettingsController::GetNotifierGroupCount() const {
168   return notifier_groups_.size();
169 }
170
171 const message_center::NotifierGroup&
172 MessageCenterSettingsController::GetNotifierGroupAt(size_t index) const {
173   DCHECK_LT(index, notifier_groups_.size());
174   return *(notifier_groups_[index]);
175 }
176
177 bool MessageCenterSettingsController::IsNotifierGroupActiveAt(
178     size_t index) const {
179   return current_notifier_group_ == index;
180 }
181
182 const message_center::NotifierGroup&
183 MessageCenterSettingsController::GetActiveNotifierGroup() const {
184   DCHECK_LT(current_notifier_group_, notifier_groups_.size());
185   return *(notifier_groups_[current_notifier_group_]);
186 }
187
188 void 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
199 void 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
304 void 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
355 void MessageCenterSettingsController::OnNotifierSettingsClosing() {
356   DCHECK(favicon_tracker_.get());
357   favicon_tracker_->TryCancelAll();
358   patterns_.clear();
359 }
360
361 bool 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
380 void 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
402 void 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)
412 void MessageCenterSettingsController::ActiveUserChanged(
413     const user_manager::User* active_user) {
414   RebuildNotifierGroups();
415 }
416 #endif
417
418 void 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
426 void 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)
444 void 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
471 void 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 }