1// Copyright (c) 2011 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/background_application_list_model.h"
6
7#include <algorithm>
8#include <set>
9
10#include "base/stl_util-inl.h"
11#include "base/utf_string_conversions.h"
12#include "chrome/app/chrome_command_ids.h"
13#include "chrome/browser/background_mode_manager.h"
14#include "chrome/browser/browser_process.h"
15#include "chrome/browser/extensions/extension_prefs.h"
16#include "chrome/browser/extensions/extension_service.h"
17#include "chrome/browser/extensions/image_loading_tracker.h"
18#include "chrome/browser/profiles/profile.h"
19#include "chrome/common/extensions/extension.h"
20#include "chrome/common/extensions/extension_resource.h"
21#include "content/common/notification_details.h"
22#include "content/common/notification_source.h"
23#include "ui/base/l10n/l10n_util_collator.h"
24
25class ExtensionNameComparator {
26 public:
27  explicit ExtensionNameComparator(icu::Collator* collator);
28  bool operator()(const Extension* x, const Extension* y);
29
30 private:
31  icu::Collator* collator_;
32};
33
34ExtensionNameComparator::ExtensionNameComparator(icu::Collator* collator)
35  : collator_(collator) {
36}
37
38bool ExtensionNameComparator::operator()(const Extension* x,
39                                         const Extension* y) {
40  return l10n_util::StringComparator<string16>(collator_)(
41    UTF8ToUTF16(x->name()),
42    UTF8ToUTF16(y->name()));
43}
44
45// Background application representation, private to the
46// BackgroundApplicationListModel class.
47class BackgroundApplicationListModel::Application
48  : public ImageLoadingTracker::Observer {
49 public:
50  Application(BackgroundApplicationListModel* model,
51              const Extension* an_extension);
52
53  virtual ~Application();
54
55  // Invoked when a request icon is available.
56  virtual void OnImageLoaded(SkBitmap* image,
57                             const ExtensionResource& resource,
58                             int index);
59
60  // Uses the FILE thread to request this extension's icon, sized
61  // appropriately.
62  void RequestIcon(Extension::Icons size);
63
64  const Extension* extension_;
65  scoped_ptr<SkBitmap> icon_;
66  BackgroundApplicationListModel* model_;
67  ImageLoadingTracker tracker_;
68};
69
70namespace {
71void GetServiceApplications(ExtensionService* service,
72                            ExtensionList* applications_result) {
73  const ExtensionList* extensions = service->extensions();
74
75  for (ExtensionList::const_iterator cursor = extensions->begin();
76       cursor != extensions->end();
77       ++cursor) {
78    const Extension* extension = *cursor;
79    if (BackgroundApplicationListModel::IsBackgroundApp(*extension))
80      applications_result->push_back(extension);
81  }
82  std::string locale = g_browser_process->GetApplicationLocale();
83  icu::Locale loc(locale.c_str());
84  UErrorCode error = U_ZERO_ERROR;
85  scoped_ptr<icu::Collator> collator(icu::Collator::createInstance(loc, error));
86  sort(applications_result->begin(), applications_result->end(),
87       ExtensionNameComparator(collator.get()));
88}
89
90bool HasBackgroundAppPermission(
91    const std::set<std::string>& api_permissions) {
92  return Extension::HasApiPermission(
93      api_permissions, Extension::kBackgroundPermission);
94}
95}  // namespace
96
97void
98BackgroundApplicationListModel::Observer::OnApplicationDataChanged(
99    const Extension* extension) {
100}
101
102void
103BackgroundApplicationListModel::Observer::OnApplicationListChanged() {
104}
105
106BackgroundApplicationListModel::Observer::~Observer() {
107}
108
109BackgroundApplicationListModel::Application::~Application() {
110}
111
112BackgroundApplicationListModel::Application::Application(
113    BackgroundApplicationListModel* model,
114    const Extension* extension)
115    : extension_(extension),
116      icon_(NULL),
117      model_(model),
118      ALLOW_THIS_IN_INITIALIZER_LIST(tracker_(this)) {
119}
120
121void BackgroundApplicationListModel::Application::OnImageLoaded(
122    SkBitmap* image,
123    const ExtensionResource& resource,
124    int index) {
125  if (!image)
126    return;
127  icon_.reset(new SkBitmap(*image));
128  model_->OnApplicationDataChanged(extension_);
129}
130
131void BackgroundApplicationListModel::Application::RequestIcon(
132    Extension::Icons size) {
133  ExtensionResource resource = extension_->GetIconResource(
134      size, ExtensionIconSet::MATCH_BIGGER);
135  tracker_.LoadImage(extension_, resource, gfx::Size(size, size),
136                     ImageLoadingTracker::CACHE);
137}
138
139BackgroundApplicationListModel::~BackgroundApplicationListModel() {
140  STLDeleteContainerPairSecondPointers(applications_.begin(),
141                                       applications_.end());
142}
143
144BackgroundApplicationListModel::BackgroundApplicationListModel(Profile* profile)
145    : profile_(profile) {
146  DCHECK(profile_);
147  registrar_.Add(this,
148                 NotificationType::EXTENSION_LOADED,
149                 Source<Profile>(profile));
150  registrar_.Add(this,
151                 NotificationType::EXTENSION_UNLOADED,
152                 Source<Profile>(profile));
153  registrar_.Add(this,
154                 NotificationType::EXTENSIONS_READY,
155                 Source<Profile>(profile));
156  ExtensionService* service = profile->GetExtensionService();
157  if (service && service->is_ready())
158    Update();
159}
160
161void BackgroundApplicationListModel::AddObserver(Observer* observer) {
162  observers_.AddObserver(observer);
163}
164
165void BackgroundApplicationListModel::AssociateApplicationData(
166    const Extension* extension) {
167  DCHECK(IsBackgroundApp(*extension));
168  Application* application = FindApplication(extension);
169  if (!application) {
170    // App position is used as a dynamic command and so must be less than any
171    // predefined command id.
172    if (applications_.size() >= IDC_MinimumLabelValue) {
173      LOG(ERROR) << "Background application limit of " << IDC_MinimumLabelValue
174                 << " exceeded.  Ignoring.";
175      return;
176    }
177    application = new Application(this, extension);
178    applications_[extension->id()] = application;
179    application->RequestIcon(Extension::EXTENSION_ICON_BITTY);
180  }
181}
182
183void BackgroundApplicationListModel::DissociateApplicationData(
184    const Extension* extension) {
185  ApplicationMap::iterator found = applications_.find(extension->id());
186  if (found != applications_.end()) {
187    delete found->second;
188    applications_.erase(found);
189  }
190}
191
192const Extension* BackgroundApplicationListModel::GetExtension(
193    int position) const {
194  DCHECK(position >= 0 && static_cast<size_t>(position) < extensions_.size());
195  return extensions_[position];
196}
197
198const BackgroundApplicationListModel::Application*
199BackgroundApplicationListModel::FindApplication(
200    const Extension* extension) const {
201  const std::string& id = extension->id();
202  ApplicationMap::const_iterator found = applications_.find(id);
203  return (found == applications_.end()) ? NULL : found->second;
204}
205
206BackgroundApplicationListModel::Application*
207BackgroundApplicationListModel::FindApplication(const Extension* extension) {
208  const std::string& id = extension->id();
209  ApplicationMap::iterator found = applications_.find(id);
210  return (found == applications_.end()) ? NULL : found->second;
211}
212
213const SkBitmap* BackgroundApplicationListModel::GetIcon(
214    const Extension* extension) {
215  const Application* application = FindApplication(extension);
216  if (application)
217    return application->icon_.get();
218  AssociateApplicationData(extension);
219  return NULL;
220}
221
222int BackgroundApplicationListModel::GetPosition(
223    const Extension* extension) const {
224  int position = 0;
225  const std::string& id = extension->id();
226  for (ExtensionList::const_iterator cursor = extensions_.begin();
227       cursor != extensions_.end();
228       ++cursor, ++position) {
229    if (id == cursor->get()->id())
230      return position;
231  }
232  NOTREACHED();
233  return -1;
234}
235
236// static
237bool BackgroundApplicationListModel::IsBackgroundApp(
238    const Extension& extension) {
239  return HasBackgroundAppPermission(extension.api_permissions());
240}
241
242void BackgroundApplicationListModel::Observe(
243    NotificationType type,
244    const NotificationSource& source,
245    const NotificationDetails& details) {
246  if (type == NotificationType::EXTENSIONS_READY) {
247    Update();
248    return;
249  }
250  ExtensionService* service = profile_->GetExtensionService();
251  if (!service || !service->is_ready())
252    return;
253
254  switch (type.value) {
255    case NotificationType::EXTENSION_LOADED:
256      OnExtensionLoaded(Details<Extension>(details).ptr());
257      break;
258    case NotificationType::EXTENSION_UNLOADED:
259      OnExtensionUnloaded(Details<UnloadedExtensionInfo>(details)->extension);
260      break;
261    default:
262      NOTREACHED() << "Received unexpected notification";
263  }
264}
265
266void BackgroundApplicationListModel::OnApplicationDataChanged(
267    const Extension* extension) {
268  FOR_EACH_OBSERVER(Observer, observers_, OnApplicationDataChanged(extension));
269}
270
271void BackgroundApplicationListModel::OnExtensionLoaded(Extension* extension) {
272  // We only care about extensions that are background applications
273  if (!IsBackgroundApp(*extension))
274    return;
275  AssociateApplicationData(extension);
276  Update();
277}
278
279void BackgroundApplicationListModel::OnExtensionUnloaded(
280    const Extension* extension) {
281  if (!IsBackgroundApp(*extension))
282    return;
283  Update();
284  DissociateApplicationData(extension);
285}
286
287void BackgroundApplicationListModel::RemoveObserver(Observer* observer) {
288  observers_.RemoveObserver(observer);
289}
290
291// Update queries the extensions service of the profile with which the model was
292// initialized to determine the current set of background applications.  If that
293// differs from the old list, it generates OnApplicationListChanged events for
294// each observer.
295void BackgroundApplicationListModel::Update() {
296  ExtensionService* service = profile_->GetExtensionService();
297
298  // Discover current background applications, compare with previous list, which
299  // is consistently sorted, and notify observers if they differ.
300  ExtensionList extensions;
301  GetServiceApplications(service, &extensions);
302  ExtensionList::const_iterator old_cursor = extensions_.begin();
303  ExtensionList::const_iterator new_cursor = extensions.begin();
304  while (old_cursor != extensions_.end() &&
305         new_cursor != extensions.end() &&
306         (*old_cursor)->name() == (*new_cursor)->name() &&
307         (*old_cursor)->id() == (*new_cursor)->id()) {
308    ++old_cursor;
309    ++new_cursor;
310  }
311  if (old_cursor != extensions_.end() || new_cursor != extensions.end()) {
312    extensions_ = extensions;
313    FOR_EACH_OBSERVER(Observer, observers_, OnApplicationListChanged());
314  }
315}
316