1// Copyright (c) 2012 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/background_application_list_model.h"
6
7#include <algorithm>
8#include <set>
9
10#include "base/stl_util.h"
11#include "base/strings/utf_string_conversions.h"
12#include "chrome/app/chrome_command_ids.h"
13#include "chrome/browser/background/background_contents_service.h"
14#include "chrome/browser/background/background_contents_service_factory.h"
15#include "chrome/browser/background/background_mode_manager.h"
16#include "chrome/browser/browser_process.h"
17#include "chrome/browser/chrome_notification_types.h"
18#include "chrome/browser/extensions/extension_prefs.h"
19#include "chrome/browser/extensions/extension_service.h"
20#include "chrome/browser/extensions/extension_system.h"
21#include "chrome/browser/extensions/image_loader.h"
22#include "chrome/browser/profiles/profile.h"
23#include "chrome/common/extensions/background_info.h"
24#include "chrome/common/extensions/extension.h"
25#include "chrome/common/extensions/extension_constants.h"
26#include "chrome/common/extensions/extension_icon_set.h"
27#include "chrome/common/extensions/manifest_handlers/icons_handler.h"
28#include "chrome/common/extensions/permissions/permission_set.h"
29#include "content/public/browser/notification_details.h"
30#include "content/public/browser/notification_source.h"
31#include "extensions/common/extension_resource.h"
32#include "ui/base/l10n/l10n_util_collator.h"
33#include "ui/gfx/image/image.h"
34#include "ui/gfx/image/image_skia.h"
35
36using extensions::APIPermission;
37using extensions::Extension;
38using extensions::ExtensionList;
39using extensions::PermissionSet;
40using extensions::UnloadedExtensionInfo;
41using extensions::UpdatedExtensionPermissionsInfo;
42
43class ExtensionNameComparator {
44 public:
45  explicit ExtensionNameComparator(icu::Collator* collator);
46  bool operator()(const scoped_refptr<const Extension>& x,
47                  const scoped_refptr<const Extension>& y);
48
49 private:
50  icu::Collator* collator_;
51};
52
53ExtensionNameComparator::ExtensionNameComparator(icu::Collator* collator)
54  : collator_(collator) {
55}
56
57bool ExtensionNameComparator::operator()(
58    const scoped_refptr<const Extension>& x,
59    const scoped_refptr<const Extension>& y) {
60  return l10n_util::StringComparator<string16>(collator_)(
61      UTF8ToUTF16(x->name()), UTF8ToUTF16(y->name()));
62}
63
64// Background application representation, private to the
65// BackgroundApplicationListModel class.
66class BackgroundApplicationListModel::Application
67    : public base::SupportsWeakPtr<Application> {
68 public:
69  Application(BackgroundApplicationListModel* model,
70              const Extension* an_extension);
71
72  virtual ~Application();
73
74  // Invoked when a request icon is available.
75  void OnImageLoaded(const gfx::Image& image);
76
77  // Uses the FILE thread to request this extension's icon, sized
78  // appropriately.
79  void RequestIcon(extension_misc::ExtensionIcons size);
80
81  const Extension* extension_;
82  scoped_ptr<gfx::ImageSkia> icon_;
83  BackgroundApplicationListModel* model_;
84};
85
86namespace {
87void GetServiceApplications(ExtensionService* service,
88                            ExtensionList* applications_result) {
89  const ExtensionSet* extensions = service->extensions();
90
91  for (ExtensionSet::const_iterator cursor = extensions->begin();
92       cursor != extensions->end();
93       ++cursor) {
94    const Extension* extension = cursor->get();
95    if (BackgroundApplicationListModel::IsBackgroundApp(*extension,
96                                                        service->profile())) {
97      applications_result->push_back(extension);
98    }
99  }
100
101  // Walk the list of terminated extensions also (just because an extension
102  // crashed doesn't mean we should ignore it).
103  extensions = service->terminated_extensions();
104  for (ExtensionSet::const_iterator cursor = extensions->begin();
105       cursor != extensions->end();
106       ++cursor) {
107    const Extension* extension = cursor->get();
108    if (BackgroundApplicationListModel::IsBackgroundApp(*extension,
109                                                        service->profile())) {
110      applications_result->push_back(extension);
111    }
112  }
113
114  std::string locale = g_browser_process->GetApplicationLocale();
115  icu::Locale loc(locale.c_str());
116  UErrorCode error = U_ZERO_ERROR;
117  scoped_ptr<icu::Collator> collator(icu::Collator::createInstance(loc, error));
118  std::sort(applications_result->begin(), applications_result->end(),
119       ExtensionNameComparator(collator.get()));
120}
121
122}  // namespace
123
124void
125BackgroundApplicationListModel::Observer::OnApplicationDataChanged(
126    const Extension* extension, Profile* profile) {
127}
128
129void
130BackgroundApplicationListModel::Observer::OnApplicationListChanged(
131    Profile* profile) {
132}
133
134BackgroundApplicationListModel::Observer::~Observer() {
135}
136
137BackgroundApplicationListModel::Application::~Application() {
138}
139
140BackgroundApplicationListModel::Application::Application(
141    BackgroundApplicationListModel* model,
142    const Extension* extension)
143    : extension_(extension), model_(model) {}
144
145void BackgroundApplicationListModel::Application::OnImageLoaded(
146    const gfx::Image& image) {
147  if (image.IsEmpty())
148    return;
149  icon_.reset(image.CopyImageSkia());
150  model_->SendApplicationDataChangedNotifications(extension_);
151}
152
153void BackgroundApplicationListModel::Application::RequestIcon(
154    extension_misc::ExtensionIcons size) {
155  extensions::ExtensionResource resource =
156      extensions::IconsInfo::GetIconResource(
157          extension_, size, ExtensionIconSet::MATCH_BIGGER);
158  extensions::ImageLoader::Get(model_->profile_)->LoadImageAsync(
159      extension_, resource, gfx::Size(size, size),
160      base::Bind(&Application::OnImageLoaded, AsWeakPtr()));
161}
162
163BackgroundApplicationListModel::~BackgroundApplicationListModel() {
164  STLDeleteContainerPairSecondPointers(applications_.begin(),
165                                       applications_.end());
166}
167
168BackgroundApplicationListModel::BackgroundApplicationListModel(Profile* profile)
169    : profile_(profile) {
170  DCHECK(profile_);
171  registrar_.Add(this,
172                 chrome::NOTIFICATION_EXTENSION_LOADED,
173                 content::Source<Profile>(profile));
174  registrar_.Add(this,
175                 chrome::NOTIFICATION_EXTENSION_UNLOADED,
176                 content::Source<Profile>(profile));
177  registrar_.Add(this,
178                 chrome::NOTIFICATION_EXTENSIONS_READY,
179                 content::Source<Profile>(profile));
180  registrar_.Add(this,
181                 chrome::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED,
182                 content::Source<Profile>(profile));
183  registrar_.Add(this,
184                 chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED,
185                 content::Source<Profile>(profile));
186  ExtensionService* service = extensions::ExtensionSystem::Get(profile)->
187      extension_service();
188  if (service && service->is_ready())
189    Update();
190}
191
192void BackgroundApplicationListModel::AddObserver(Observer* observer) {
193  observers_.AddObserver(observer);
194}
195
196void BackgroundApplicationListModel::AssociateApplicationData(
197    const Extension* extension) {
198  DCHECK(IsBackgroundApp(*extension, profile_));
199  Application* application = FindApplication(extension);
200  if (!application) {
201    // App position is used as a dynamic command and so must be less than any
202    // predefined command id.
203    if (applications_.size() >= IDC_MinimumLabelValue) {
204      LOG(ERROR) << "Background application limit of " << IDC_MinimumLabelValue
205                 << " exceeded.  Ignoring.";
206      return;
207    }
208    application = new Application(this, extension);
209    applications_[extension->id()] = application;
210    Update();
211    application->RequestIcon(extension_misc::EXTENSION_ICON_BITTY);
212  }
213}
214
215void BackgroundApplicationListModel::DissociateApplicationData(
216    const Extension* extension) {
217  ApplicationMap::iterator found = applications_.find(extension->id());
218  if (found != applications_.end()) {
219    delete found->second;
220    applications_.erase(found);
221  }
222}
223
224const Extension* BackgroundApplicationListModel::GetExtension(
225    int position) const {
226  DCHECK(position >= 0 && static_cast<size_t>(position) < extensions_.size());
227  return extensions_[position].get();
228}
229
230const BackgroundApplicationListModel::Application*
231BackgroundApplicationListModel::FindApplication(
232    const Extension* extension) const {
233  const std::string& id = extension->id();
234  ApplicationMap::const_iterator found = applications_.find(id);
235  return (found == applications_.end()) ? NULL : found->second;
236}
237
238BackgroundApplicationListModel::Application*
239BackgroundApplicationListModel::FindApplication(
240    const Extension* extension) {
241  const std::string& id = extension->id();
242  ApplicationMap::iterator found = applications_.find(id);
243  return (found == applications_.end()) ? NULL : found->second;
244}
245
246const gfx::ImageSkia* BackgroundApplicationListModel::GetIcon(
247    const Extension* extension) {
248  const Application* application = FindApplication(extension);
249  if (application)
250    return application->icon_.get();
251  AssociateApplicationData(extension);
252  return NULL;
253}
254
255int BackgroundApplicationListModel::GetPosition(
256    const Extension* extension) const {
257  int position = 0;
258  const std::string& id = extension->id();
259  for (ExtensionList::const_iterator cursor = extensions_.begin();
260       cursor != extensions_.end();
261       ++cursor, ++position) {
262    if (id == cursor->get()->id())
263      return position;
264  }
265  NOTREACHED();
266  return -1;
267}
268
269// static
270bool BackgroundApplicationListModel::IsBackgroundApp(
271    const Extension& extension, Profile* profile) {
272  // An extension is a "background app" if it has the "background API"
273  // permission, and meets one of the following criteria:
274  // 1) It is an extension (not a hosted app).
275  // 2) It is a hosted app, and has a background contents registered or in the
276  //    manifest.
277
278  // Not a background app if we don't have the background permission or
279  // the push messaging permission
280  if (!extension.HasAPIPermission(APIPermission::kBackground) &&
281      !extension.HasAPIPermission(APIPermission::kPushMessaging) )
282    return false;
283
284  // Extensions and packaged apps with background permission are always treated
285  // as background apps.
286  if (!extension.is_hosted_app())
287    return true;
288
289  // Hosted apps with manifest-provided background pages are background apps.
290  if (extensions::BackgroundInfo::HasBackgroundPage(&extension))
291    return true;
292
293  BackgroundContentsService* service =
294      BackgroundContentsServiceFactory::GetForProfile(profile);
295  string16 app_id = ASCIIToUTF16(extension.id());
296  // If we have an active or registered background contents for this app, then
297  // it's a background app. This covers the cases where the app has created its
298  // background contents, but it hasn't navigated yet, or the background
299  // contents crashed and hasn't yet been restarted - in both cases we still
300  // want to treat the app as a background app.
301  if (service->GetAppBackgroundContents(app_id) ||
302      service->HasRegisteredBackgroundContents(app_id)) {
303    return true;
304  }
305
306  // Doesn't meet our criteria, so it's not a background app.
307  return false;
308}
309
310void BackgroundApplicationListModel::Observe(
311    int type,
312    const content::NotificationSource& source,
313    const content::NotificationDetails& details) {
314  if (type == chrome::NOTIFICATION_EXTENSIONS_READY) {
315    Update();
316    return;
317  }
318  ExtensionService* service = extensions::ExtensionSystem::Get(profile_)->
319      extension_service();
320  if (!service || !service->is_ready())
321    return;
322
323  switch (type) {
324    case chrome::NOTIFICATION_EXTENSION_LOADED:
325      OnExtensionLoaded(content::Details<Extension>(details).ptr());
326      break;
327    case chrome::NOTIFICATION_EXTENSION_UNLOADED:
328      OnExtensionUnloaded(
329          content::Details<UnloadedExtensionInfo>(details)->extension);
330      break;
331    case chrome::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED:
332      OnExtensionPermissionsUpdated(
333          content::Details<UpdatedExtensionPermissionsInfo>(details)->extension,
334          content::Details<UpdatedExtensionPermissionsInfo>(details)->reason,
335          content::Details<UpdatedExtensionPermissionsInfo>(details)->
336              permissions);
337      break;
338    case chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED:
339      Update();
340      break;
341    default:
342      NOTREACHED() << "Received unexpected notification";
343  }
344}
345
346void BackgroundApplicationListModel::SendApplicationDataChangedNotifications(
347    const Extension* extension) {
348  FOR_EACH_OBSERVER(Observer, observers_, OnApplicationDataChanged(extension,
349                                                                   profile_));
350}
351
352void BackgroundApplicationListModel::OnExtensionLoaded(
353    const Extension* extension) {
354  // We only care about extensions that are background applications
355  if (!IsBackgroundApp(*extension, profile_))
356    return;
357  AssociateApplicationData(extension);
358}
359
360void BackgroundApplicationListModel::OnExtensionUnloaded(
361    const Extension* extension) {
362  if (!IsBackgroundApp(*extension, profile_))
363    return;
364  Update();
365  DissociateApplicationData(extension);
366}
367
368void BackgroundApplicationListModel::OnExtensionPermissionsUpdated(
369    const Extension* extension,
370    UpdatedExtensionPermissionsInfo::Reason reason,
371    const PermissionSet* permissions) {
372  if (permissions->HasAPIPermission(APIPermission::kBackground)) {
373    switch (reason) {
374      case UpdatedExtensionPermissionsInfo::ADDED:
375        DCHECK(IsBackgroundApp(*extension, profile_));
376        OnExtensionLoaded(extension);
377        break;
378      case UpdatedExtensionPermissionsInfo::REMOVED:
379        DCHECK(!IsBackgroundApp(*extension, profile_));
380        Update();
381        DissociateApplicationData(extension);
382        break;
383      default:
384        NOTREACHED();
385    }
386  }
387}
388
389void BackgroundApplicationListModel::RemoveObserver(Observer* observer) {
390  observers_.RemoveObserver(observer);
391}
392
393// Update queries the extensions service of the profile with which the model was
394// initialized to determine the current set of background applications.  If that
395// differs from the old list, it generates OnApplicationListChanged events for
396// each observer.
397void BackgroundApplicationListModel::Update() {
398  ExtensionService* service = extensions::ExtensionSystem::Get(profile_)->
399      extension_service();
400
401  // Discover current background applications, compare with previous list, which
402  // is consistently sorted, and notify observers if they differ.
403  ExtensionList extensions;
404  GetServiceApplications(service, &extensions);
405  ExtensionList::const_iterator old_cursor = extensions_.begin();
406  ExtensionList::const_iterator new_cursor = extensions.begin();
407  while (old_cursor != extensions_.end() &&
408         new_cursor != extensions.end() &&
409         (*old_cursor)->name() == (*new_cursor)->name() &&
410         (*old_cursor)->id() == (*new_cursor)->id()) {
411    ++old_cursor;
412    ++new_cursor;
413  }
414  if (old_cursor != extensions_.end() || new_cursor != extensions.end()) {
415    extensions_ = extensions;
416    FOR_EACH_OBSERVER(Observer, observers_, OnApplicationListChanged(profile_));
417  }
418}
419