blob: 4105def1585001cd63d3934bd2fe81e92b710994 [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 <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/files/file_util.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "chrome/browser/apps/app_service/dip_px_util.h"
#include "chrome/browser/extensions/chrome_app_icon.h"
#include "chrome/browser/extensions/chrome_app_icon_loader.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_util.h"
#include "content/public/common/service_manager_connection.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/image_loader.h"
#include "extensions/common/manifest_handlers/icons_handler.h"
#include "services/data_decoder/public/cpp/decode_image.h"
#include "skia/ext/image_operations.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_operations.h"
#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/extensions/gfx_utils.h"
#include "chrome/browser/ui/app_list/md_icon_normalizer.h"
#endif
namespace {
std::vector<uint8_t> ReadFileAsCompressedData(const base::FilePath path) {
std::string data;
base::ReadFileToString(path, &data);
return std::vector<uint8_t>(data.begin(), data.end());
}
// Runs |callback| passing an IconValuePtr with a compressed image: a
// std::vector<uint8_t>.
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));
}
// Like RunCallbackWithCompressedData, but calls "fallback(callback)" if the
// data is empty.
void RunCallbackWithCompressedDataWithFallback(
apps::mojom::Publisher::LoadIconCallback callback,
base::OnceCallback<void(apps::mojom::Publisher::LoadIconCallback)> fallback,
std::vector<uint8_t> data) {
if (data.empty()) {
std::move(fallback).Run(std::move(callback));
return;
}
RunCallbackWithCompressedData(std::move(callback), std::move(data));
}
// Runs |callback| passing an IconValuePtr with an uncompressed image: a
// SkBitmap.
void RunCallbackWithUncompressedSkBitmap(
apps::mojom::Publisher::LoadIconCallback callback,
const SkBitmap& bitmap) {
apps::mojom::IconValuePtr iv = apps::mojom::IconValue::New();
iv->icon_compression = apps::mojom::IconCompression::kUncompressed;
iv->uncompressed = gfx::ImageSkia(gfx::ImageSkiaRep(bitmap, 0.0f));
std::move(callback).Run(std::move(iv));
}
// Runs |callback| after converting (in a separate sandboxed process) from a
// std::vector<uint8_t> to a SkBitmap. It calls "fallback(callback)" if the
// data is empty.
void RunCallbackWithCompressedDataToUncompressWithFallback(
apps::mojom::Publisher::LoadIconCallback callback,
base::OnceCallback<void(apps::mojom::Publisher::LoadIconCallback)> fallback,
std::vector<uint8_t> data) {
if (data.empty()) {
std::move(fallback).Run(std::move(callback));
return;
}
data_decoder::DecodeImage(
content::ServiceManagerConnection::GetForProcess()->GetConnector(), data,
data_decoder::mojom::ImageCodec::DEFAULT, false,
data_decoder::kDefaultMaxSizeInBytes, gfx::Size(),
base::BindOnce(&RunCallbackWithUncompressedSkBitmap,
std::move(callback)));
}
// Runs |callback| passing an IconValuePtr with an uncompressed image: an
// ImageSkia.
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 a filtered, uncompressed image:
// an Image.
void RunCallbackWithUncompressedImage(
base::OnceCallback<void(gfx::ImageSkia*)> image_filter,
apps::mojom::Publisher::LoadIconCallback callback,
const gfx::Image& image) {
gfx::ImageSkia image_skia = image.AsImageSkia();
std::move(image_filter).Run(&image_skia);
RunCallbackWithUncompressedImageSkia(std::move(callback), image_skia);
}
// Forwards to extensions::ChromeAppIcon::ApplyEffects, with subtle differences
// in argument types. For example, resize_function is a "ResizeFunction" here,
// but a "const ResizeFunction&" in extensions::ChromeAppIcon::ApplyEffects.
void ChromeAppIconApplyEffects(
int resource_size_in_dip,
extensions::ChromeAppIconLoader::ResizeFunction resize_function,
bool apply_chrome_badge,
bool app_launchable,
bool from_bookmark,
gfx::ImageSkia* image_skia) {
extensions::ChromeAppIcon::ApplyEffects(resource_size_in_dip, resize_function,
apply_chrome_badge, app_launchable,
from_bookmark, image_skia);
}
} // namespace
namespace apps {
void LoadIconFromExtension(apps::mojom::IconCompression icon_compression,
int size_hint_in_dip,
content::BrowserContext* context,
const std::string& extension_id,
apps::mojom::Publisher::LoadIconCallback callback) {
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);
switch (icon_compression) {
case apps::mojom::IconCompression::kUnknown:
break;
case apps::mojom::IconCompression::kUncompressed: {
extensions::ChromeAppIconLoader::ResizeFunction resize_function;
bool apply_chrome_badge = false;
#if defined(OS_CHROMEOS)
resize_function =
base::BindRepeating(&app_list::MaybeResizeAndPadIconForMd),
apply_chrome_badge =
extensions::util::ShouldApplyChromeBadge(context, extension_id);
#endif
extensions::ImageLoader::Get(context)->LoadImageAsync(
extension, std::move(ext_resource),
gfx::Size(size_hint_in_px, size_hint_in_px),
base::BindOnce(
&RunCallbackWithUncompressedImage,
base::BindOnce(
&ChromeAppIconApplyEffects, size_hint_in_dip,
resize_function, apply_chrome_badge,
extensions::util::IsAppLaunchable(extension_id, context),
extension->from_bookmark()),
std::move(callback)));
return;
}
case apps::mojom::IconCompression::kCompressed: {
// TODO(crbug.com/826982): do we also want to apply the
// ChromeAppIconApplyEffects image filter here? This will require
// decoding from and re-encoding to PNG before and after the filter.
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
base::BindOnce(&ReadFileAsCompressedData,
ext_resource.GetFilePath()),
base::BindOnce(&RunCallbackWithCompressedData,
std::move(callback)));
return;
}
}
}
std::move(callback).Run(apps::mojom::IconValue::New());
}
void LoadIconFromFileWithFallback(
apps::mojom::IconCompression icon_compression,
int size_hint_in_dip,
const base::FilePath& path,
apps::mojom::Publisher::LoadIconCallback callback,
base::OnceCallback<void(apps::mojom::Publisher::LoadIconCallback)>
fallback) {
// TODO(crbug.com/826982): pass size_hint_in_dip (or _in_px) on to the
// callbacks, and re-size the decoded-from-file image)?
switch (icon_compression) {
case apps::mojom::IconCompression::kUnknown:
break;
case apps::mojom::IconCompression::kUncompressed: {
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
base::BindOnce(&ReadFileAsCompressedData, path),
base::BindOnce(&RunCallbackWithCompressedDataToUncompressWithFallback,
std::move(callback), std::move(fallback)));
return;
}
case apps::mojom::IconCompression::kCompressed: {
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
base::BindOnce(&ReadFileAsCompressedData, path),
base::BindOnce(&RunCallbackWithCompressedDataWithFallback,
std::move(callback), std::move(fallback)));
return;
}
}
std::move(callback).Run(apps::mojom::IconValue::New());
}
void LoadIconFromResource(apps::mojom::IconCompression icon_compression,
int size_hint_in_dip,
int resource_id,
apps::mojom::Publisher::LoadIconCallback callback) {
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