// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/background_application_list_model.h"
#include <algorithm>
#include <set>
#include "base/stl_util-inl.h"
#include "base/utf_string_conversions.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/background_mode_manager.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/extension_prefs.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/image_loading_tracker.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_resource.h"
#include "content/common/notification_details.h"
#include "content/common/notification_source.h"
#include "ui/base/l10n/l10n_util_collator.h"
class ExtensionNameComparator {
public:
explicit ExtensionNameComparator(icu::Collator* collator);
bool operator()(const Extension* x, const Extension* y);
private:
icu::Collator* collator_;
};
ExtensionNameComparator::ExtensionNameComparator(icu::Collator* collator)
: collator_(collator) {
}
bool ExtensionNameComparator::operator()(const Extension* x,
const Extension* y) {
return l10n_util::StringComparator<string16>(collator_)(
UTF8ToUTF16(x->name()),
UTF8ToUTF16(y->name()));
}
// Background application representation, private to the
// BackgroundApplicationListModel class.
class BackgroundApplicationListModel::Application
: public ImageLoadingTracker::Observer {
public:
Application(BackgroundApplicationListModel* model,
const Extension* an_extension);
virtual ~Application();
// Invoked when a request icon is available.
virtual void OnImageLoaded(SkBitmap* image,
const ExtensionResource& resource,
int index);
// Uses the FILE thread to request this extension's icon, sized
// appropriately.
void RequestIcon(Extension::Icons size);
const Extension* extension_;
scoped_ptr<SkBitmap> icon_;
BackgroundApplicationListModel* model_;
ImageLoadingTracker tracker_;
};
namespace {
void GetServiceApplications(ExtensionService* service,
ExtensionList* applications_result) {
const ExtensionList* extensions = service->extensions();
for (ExtensionList::const_iterator cursor = extensions->begin();
cursor != extensions->end();
++cursor) {
const Extension* extension = *cursor;
if (BackgroundApplicationListModel::IsBackgroundApp(*extension))
applications_result->push_back(extension);
}
std::string locale = g_browser_process->GetApplicationLocale();
icu::Locale loc(locale.c_str());
UErrorCode error = U_ZERO_ERROR;
scoped_ptr<icu::Collator> collator(icu::Collator::createInstance(loc, error));
sort(applications_result->begin(), applications_result->end(),
ExtensionNameComparator(collator.get()));
}
bool HasBackgroundAppPermission(
const std::set<std::string>& api_permissions) {
return Extension::HasApiPermission(
api_permissions, Extension::kBackgroundPermission);
}
} // namespace
void
BackgroundApplicationListModel::Observer::OnApplicationDataChanged(
const Extension* extension) {
}
void
BackgroundApplicationListModel::Observer::OnApplicationListChanged() {
}
BackgroundApplicationListModel::Observer::~Observer() {
}
BackgroundApplicationListModel::Application::~Application() {
}
BackgroundApplicationListModel::Application::Application(
BackgroundApplicationListModel* model,
const Extension* extension)
: extension_(extension),
icon_(NULL),
model_(model),
ALLOW_THIS_IN_INITIALIZER_LIST(tracker_(this)) {
}
void BackgroundApplicationListModel::Application::OnImageLoaded(
SkBitmap* image,
const ExtensionResource& resource,
int index) {
if (!image)
return;
icon_.reset(new SkBitmap(*image));
model_->OnApplicationDataChanged(extension_);
}
void BackgroundApplicationListModel::Application::RequestIcon(
Extension::Icons size) {
ExtensionResource resource = extension_->GetIconResource(
size, ExtensionIconSet::MATCH_BIGGER);
tracker_.LoadImage(extension_, resource, gfx::Size(size, size),
ImageLoadingTracker::CACHE);
}
BackgroundApplicationListModel::~BackgroundApplicationListModel() {
STLDeleteContainerPairSecondPointers(applications_.begin(),
applications_.end());
}
BackgroundApplicationListModel::BackgroundApplicationListModel(Profile* profile)
: profile_(profile) {
DCHECK(profile_);
registrar_.Add(this,
NotificationType::EXTENSION_LOADED,
Source<Profile>(profile));
registrar_.Add(this,
NotificationType::EXTENSION_UNLOADED,
Source<Profile>(profile));
registrar_.Add(this,
NotificationType::EXTENSIONS_READY,
Source<Profile>(profile));
ExtensionService* service = profile->GetExtensionService();
if (service && service->is_ready())
Update();
}
void BackgroundApplicationListModel::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void BackgroundApplicationListModel::AssociateApplicationData(
const Extension* extension) {
DCHECK(IsBackgroundApp(*extension));
Application* application = FindApplication(extension);
if (!application) {
// App position is used as a dynamic command and so must be less than any
// predefined command id.
if (applications_.size() >= IDC_MinimumLabelValue) {
LOG(ERROR) << "Background application limit of " << IDC_MinimumLabelValue
<< " exceeded. Ignoring.";
return;
}
application = new Application(this, extension);
applications_[extension->id()] = application;
application->RequestIcon(Extension::EXTENSION_ICON_BITTY);
}
}
void BackgroundApplicationListModel::DissociateApplicationData(
const Extension* extension) {
ApplicationMap::iterator found = applications_.find(extension->id());
if (found != applications_.end()) {
delete found->second;
applications_.erase(found);
}
}
const Extension* BackgroundApplicationListModel::GetExtension(
int position) const {
DCHECK(position >= 0 && static_cast<size_t>(position) < extensions_.size());
return extensions_[position];
}
const BackgroundApplicationListModel::Application*
BackgroundApplicationListModel::FindApplication(
const Extension* extension) const {
const std::string& id = extension->id();
ApplicationMap::const_iterator found = applications_.find(id);
return (found == applications_.end()) ? NULL : found->second;
}
BackgroundApplicationListModel::Application*
BackgroundApplicationListModel::FindApplication(const Extension* extension) {
const std::string& id = extension->id();
ApplicationMap::iterator found = applications_.find(id);
return (found == applications_.end()) ? NULL : found->second;
}
const SkBitmap* BackgroundApplicationListModel::GetIcon(
const Extension* extension) {
const Application* application = FindApplication(extension);
if (application)
return application->icon_.get();
AssociateApplicationData(extension);
return NULL;
}
int BackgroundApplicationListModel::GetPosition(
const Extension* extension) const {
int position = 0;
const std::string& id = extension->id();
for (ExtensionList::const_iterator cursor = extensions_.begin();
cursor != extensions_.end();
++cursor, ++position) {
if (id == cursor->get()->id())
return position;
}
NOTREACHED();
return -1;
}
// static
bool BackgroundApplicationListModel::IsBackgroundApp(
const Extension& extension) {
return HasBackgroundAppPermission(extension.api_permissions());
}
void BackgroundApplicationListModel::Observe(
NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
if (type == NotificationType::EXTENSIONS_READY) {
Update();
return;
}
ExtensionService* service = profile_->GetExtensionService();
if (!service || !service->is_ready())
return;
switch (type.value) {
case NotificationType::EXTENSION_LOADED:
OnExtensionLoaded(Details<Extension>(details).ptr());
break;
case NotificationType::EXTENSION_UNLOADED:
OnExtensionUnloaded(Details<UnloadedExtensionInfo>(details)->extension);
break;
default:
NOTREACHED() << "Received unexpected notification";
}
}
void BackgroundApplicationListModel::OnApplicationDataChanged(
const Extension* extension) {
FOR_EACH_OBSERVER(Observer, observers_, OnApplicationDataChanged(extension));
}
void BackgroundApplicationListModel::OnExtensionLoaded(Extension* extension) {
// We only care about extensions that are background applications
if (!IsBackgroundApp(*extension))
return;
AssociateApplicationData(extension);
Update();
}
void BackgroundApplicationListModel::OnExtensionUnloaded(
const Extension* extension) {
if (!IsBackgroundApp(*extension))
return;
Update();
DissociateApplicationData(extension);
}
void BackgroundApplicationListModel::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
// Update queries the extensions service of the profile with which the model was
// initialized to determine the current set of background applications. If that
// differs from the old list, it generates OnApplicationListChanged events for
// each observer.
void BackgroundApplicationListModel::Update() {
ExtensionService* service = profile_->GetExtensionService();
// Discover current background applications, compare with previous list, which
// is consistently sorted, and notify observers if they differ.
ExtensionList extensions;
GetServiceApplications(service, &extensions);
ExtensionList::const_iterator old_cursor = extensions_.begin();
ExtensionList::const_iterator new_cursor = extensions.begin();
while (old_cursor != extensions_.end() &&
new_cursor != extensions.end() &&
(*old_cursor)->name() == (*new_cursor)->name() &&
(*old_cursor)->id() == (*new_cursor)->id()) {
++old_cursor;
++new_cursor;
}
if (old_cursor != extensions_.end() || new_cursor != extensions.end()) {
extensions_ = extensions;
FOR_EACH_OBSERVER(Observer, observers_, OnApplicationListChanged());
}
}