// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "chrome/browser/apps/app_service/app_icon/app_icon_writer.h"
#include "base/containers/span.h"
#include "base/files/file_util.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/apps/app_service/app_icon/app_icon_util.h"
#include "chrome/browser/apps/app_service/app_icon/compressed_icon_getter.h"
#include "chrome/browser/apps/app_service/app_icon/dip_px_util.h"
#include "chrome/browser/profiles/profile.h"
#include "ui/base/resource/resource_scale_factor.h"
namespace {
bool WriteIconFiles(const base::FilePath& base_path,
const std::string& id,
int32_t icon_size_in_px,
apps::IconValuePtr iv) {
if (!iv || iv->icon_type != apps::IconType::kCompressed) {
return false;
}
if (!iv->foreground_icon_png_data.empty() &&
!iv->background_icon_png_data.empty()) {
// For the adaptive icon, write the foreground and background icon data to
// the local files.
const auto foreground_icon_path =
apps::GetForegroundIconPath(base_path, id, icon_size_in_px);
const auto background_icon_path =
apps::GetBackgroundIconPath(base_path, id, icon_size_in_px);
if (!base::CreateDirectory(foreground_icon_path.DirName()) ||
!base::CreateDirectory(background_icon_path.DirName())) {
return false;
}
auto foreground_icon_data = base::make_span(
&iv->foreground_icon_png_data[0], iv->foreground_icon_png_data.size());
auto background_icon_data = base::make_span(
&iv->background_icon_png_data[0], iv->background_icon_png_data.size());
return base::WriteFile(foreground_icon_path, foreground_icon_data) &&
base::WriteFile(background_icon_path, background_icon_data);
}
if (iv->compressed.empty()) {
return false;
}
const auto icon_path =
apps::GetIconPath(base_path, id, icon_size_in_px, iv->is_maskable_icon);
if (!base::CreateDirectory(icon_path.DirName())) {
return false;
}
auto icon_data = base::make_span(&iv->compressed[0], iv->compressed.size());
return base::WriteFile(icon_path, icon_data);
}
} // namespace
namespace apps {
AppIconWriter::Key::Key(const std::string& id, int32_t size_in_dip)
: id_(id), size_in_dip_(size_in_dip) {}
AppIconWriter::Key::~Key() = default;
bool AppIconWriter::Key::operator<(const Key& other) const {
if (this->id_ != other.id_) {
return this->id_ < other.id_;
}
return this->size_in_dip_ < other.size_in_dip_;
}
AppIconWriter::PendingResult::PendingResult() = default;
AppIconWriter::PendingResult::~PendingResult() = default;
AppIconWriter::PendingResult::PendingResult(PendingResult&&) = default;
AppIconWriter::PendingResult& AppIconWriter::PendingResult::operator=(
PendingResult&&) = default;
AppIconWriter::AppIconWriter(Profile* profile) : profile_(profile) {}
AppIconWriter::~AppIconWriter() = default;
void AppIconWriter::InstallIcon(CompressedIconGetter* compressed_icon_getter,
const std::string& id,
int32_t size_in_dip,
base::OnceCallback<void(bool)> callback) {
CHECK(compressed_icon_getter);
Key key(id, size_in_dip);
auto it = pending_results_.find(key);
if (it != pending_results_.end()) {
it->second.callbacks.push_back(std::move(callback));
return;
}
pending_results_[Key(id, size_in_dip)].callbacks.push_back(
std::move(callback));
it = pending_results_.find(key);
std::set<ui::ResourceScaleFactor> scale_factors;
// For the adaptive icon, we need to get the raw icon data for all scale
// factors to convert to the uncompressed icon, then generate the adaptive
// icon with both the foreground and the background icon files. Since we don't
// know whether the icon is an adaptive icon, we always get the raw icon data
// for all scale factors.
for (const auto scale_factor : ui::GetSupportedResourceScaleFactors()) {
it->second.scale_factors.insert(scale_factor);
scale_factors.insert(scale_factor);
}
for (const auto scale_factor : scale_factors) {
auto pending_results_it = pending_results_.find(key);
if (pending_results_it == pending_results_.end()) {
// If the getting icon request has been removed (e.g. the compressed
// icon data doesn't exist) by OnIconLoad, we don't need to continue
// getting other scale factors for the icon request.
return;
}
compressed_icon_getter->GetCompressedIconData(
id, size_in_dip, scale_factor,
base::BindOnce(&AppIconWriter::OnIconLoad,
weak_ptr_factory_.GetWeakPtr(), id, size_in_dip,
scale_factor));
}
}
void AppIconWriter::OnIconLoad(const std::string& id,
int32_t size_in_dip,
ui::ResourceScaleFactor scale_factor,
IconValuePtr iv) {
auto it = pending_results_.find(Key(id, size_in_dip));
if (it == pending_results_.end()) {
return;
}
if (!iv || iv->icon_type != IconType::kCompressed ||
(iv->compressed.empty() && iv->foreground_icon_png_data.empty() &&
iv->background_icon_png_data.empty())) {
for (auto& callback : it->second.callbacks) {
std::move(callback).Run(false);
}
pending_results_.erase(it);
return;
}
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
base::BindOnce(
&WriteIconFiles, profile_->GetPath(), id,
apps_util::ConvertDipToPxForScale(size_in_dip, scale_factor),
std::move(iv)),
base::BindOnce(&AppIconWriter::OnWriteIconFile,
weak_ptr_factory_.GetWeakPtr(), id, size_in_dip,
scale_factor));
}
void AppIconWriter::OnWriteIconFile(const std::string& id,
int32_t size_in_dip,
ui::ResourceScaleFactor scale_factor,
bool ret) {
auto it = pending_results_.find(Key(id, size_in_dip));
if (it == pending_results_.end()) {
return;
}
it->second.complete_scale_factors.insert(scale_factor);
if (it->second.scale_factors != it->second.complete_scale_factors) {
// There are other icon fetching requests, so wait for other icon data.
return;
}
// The icon fetching requests have returned for all scale factors, so we can
// call callbacks to return the result, and remove the icon request from
// `pending_results_`.
for (auto& callback : it->second.callbacks) {
std::move(callback).Run(ret);
}
pending_results_.erase(it);
}
} // namespace apps