message_center_settings_controller.cc revision 1e9bf3e0803691d0a228da41fc608347b6db4340
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/favicon/favicon_service.h"
17#include "chrome/browser/favicon/favicon_service_factory.h"
18#include "chrome/browser/history/history_types.h"
19#include "chrome/browser/notifications/desktop_notification_service.h"
20#include "chrome/browser/notifications/desktop_notification_service_factory.h"
21#include "chrome/browser/notifications/sync_notifier/chrome_notifier_service.h"
22#include "chrome/browser/notifications/sync_notifier/chrome_notifier_service_factory.h"
23#include "chrome/browser/profiles/profile.h"
24#include "chrome/browser/profiles/profile_info_cache.h"
25#include "chrome/browser/profiles/profile_manager.h"
26#include "chrome/common/cancelable_task_tracker.h"
27#include "chrome/common/extensions/extension_constants.h"
28#include "chrome/common/favicon/favicon_types.h"
29#include "content/public/browser/notification_service.h"
30#include "content/public/browser/notification_source.h"
31#include "grit/theme_resources.h"
32#include "grit/ui_strings.h"
33#include "ui/base/l10n/l10n_util.h"
34#include "ui/base/resource/resource_bundle.h"
35#include "ui/gfx/image/image.h"
36#include "ui/message_center/message_center_style.h"
37
38#if defined(OS_CHROMEOS)
39#include "ash/system/system_notifier.h"
40#include "chrome/browser/chromeos/profiles/profile_helper.h"
41#endif
42
43using message_center::Notifier;
44using message_center::NotifierId;
45
46namespace message_center {
47class ProfileNotifierGroup : public message_center::NotifierGroup {
48 public:
49  ProfileNotifierGroup(const gfx::Image& icon,
50                       const string16& display_name,
51                       const string16& login_info,
52                       size_t index,
53                       const base::FilePath& profile_path);
54  virtual ~ProfileNotifierGroup() {}
55
56  Profile* profile() const { return profile_; }
57
58 private:
59  Profile* profile_;
60};
61
62ProfileNotifierGroup::ProfileNotifierGroup(const gfx::Image& icon,
63                                           const string16& display_name,
64                                           const string16& login_info,
65                                           size_t index,
66                                           const base::FilePath& profile_path)
67    : message_center::NotifierGroup(icon, display_name, login_info, index),
68      profile_(NULL) {
69  // Try to get the profile
70  profile_ =
71      g_browser_process->profile_manager()->GetProfileByPath(profile_path);
72}
73}  // namespace message_center
74
75namespace {
76class NotifierComparator {
77 public:
78  explicit NotifierComparator(icu::Collator* collator) : collator_(collator) {}
79
80  bool operator() (Notifier* n1, Notifier* n2) {
81    return base::i18n::CompareString16WithCollator(
82        collator_, n1->name, n2->name) == UCOL_LESS;
83  }
84
85 private:
86  icu::Collator* collator_;
87};
88
89bool SimpleCompareNotifiers(Notifier* n1, Notifier* n2) {
90  return n1->name < n2->name;
91}
92
93}  // namespace
94
95MessageCenterSettingsController::MessageCenterSettingsController(
96    ProfileInfoCache* profile_info_cache)
97    : current_notifier_group_(0), profile_info_cache_(profile_info_cache) {
98  DCHECK(profile_info_cache_);
99  // The following events all represent changes that may need to be reflected in
100  // the profile selector context menu, so listen for them all.  We'll just
101  // rebuild the list when we get any of them.
102  registrar_.Add(this,
103                 chrome::NOTIFICATION_PROFILE_CREATED,
104                 content::NotificationService::AllBrowserContextsAndSources());
105  registrar_.Add(this,
106                 chrome::NOTIFICATION_PROFILE_ADDED,
107                 content::NotificationService::AllBrowserContextsAndSources());
108  registrar_.Add(this,
109                 chrome::NOTIFICATION_PROFILE_DESTROYED,
110                 content::NotificationService::AllBrowserContextsAndSources());
111  registrar_.Add(this,
112                 chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED,
113                 content::NotificationService::AllBrowserContextsAndSources());
114  RebuildNotifierGroups();
115}
116
117MessageCenterSettingsController::~MessageCenterSettingsController() {
118}
119
120void MessageCenterSettingsController::AddObserver(
121    message_center::NotifierSettingsObserver* observer) {
122  observers_.AddObserver(observer);
123}
124
125void MessageCenterSettingsController::RemoveObserver(
126    message_center::NotifierSettingsObserver* observer) {
127  observers_.RemoveObserver(observer);
128}
129
130size_t MessageCenterSettingsController::GetNotifierGroupCount() const {
131  return notifier_groups_.size();
132}
133
134const message_center::NotifierGroup&
135MessageCenterSettingsController::GetNotifierGroupAt(size_t index) const {
136  DCHECK_LT(index, notifier_groups_.size());
137  return *(notifier_groups_[index]);
138}
139
140bool MessageCenterSettingsController::IsNotifierGroupActiveAt(
141    size_t index) const {
142  return current_notifier_group_ == index;
143}
144
145const message_center::NotifierGroup&
146MessageCenterSettingsController::GetActiveNotifierGroup() const {
147  DCHECK_LT(current_notifier_group_, notifier_groups_.size());
148  return *(notifier_groups_[current_notifier_group_]);
149}
150
151void MessageCenterSettingsController::SwitchToNotifierGroup(size_t index) {
152  DCHECK_LT(index, notifier_groups_.size());
153  if (current_notifier_group_ == index)
154    return;
155
156  current_notifier_group_ = index;
157  FOR_EACH_OBSERVER(message_center::NotifierSettingsObserver,
158                    observers_,
159                    NotifierGroupChanged());
160}
161
162void MessageCenterSettingsController::GetNotifierList(
163    std::vector<Notifier*>* notifiers) {
164  DCHECK(notifiers);
165  // Temporarily use the last used profile to prevent chrome from crashing when
166  // the default profile is not loaded.
167  Profile* profile = GetCurrentProfile();
168  if (!profile)
169    return;
170
171  DesktopNotificationService* notification_service =
172      DesktopNotificationServiceFactory::GetForProfile(profile);
173
174  UErrorCode error;
175  scoped_ptr<icu::Collator> collator(icu::Collator::createInstance(error));
176  scoped_ptr<NotifierComparator> comparator;
177  if (!U_FAILURE(error))
178    comparator.reset(new NotifierComparator(collator.get()));
179
180  ExtensionService* extension_service = profile->GetExtensionService();
181  const ExtensionSet* extension_set = extension_service->extensions();
182  // The extension icon size has to be 32x32 at least to load bigger icons if
183  // the icon doesn't exist for the specified size, and in that case it falls
184  // back to the default icon. The fetched icon will be resized in the settings
185  // dialog. See chrome/browser/extensions/extension_icon_image.cc and
186  // crbug.com/222931
187  app_icon_loader_.reset(new extensions::AppIconLoaderImpl(
188      profile, extension_misc::EXTENSION_ICON_SMALL, this));
189  for (ExtensionSet::const_iterator iter = extension_set->begin();
190       iter != extension_set->end();
191       ++iter) {
192    const extensions::Extension* extension = iter->get();
193    if (!extension->HasAPIPermission(
194            extensions::APIPermission::kNotification)) {
195      continue;
196    }
197
198    NotifierId notifier_id(NotifierId::APPLICATION, extension->id());
199    notifiers->push_back(new Notifier(
200        notifier_id,
201        UTF8ToUTF16(extension->name()),
202        notification_service->IsNotifierEnabled(notifier_id)));
203    app_icon_loader_->FetchImage(extension->id());
204  }
205
206  if (notifier::ChromeNotifierServiceFactory::UseSyncedNotifications(
207          CommandLine::ForCurrentProcess())) {
208    notifier::ChromeNotifierService* sync_notifier_service =
209        notifier::ChromeNotifierServiceFactory::GetInstance()->GetForProfile(
210            profile, Profile::EXPLICIT_ACCESS);
211    if (sync_notifier_service) {
212      sync_notifier_service->GetSyncedNotificationServices(notifiers);
213
214      if (comparator)
215        std::sort(notifiers->begin(), notifiers->end(), *comparator);
216      else
217        std::sort(notifiers->begin(), notifiers->end(), SimpleCompareNotifiers);
218    }
219  }
220
221  int app_count = notifiers->size();
222
223  ContentSettingsForOneType settings;
224  notification_service->GetNotificationsSettings(&settings);
225  FaviconService* favicon_service =
226      FaviconServiceFactory::GetForProfile(profile, Profile::EXPLICIT_ACCESS);
227  favicon_tracker_.reset(new CancelableTaskTracker());
228  patterns_.clear();
229  for (ContentSettingsForOneType::const_iterator iter = settings.begin();
230       iter != settings.end(); ++iter) {
231    if (iter->primary_pattern == ContentSettingsPattern::Wildcard() &&
232        iter->secondary_pattern == ContentSettingsPattern::Wildcard() &&
233        iter->source != "preference") {
234      continue;
235    }
236
237    std::string url_pattern = iter->primary_pattern.ToString();
238    string16 name = UTF8ToUTF16(url_pattern);
239    GURL url(url_pattern);
240    NotifierId notifier_id(url);
241    notifiers->push_back(new Notifier(
242        notifier_id,
243        name,
244        notification_service->IsNotifierEnabled(notifier_id)));
245    patterns_[name] = iter->primary_pattern;
246    FaviconService::FaviconForURLParams favicon_params(
247        profile,
248        url,
249        chrome::FAVICON | chrome::TOUCH_ICON,
250        message_center::kSettingsIconSize);
251    // Note that favicon service obtains the favicon from history. This means
252    // that it will fail to obtain the image if there are no history data for
253    // that URL.
254    favicon_service->GetFaviconImageForURL(
255        favicon_params,
256        base::Bind(&MessageCenterSettingsController::OnFaviconLoaded,
257                   base::Unretained(this), url),
258        favicon_tracker_.get());
259  }
260
261  // Screenshot notification feature is only for ChromeOS. See crbug.com/238358
262#if defined(OS_CHROMEOS)
263  const string16 screenshot_name =
264      l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_NOTIFIER_SCREENSHOT_NAME);
265  NotifierId screenshot_notifier_id(ash::system_notifier::NOTIFIER_SCREENSHOT);
266  Notifier* const screenshot_notifier = new Notifier(
267      screenshot_notifier_id,
268      screenshot_name,
269      notification_service->IsNotifierEnabled(screenshot_notifier_id));
270  screenshot_notifier->icon =
271      ui::ResourceBundle::GetSharedInstance().GetImageNamed(
272          IDR_SCREENSHOT_NOTIFICATION_ICON);
273  notifiers->push_back(screenshot_notifier);
274#endif
275
276  if (comparator) {
277    std::sort(notifiers->begin() + app_count, notifiers->end(), *comparator);
278  } else {
279    std::sort(notifiers->begin() + app_count, notifiers->end(),
280              SimpleCompareNotifiers);
281  }
282}
283
284void MessageCenterSettingsController::SetNotifierEnabled(
285    const Notifier& notifier,
286    bool enabled) {
287  Profile* profile = GetCurrentProfile();
288  DCHECK(profile);
289
290  DesktopNotificationService* notification_service =
291      DesktopNotificationServiceFactory::GetForProfile(profile);
292
293  if (notifier.notifier_id.type == NotifierId::WEB_PAGE) {
294    // WEB_PAGE notifier cannot handle in DesktopNotificationService
295    // since it has the exact URL pattern.
296    // TODO(mukai): fix this.
297    ContentSetting default_setting =
298        notification_service->GetDefaultContentSetting(NULL);
299    DCHECK(default_setting == CONTENT_SETTING_ALLOW ||
300           default_setting == CONTENT_SETTING_BLOCK ||
301           default_setting == CONTENT_SETTING_ASK);
302    if ((enabled && default_setting != CONTENT_SETTING_ALLOW) ||
303        (!enabled && default_setting == CONTENT_SETTING_ALLOW)) {
304      if (notifier.notifier_id.url.is_valid()) {
305        if (enabled)
306          notification_service->GrantPermission(notifier.notifier_id.url);
307        else
308          notification_service->DenyPermission(notifier.notifier_id.url);
309      } else {
310        LOG(ERROR) << "Invalid url pattern: "
311                   << notifier.notifier_id.url.spec();
312      }
313    } else {
314      std::map<string16, ContentSettingsPattern>::const_iterator iter =
315          patterns_.find(notifier.name);
316      if (iter != patterns_.end()) {
317        notification_service->ClearSetting(iter->second);
318      } else {
319        LOG(ERROR) << "Invalid url pattern: "
320                   << notifier.notifier_id.url.spec();
321      }
322    }
323  } else {
324    notification_service->SetNotifierEnabled(notifier.notifier_id, enabled);
325    if (notifier.notifier_id.type == NotifierId::SYNCED_NOTIFICATION_SERVICE) {
326      notifier::ChromeNotifierService* notifier_service =
327          notifier::ChromeNotifierServiceFactory::GetInstance()->GetForProfile(
328              profile, Profile::EXPLICIT_ACCESS);
329      notifier_service->OnSyncedNotificationServiceEnabled(
330          notifier.notifier_id.id, enabled);
331    }
332  }
333}
334
335void MessageCenterSettingsController::OnNotifierSettingsClosing() {
336  DCHECK(favicon_tracker_.get());
337  favicon_tracker_->TryCancelAll();
338  patterns_.clear();
339}
340
341void MessageCenterSettingsController::OnFaviconLoaded(
342    const GURL& url,
343    const chrome::FaviconImageResult& favicon_result) {
344  FOR_EACH_OBSERVER(message_center::NotifierSettingsObserver,
345                    observers_,
346                    UpdateIconImage(NotifierId(url), favicon_result.image));
347}
348
349
350void MessageCenterSettingsController::SetAppImage(const std::string& id,
351                                                  const gfx::ImageSkia& image) {
352  FOR_EACH_OBSERVER(message_center::NotifierSettingsObserver,
353                    observers_,
354                    UpdateIconImage(NotifierId(NotifierId::APPLICATION, id),
355                                    gfx::Image(image)));
356}
357
358void MessageCenterSettingsController::Observe(
359    int type,
360    const content::NotificationSource& source,
361    const content::NotificationDetails& details) {
362  RebuildNotifierGroups();
363  FOR_EACH_OBSERVER(message_center::NotifierSettingsObserver,
364                    observers_,
365                    NotifierGroupChanged());
366}
367
368Profile* MessageCenterSettingsController::GetCurrentProfile() {
369  if (notifier_groups_.size() > current_notifier_group_)
370    return notifier_groups_[current_notifier_group_]->profile();
371
372#if defined(OS_CHROMEOS)
373  return ProfileManager::GetDefaultProfileOrOffTheRecord();
374#else
375  NOTREACHED();
376  return NULL;
377#endif
378}
379
380void MessageCenterSettingsController::RebuildNotifierGroups() {
381  notifier_groups_.clear();
382  current_notifier_group_ = 0;
383
384  const size_t count = profile_info_cache_->GetNumberOfProfiles();
385
386  for (size_t i = 0; i < count; ++i) {
387    scoped_ptr<message_center::ProfileNotifierGroup> group(
388        new message_center::ProfileNotifierGroup(
389            profile_info_cache_->GetAvatarIconOfProfileAtIndex(i),
390            profile_info_cache_->GetNameOfProfileAtIndex(i),
391            profile_info_cache_->GetUserNameOfProfileAtIndex(i),
392            i,
393            profile_info_cache_->GetPathOfProfileAtIndex(i)));
394    if (group->profile() == NULL)
395      continue;
396
397#if defined(OS_CHROMEOS)
398    // In ChromeOS, the login screen first creates a dummy profile which is not
399    // actually used, and then the real profile for the user is created when
400    // login (or turns into kiosk mode). This profile should be skipped.
401    if (chromeos::ProfileHelper::IsSigninProfile(group->profile()))
402      continue;
403#endif
404    notifier_groups_.push_back(group.release());
405  }
406}
407