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