blob: cf374179472aedf1feea30344794d0c7facff3fe [file] [log] [blame]
// Copyright 2018 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/apps/app_service/app_icon_factory.h"
#include <cmath>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/files/file_util.h"
#include "base/numerics/safe_conversions.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "chrome/browser/extensions/extension_service.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/image_loader.h"
#include "extensions/common/manifest_handlers/icons_handler.h"
#include "skia/ext/image_operations.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_operations.h"
namespace {
float GetDeviceScaleFactor() {
display::Screen* screen = display::Screen::GetScreen();
if (!screen) {
return 1.0f;
}
return screen->GetPrimaryDisplay().device_scale_factor();
}
int ConvertDipToPx(int dip) {
return base::saturated_cast<int>(
std::floor(static_cast<float>(dip) * GetDeviceScaleFactor()));
}
std::vector<uint8_t> ReadExtensionResource(
extensions::ExtensionResource ext_resource) {
std::string data;
base::ReadFileToString(ext_resource.GetFilePath(), &data);
return std::vector<uint8_t>(data.begin(), data.end());
}
// Runs |callback| passing an IconValuePtr with a compressed image.
void RunCallbackWithCompressedData(
apps::mojom::Publisher::LoadIconCallback callback,
std::vector<uint8_t> data) {
apps::mojom::IconValuePtr iv = apps::mojom::IconValue::New();
iv->icon_compression = data.empty()
? apps::mojom::IconCompression::kUnknown
: apps::mojom::IconCompression::kCompressed;
iv->compressed = std::move(data);
std::move(callback).Run(std::move(iv));
}
// Runs |callback| passing an IconValuePtr with an uncompressed image.
void RunCallbackWithUncompressedImageSkia(
apps::mojom::Publisher::LoadIconCallback callback,
const gfx::ImageSkia image) {
apps::mojom::IconValuePtr iv = apps::mojom::IconValue::New();
iv->icon_compression = apps::mojom::IconCompression::kUncompressed;
iv->uncompressed = image;
std::move(callback).Run(std::move(iv));
}
// Runs |callback| passing an IconValuePtr with an uncompressed image.
void RunCallbackWithUncompressedImage(
apps::mojom::Publisher::LoadIconCallback callback,
const gfx::Image& image) {
RunCallbackWithUncompressedImageSkia(std::move(callback),
image.AsImageSkia());
}
} // namespace
namespace apps {
void LoadIconFromExtension(apps::mojom::IconCompression icon_compression,
int size_hint_in_dip,
apps::mojom::Publisher::LoadIconCallback callback,
content::BrowserContext* context,
const std::string& extension_id) {
int size_hint_in_px = ConvertDipToPx(size_hint_in_dip);
const extensions::Extension* extension =
extensions::ExtensionSystem::Get(context)
->extension_service()
->GetInstalledExtension(extension_id);
if (extension) {
extensions::ExtensionResource ext_resource =
extensions::IconsInfo::GetIconResource(extension, size_hint_in_px,
ExtensionIconSet::MATCH_BIGGER);
// TODO(crbug.com/826982): at some point near here, we should call into
// chrome/browser/extensions/chrome_app_icon.h code to get e.g. graying out
// disabled icons, or rounding off corners. It's not entirely trivial. The
// ChromeAppIcon object expects to be long-lived, calling back into the
// ChromeAppIconDelegate e.g. when the extension's underlying icon changes,
// or when an enabled/disabled state change means graying or un-graying an
// extension's icon.
//
// For example, in chrome/browser/ui/app_list, ExtensionAppItem implements
// ChromeAppIconDelegate, as the ExtensionAppItem is a long-lived object.
// It is a model, in the model-view-controller sense.
//
// In contrast, this API here (a Mojo IPC API) is a request-response IPC,
// with nothing long-lived enough to be the ChromeAppIconDelegate.
switch (icon_compression) {
case apps::mojom::IconCompression::kUnknown:
break;
case apps::mojom::IconCompression::kUncompressed:
extensions::ImageLoader::Get(context)->LoadImageAsync(
extension, std::move(ext_resource),
gfx::Size(size_hint_in_px, size_hint_in_px),
base::BindOnce(&RunCallbackWithUncompressedImage,
std::move(callback)));
return;
case apps::mojom::IconCompression::kCompressed:
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
base::BindOnce(&ReadExtensionResource, std::move(ext_resource)),
base::BindOnce(&RunCallbackWithCompressedData,
std::move(callback)));
return;
}
}
std::move(callback).Run(apps::mojom::IconValue::New());
}
void LoadIconFromResource(apps::mojom::IconCompression icon_compression,
int size_hint_in_dip,
apps::mojom::Publisher::LoadIconCallback callback,
int resource_id) {
if (resource_id != 0) {
switch (icon_compression) {
case apps::mojom::IconCompression::kUnknown:
break;
case apps::mojom::IconCompression::kUncompressed: {
gfx::ImageSkia* unscaled =
ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
resource_id);
RunCallbackWithUncompressedImageSkia(
std::move(callback),
gfx::ImageSkiaOperations::CreateResizedImage(
*unscaled, skia::ImageOperations::RESIZE_BEST,
gfx::Size(size_hint_in_dip, size_hint_in_dip)));
return;
}
case apps::mojom::IconCompression::kCompressed: {
base::StringPiece data =
ui::ResourceBundle::GetSharedInstance().GetRawDataResource(
resource_id);
RunCallbackWithCompressedData(
std::move(callback),
std::vector<uint8_t>(data.begin(), data.end()));
return;
}
}
}
std::move(callback).Run(apps::mojom::IconValue::New());
}
} // namespace apps