chromium/chrome/browser/apps/app_service/app_icon/app_icon_reader.cc

// 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.

#include "chrome/browser/apps/app_service/app_icon/app_icon_reader.h"

#include "ash/constants/ash_switches.h"
#include "base/files/file_util.h"
#include "base/not_fatal_until.h"
#include "base/task/thread_pool.h"
#include "base/trace_event/trace_event.h"
#include "chrome/browser/apps/app_service/app_icon/app_icon_decoder.h"
#include "chrome/browser/apps/app_service/app_icon/app_icon_factory.h"
#include "chrome/browser/apps/app_service/app_icon/app_icon_util.h"
#include "chrome/browser/apps/app_service/app_icon/dip_px_util.h"
#include "chrome/browser/ash/app_list/arc/arc_app_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/grit/chrome_unscaled_resources.h"
#include "chrome/grit/component_extension_resources.h"
#include "components/app_constants/constants.h"

namespace {

bool UseSmallIcon(int32_t size_in_dip) {
  int size_in_px = apps_util::ConvertDipToPx(
      size_in_dip, /*quantize_to_supported_scale_factor=*/false);
  return size_in_px <= 32;
}

int GetResourceIdForIcon(const std::string& id,
                         int32_t size_in_dip,
                         const apps::IconKey& icon_key) {
  if (id == arc::kPlayStoreAppId) {
    return UseSmallIcon(size_in_dip) ? IDR_ARC_SUPPORT_ICON_32_PNG
                                     : IDR_ARC_SUPPORT_ICON_192_PNG;
  }

  if (ash::switches::IsAshDebugBrowserEnabled() &&
      id == app_constants::kChromeAppId) {
    return UseSmallIcon(size_in_dip) ? IDR_DEBUG_CHROME_APP_ICON_32
                                     : IDR_DEBUG_CHROME_APP_ICON_192;
  }

  return icon_key.resource_id;
}

}  // namespace

namespace apps {

AppIconReader::AppIconReader(Profile* profile) : profile_(profile) {}

AppIconReader::~AppIconReader() = default;

void AppIconReader::ReadIcons(const std::string& id,
                              int32_t size_in_dip,
                              const IconKey& icon_key,
                              IconType icon_type,
                              LoadIconCallback callback) {
  TRACE_EVENT0("ui", "AppIconReader::ReadIcons");
  IconEffects icon_effects = static_cast<IconEffects>(icon_key.icon_effects);
  int resource_id = GetResourceIdForIcon(id, size_in_dip, icon_key);
  if (resource_id != IconKey::kInvalidResourceId) {
    LoadIconFromResource(profile_, id, icon_type, size_in_dip, resource_id,
                         /*is_placeholder_icon=*/false, icon_effects,
                         std::move(callback));
    return;
  }

  const base::FilePath& base_path = profile_->GetPath();

  if (icon_type == IconType::kUnknown) {
    std::move(callback).Run(std::make_unique<apps::IconValue>());
    return;
  }

  decodes_.emplace_back(std::make_unique<AppIconDecoder>(
      base_path, id, size_in_dip,
      base::BindOnce(&AppIconReader::OnUncompressedIconRead,
                     weak_ptr_factory_.GetWeakPtr(), size_in_dip, icon_effects,
                     icon_type, id, std::move(callback))));
  decodes_.back()->Start();
}

void AppIconReader::OnUncompressedIconRead(int32_t size_in_dip,
                                           IconEffects icon_effects,
                                           IconType icon_type,
                                           const std::string& id,
                                           LoadIconCallback callback,
                                           AppIconDecoder* decoder,
                                           IconValuePtr iv) {
  TRACE_EVENT0("ui", "AppIconReader::OnUncompressedIconRead");
  DCHECK_NE(IconType::kUnknown, icon_type);

  auto it = base::ranges::find(decodes_, decoder,
                               &std::unique_ptr<AppIconDecoder>::get);
  CHECK(it != decodes_.end(), base::NotFatalUntil::M130);
  decodes_.erase(it);

  if (!iv || iv->icon_type != IconType::kUncompressed ||
      iv->uncompressed.isNull()) {
    std::move(callback).Run(std::move(iv));
    return;
  }

  iv->icon_type = icon_type;

  if (!icon_effects) {
    // If the caller requests an uncompressed icon, return the uncompressed
    // result; otherwise, encode the icon to a compressed icon, return the
    // compressed result.
    OnCompleteWithIconValue(size_in_dip, icon_type, std::move(callback),
                            std::move(iv));
    return;
  }

  // Apply the icon effects on the uncompressed data.

  // Per https://www.w3.org/TR/appmanifest/#icon-masks, we apply a white
  // background in case the maskable icon contains transparent pixels in its
  // safe zone, and clear the standard icon effect, apply the mask to the icon
  // without shrinking it.
  if (iv->is_maskable_icon) {
    icon_effects &= ~apps::IconEffects::kCrOsStandardIcon;
    icon_effects |= apps::IconEffects::kCrOsStandardBackground;
    icon_effects |= apps::IconEffects::kCrOsStandardMask;
  }

  if (icon_type == apps::IconType::kUncompressed) {
    // For uncompressed icon, apply the resize and pad effect.
    icon_effects |= apps::IconEffects::kMdIconStyle;

    // For uncompressed icon, clear the standard icon effects, kBackground
    // and kMask.
    icon_effects &= ~apps::IconEffects::kCrOsStandardIcon;
    icon_effects &= ~apps::IconEffects::kCrOsStandardBackground;
    icon_effects &= ~apps::IconEffects::kCrOsStandardMask;
  }

  apps::ApplyIconEffects(
      profile_, id, icon_effects, size_in_dip, std::move(iv),
      base::BindOnce(&AppIconReader::OnCompleteWithIconValue,
                     weak_ptr_factory_.GetWeakPtr(), size_in_dip, icon_type,
                     std::move(callback)));
}

void AppIconReader::OnCompleteWithIconValue(int32_t size_in_dip,
                                            IconType icon_type,
                                            LoadIconCallback callback,
                                            IconValuePtr iv) {
  TRACE_EVENT0("ui", "AppIconReader::OnCompleteWithIconValue");
  iv->uncompressed.MakeThreadSafe();

  if (icon_type != IconType::kCompressed) {
    std::move(callback).Run(std::move(iv));
    return;
  }

  float icon_scale = static_cast<float>(apps_util::ConvertDipToPx(
                         size_in_dip,
                         /*quantize_to_supported_scale_factor=*/true)) /
                     size_in_dip;
  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
      base::BindOnce(&apps::EncodeImageToPngBytes, iv->uncompressed,
                     icon_scale),
      base::BindOnce(&AppIconReader::OnCompleteWithCompressedData,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void AppIconReader::OnCompleteWithCompressedData(
    LoadIconCallback callback,
    std::vector<uint8_t> icon_data) {
  TRACE_EVENT0("ui", "AppIconReader::OnCompleteWithCompressedData");
  auto iv = std::make_unique<IconValue>();
  iv->icon_type = IconType::kCompressed;
  iv->compressed = std::move(icon_data);

  std::move(callback).Run(std::move(iv));
}

}  // namespace apps