chromium/chrome/browser/ash/app_list/arc/arc_app_icon.cc

// Copyright 2015 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/ash/app_list/arc/arc_app_icon.h"

#include <map>
#include <memory>
#include <utility>
#include <vector>

#include "base/containers/contains.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/lazy_instance.h"
#include "base/memory/raw_ref.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/ranges/algorithm.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/ash/app_list/arc/arc_app_icon_descriptor.h"
#include "chrome/browser/ash/app_list/arc/arc_app_list_prefs.h"
#include "chrome/browser/ash/app_list/arc/arc_app_utils.h"
#include "chrome/browser/image_decoder/image_decoder.h"
#include "chrome/browser/ui/ash/shelf/arc_app_shelf_id.h"
#include "chrome/grit/component_extension_resources.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/grit/extensions_browser_resources.h"
#include "services/data_decoder/public/cpp/data_decoder.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/resource/resource_scale_factor.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/image/image_skia_rep.h"
#include "ui/gfx/image/image_skia_source.h"

namespace {

bool disable_safe_decoding_for_testing = false;

data_decoder::DataDecoder& GetDataDecoder() {
  static base::NoDestructor<data_decoder::DataDecoder> data_decoder;
  return *data_decoder;
}

}  // namespace

////////////////////////////////////////////////////////////////////////////////
// ArcAppIcon::ReadResult

ArcAppIcon::ReadResult::ReadResult(bool error,
                                   bool request_to_install,
                                   ui::ResourceScaleFactor scale_factor,
                                   bool resize_allowed,
                                   std::vector<std::string> unsafe_icon_data)
    : error(error),
      request_to_install(request_to_install),
      scale_factor(scale_factor),
      resize_allowed(resize_allowed),
      unsafe_icon_data(std::move(unsafe_icon_data)) {}
ArcAppIcon::ReadResult::~ReadResult() = default;

////////////////////////////////////////////////////////////////////////////////
// ArcAppIcon::Source

// Initializes the ImageSkia with placeholder bitmaps, decoded from
// compiled-into-the-binary resources such as IDR_APP_DEFAULT_ICON, and
// schedules the asynchronous loading of the app's actual bitmaps.
class ArcAppIcon::Source : public gfx::ImageSkiaSource {
 public:
  Source(const base::WeakPtr<ArcAppIcon>& host, int resource_size_in_dip);

  Source(const Source&) = delete;
  Source& operator=(const Source&) = delete;

  ~Source() override;

 private:
  // gfx::ImageSkiaSource overrides:
  gfx::ImageSkiaRep GetImageForScale(float scale) override;

  // Used to load images asynchronously. NULLed out when the ArcAppIcon is
  // destroyed.
  base::WeakPtr<ArcAppIcon> host_;

  const int resource_size_in_dip_;

  // A map from a pair of a resource ID and size in DIP to an image. This
  // is a cache to avoid resizing IDR icons in GetImageForScale every time.
  static base::LazyInstance<std::map<std::pair<int, int>, gfx::ImageSkia>>::
      DestructorAtExit default_icons_cache_;
};

base::LazyInstance<std::map<std::pair<int, int>, gfx::ImageSkia>>::
    DestructorAtExit ArcAppIcon::Source::default_icons_cache_ =
        LAZY_INSTANCE_INITIALIZER;

ArcAppIcon::Source::Source(const base::WeakPtr<ArcAppIcon>& host,
                           int resource_size_in_dip)
    : host_(host),
      resource_size_in_dip_(resource_size_in_dip) {
}

ArcAppIcon::Source::~Source() {
}

gfx::ImageSkiaRep ArcAppIcon::Source::GetImageForScale(float scale) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  // Host loads icon asynchronously, so use default icon so far.
  int resource_id;
  if (host_ && host_->app_id() == arc::kPlayStoreAppId) {
    // Don't request icon from Android side. Use overloaded Chrome icon for Play
    // Store that is adapted according Chrome style.
    const int resource_size_in_px =
        static_cast<int>(resource_size_in_dip_ * scale + 0.5);
    resource_id = resource_size_in_px <= 32 ? IDR_ARC_SUPPORT_ICON_32_PNG
                                            : IDR_ARC_SUPPORT_ICON_192_PNG;
  } else {
    if (host_)
      host_->LoadForScaleFactor(ui::GetSupportedResourceScaleFactor(scale));
    resource_id = IDR_APP_DEFAULT_ICON;
  }

  // Check |default_icons_cache_| and returns the existing one if possible.
  const auto key = std::make_pair(resource_id, resource_size_in_dip_);
  const auto it = default_icons_cache_.Get().find(key);
  if (it != default_icons_cache_.Get().end())
    return it->second.GetRepresentation(scale);

  const gfx::ImageSkia* default_image =
      ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(resource_id);
  CHECK(default_image);
  gfx::ImageSkia resized_image = gfx::ImageSkiaOperations::CreateResizedImage(
      *default_image, skia::ImageOperations::RESIZE_BEST,
      gfx::Size(resource_size_in_dip_, resource_size_in_dip_));

  // Add the resized image to the cache to avoid executing the expensive resize
  // operation many times. Caching the result is safe because unlike ARC icons
  // that can be updated dynamically, IDR icons are static.
  default_icons_cache_.Get().insert(std::make_pair(key, resized_image));
  return resized_image.GetRepresentation(scale);
}

class ArcAppIcon::DecodeRequest : public ImageDecoder::ImageRequest {
 public:
  DecodeRequest(
      ArcAppIcon& host,
      const ArcAppIconDescriptor& descriptor,
      bool resize_allowed,
      bool retain_padding,
      gfx::ImageSkia& image_skia,
      std::map<ui::ResourceScaleFactor, base::Time>& incomplete_scale_factors);

  DecodeRequest(const DecodeRequest&) = delete;
  DecodeRequest& operator=(const DecodeRequest&) = delete;

  ~DecodeRequest() override;

  // ImageDecoder::ImageRequest
  void OnImageDecoded(const SkBitmap& bitmap) override;
  void OnDecodeImageFailed() override;

 private:
  const raw_ref<ArcAppIcon> host_;
  const ArcAppIconDescriptor descriptor_;
  const bool resize_allowed_;
  const bool retain_padding_;
  const raw_ref<gfx::ImageSkia> image_skia_;
  const raw_ref<std::map<ui::ResourceScaleFactor, base::Time>>
      incomplete_scale_factors_;
};

////////////////////////////////////////////////////////////////////////////////
// ArcAppIcon::DecodeRequest

ArcAppIcon::DecodeRequest::DecodeRequest(
    ArcAppIcon& host,
    const ArcAppIconDescriptor& descriptor,
    bool resize_allowed,
    bool retain_padding,
    gfx::ImageSkia& image_skia,
    std::map<ui::ResourceScaleFactor, base::Time>& incomplete_scale_factors)
    : ImageRequest(&GetDataDecoder()),
      host_(host),
      descriptor_(descriptor),
      resize_allowed_(resize_allowed),
      retain_padding_(retain_padding),
      image_skia_(image_skia),
      incomplete_scale_factors_(incomplete_scale_factors) {}

ArcAppIcon::DecodeRequest::~DecodeRequest() {
  ImageDecoder::Cancel(this);
}

void ArcAppIcon::DecodeRequest::OnImageDecoded(const SkBitmap& bitmap) {
  DCHECK(!bitmap.isNull() && !bitmap.empty());

  const int expected_dim = descriptor_.GetSizeInPixels();

  // If retain_padding is true, that means the foreground or the
  // background image has paddings which should be chopped by AppService, so the
  // raw image is forwarded without resize.
  if (!retain_padding_ &&
      (bitmap.width() != expected_dim || bitmap.height() != expected_dim)) {
    if (!resize_allowed_) {
      VLOG(2) << "Decoded ARC icon has unexpected dimension " << bitmap.width()
              << "x" << bitmap.height() << ". Expected " << expected_dim << ".";
      host_->MaybeRequestIcon(descriptor_.scale_factor);
    } else {
      host_->UpdateImageSkia(descriptor_.scale_factor,
                             skia::ImageOperations::Resize(
                                 bitmap, skia::ImageOperations::RESIZE_BEST,
                                 expected_dim, expected_dim),
                             *image_skia_, *incomplete_scale_factors_);
    }
  } else {
    host_->UpdateImageSkia(descriptor_.scale_factor, bitmap, *image_skia_,
                           *incomplete_scale_factors_);
  }

  host_->DiscardDecodeRequest(this, true /* bool is_decode_success */);
}

void ArcAppIcon::DecodeRequest::OnDecodeImageFailed() {
  VLOG(2) << "Failed to decode ARC icon.";
  host_->MaybeRequestIcon(descriptor_.scale_factor);
  host_->DiscardDecodeRequest(this, false /* bool is_decode_success*/);
}

////////////////////////////////////////////////////////////////////////////////
// ArcAppIcon

// static
void ArcAppIcon::DisableSafeDecodingForTesting() {
  disable_safe_decoding_for_testing = true;
}

// static
bool ArcAppIcon::IsSafeDecodingDisabledForTesting() {
  return disable_safe_decoding_for_testing;
}

ArcAppIcon::ArcAppIcon(content::BrowserContext* context,
                       const std::string& app_id,
                       int resource_size_in_dip,
                       Observer* observer,
                       IconType icon_type)
    : context_(context),
      app_id_(app_id),
      mapped_app_id_(arc::GetAppFromAppOrGroupId(context, app_id)),
      resource_size_in_dip_(resource_size_in_dip),
      observer_(observer),
      icon_type_(icon_type) {
  CHECK(observer_);

  gfx::Size resource_size(resource_size_in_dip, resource_size_in_dip);
  const std::vector<ui::ResourceScaleFactor>& scale_factors =
      ui::GetSupportedResourceScaleFactors();
  switch (icon_type) {
    case IconType::kAdaptive:
      foreground_image_skia_ = gfx::ImageSkia(
          std::make_unique<Source>(weak_ptr_factory_.GetWeakPtr(),
                                   resource_size_in_dip),
          resource_size);

      // ArcAppIcon::Source::GetImageForScale calls host_->LoadForScaleFactor to
      // read both the foreground and background files, so the
      // |background_image_skia_| doesn't need to set the host to call
      // LoadForScaleFactor again. Otherwise, it might duplicate the opened
      // files number, and cause the system crash,
      background_image_skia_ = gfx::ImageSkia(
          std::make_unique<Source>(nullptr, resource_size_in_dip),
          resource_size);
      for (const auto scale_factor : scale_factors) {
        foreground_incomplete_scale_factors_.insert(
            {scale_factor, base::Time::Now()});
        background_incomplete_scale_factors_.insert(
            {scale_factor, base::Time::Now()});
        is_adaptive_icons_.insert({scale_factor, true});
      }
      // Deliberately fall through to IconType::kUncompressed to update
      // |image_skia_| and |incomplete_scale_factors_|.
      [[fallthrough]];
    case IconType::kUncompressed:
      image_skia_ = gfx::ImageSkia(
          std::make_unique<Source>(weak_ptr_factory_.GetWeakPtr(),
                                   resource_size_in_dip),
          resource_size);
      // Deliberately fall through to IconType::kCompressed to update
      // |incomplete_scale_factors_|.
      [[fallthrough]];
    case IconType::kCompressed:
      for (const auto scale_factor : scale_factors) {
        incomplete_scale_factors_.insert({scale_factor, base::Time::Now()});
        if (icon_type != IconType::kAdaptive)
          is_adaptive_icons_.insert({scale_factor, false});
      }
      break;
  }
}

ArcAppIcon::~ArcAppIcon() = default;

void ArcAppIcon::LoadSupportedScaleFactors() {
  switch (icon_type_) {
    case IconType::kCompressed:
      for (auto scale_factor : incomplete_scale_factors_)
        LoadForScaleFactor(scale_factor.first);
      break;
    case IconType::kAdaptive:
      for (auto scale_factor : foreground_incomplete_scale_factors_) {
        foreground_image_skia_.GetRepresentation(
            ui::GetScaleForResourceScaleFactor(scale_factor.first));
      }
      for (auto scale_factor : background_incomplete_scale_factors_) {
        background_image_skia_.GetRepresentation(
            ui::GetScaleForResourceScaleFactor(scale_factor.first));
      }
      // Deliberately fall through to IconType::kCompressed to update
      // |image_skia_|.
      [[fallthrough]];
    case IconType::kUncompressed:
      // Calling GetRepresentation indirectly calls LoadForScaleFactor but also
      // first initializes image_skia_ with the placeholder icons (e.g.
      // IDR_APP_DEFAULT_ICON), via ArcAppIcon::Source::GetImageForScale.
      for (auto scale_factor : incomplete_scale_factors_) {
        image_skia_.GetRepresentation(
            ui::GetScaleForResourceScaleFactor(scale_factor.first));
      }
      break;
  }
}

bool ArcAppIcon::EverySupportedScaleFactorIsLoaded() {
  switch (icon_type_) {
    case IconType::kUncompressed:
      // Deliberately fall through to IconType::kCompressed to check
      // |incomplete_scale_factors_|.
      [[fallthrough]];
    case IconType::kCompressed:
      return incomplete_scale_factors_.empty();
    case IconType::kAdaptive: {
      // Check whether there is non-adaptive icon for any scale.
      bool is_adaptive_icon = true;
      for (const auto& it : is_adaptive_icons_) {
        if (!it.second) {
          is_adaptive_icon = false;
          break;
        }
      }

      // All scales have adaptive icon.
      if (is_adaptive_icon) {
        return foreground_incomplete_scale_factors_.empty() &&
               background_incomplete_scale_factors_.empty();
      }

      // Some scales have non-adaptive icons. Copy the decode image
      // representation from |foreground_image_skia_| to |image_skia_|.
      for (auto it = is_adaptive_icons_.begin(); it != is_adaptive_icons_.end();
           it++) {
        if (it->second &&
            !base::Contains(foreground_incomplete_scale_factors_, it->first)) {
          it->second = false;
          float scale = ui::GetScaleForResourceScaleFactor(it->first);
          image_skia_.RemoveRepresentation(scale);
          image_skia_.AddRepresentation(
              foreground_image_skia_.GetRepresentation(scale));
          image_skia_.RemoveUnsupportedRepresentationsForScale(scale);
          incomplete_scale_factors_.erase(it->first);
        }
      }

      return incomplete_scale_factors_.empty();
    }
  }
}

void ArcAppIcon::LoadForScaleFactor(ui::ResourceScaleFactor scale_factor) {
  // We provide Play Store icon from Chrome resources and it is not expected
  // that we have external load request.
  DCHECK_NE(app_id(), arc::kPlayStoreAppId);

  ArcAppListPrefs* const prefs = ArcAppListPrefs::Get(context_);
  DCHECK(prefs);

  const ArcAppIconDescriptor descriptor(resource_size_in_dip_, scale_factor);
  std::vector<base::FilePath> paths;
  std::vector<base::FilePath> default_app_paths;
  switch (icon_type_) {
    case IconType::kAdaptive: {
      base::FilePath foreground_path =
          prefs->GetForegroundIconPath(mapped_app_id_, descriptor);
      base::FilePath background_path =
          prefs->GetBackgroundIconPath(mapped_app_id_, descriptor);
      if (foreground_path.empty() || background_path.empty())
        return;
      paths.emplace_back(foreground_path);
      paths.emplace_back(background_path);

      default_app_paths.emplace_back(
          prefs->MaybeGetForegroundIconPathForDefaultApp(mapped_app_id_,
                                                         descriptor));
      default_app_paths.emplace_back(
          prefs->MaybeGetBackgroundIconPathForDefaultApp(mapped_app_id_,
                                                         descriptor));
      // Deliberately fall through to IconType::kCompressed to add |path| to
      // |paths|. For the migration scenario, when the foreground icon file
      // doesn't exist, load the original icon file to resolve the icon lag
      // issue.
      [[fallthrough]];
    }
    case IconType::kUncompressed: {
      // Deliberately fall through to IconType::kCompressed to add |path| to
      // |paths|.
      [[fallthrough]];
    }
    case IconType::kCompressed: {
      base::FilePath path = prefs->GetIconPath(mapped_app_id_, descriptor);
      if (path.empty())
        return;
      paths.emplace_back(path);
      default_app_paths.emplace_back(
          prefs->MaybeGetIconPathForDefaultApp(mapped_app_id_, descriptor));
      break;
    }
  }

  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
      base::BindOnce(&ArcAppIcon::ReadOnBackgroundThread, icon_type_,
                     scale_factor, paths, default_app_paths),
      base::BindOnce(&ArcAppIcon::OnIconRead, weak_ptr_factory_.GetWeakPtr()));
}

void ArcAppIcon::MaybeRequestIcon(ui::ResourceScaleFactor scale_factor) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  ArcAppListPrefs* prefs = ArcAppListPrefs::Get(context_);
  DCHECK(prefs);

  // ArcAppListPrefs notifies the ArcAppIconLoader (which is an
  // ArcAppListPrefs::Observer) when the icon is updated, and
  // ArcAppIconLoader::OnAppIconUpdated calls ArcAppIcon::LoadForScaleFactor on
  // the corresponding ArcAppIcon.
  prefs->MaybeRequestIcon(
      mapped_app_id_,
      ArcAppIconDescriptor(resource_size_in_dip_, scale_factor));
}

// static
std::unique_ptr<ArcAppIcon::ReadResult> ArcAppIcon::ReadOnBackgroundThread(
    ArcAppIcon::IconType icon_type,
    ui::ResourceScaleFactor scale_factor,
    const std::vector<base::FilePath>& paths,
    const std::vector<base::FilePath>& default_app_paths) {
  DCHECK(!paths.empty());

  switch (icon_type) {
    case IconType::kUncompressed:
      // Deliberately fall through to IconType::kCompressed.
      [[fallthrough]];
    case IconType::kCompressed:
      DCHECK_EQ(1u, paths.size());
      return ArcAppIcon::ReadSingleIconFile(scale_factor, paths[0],
                                            default_app_paths[0]);
    case IconType::kAdaptive:
      return ArcAppIcon::ReadAdaptiveIconFiles(scale_factor, paths,
                                               default_app_paths);
  }
}

// static
std::unique_ptr<ArcAppIcon::ReadResult> ArcAppIcon::ReadSingleIconFile(
    ui::ResourceScaleFactor scale_factor,
    const base::FilePath& path,
    const base::FilePath& default_app_path) {
  DCHECK(!path.empty());

  base::FilePath path_to_read;
  // Allow resizing only for default app icons.
  bool resize_allowed;
  if (base::PathExists(path)) {
    path_to_read = path;
    resize_allowed = false;
  } else {
    if (default_app_path.empty() || !base::PathExists(default_app_path)) {
      return std::make_unique<ArcAppIcon::ReadResult>(
          false /* error */, true /* request_to_install */, scale_factor,
          false /* resize_allowed */,
          std::vector<std::string>() /* unsafe_icon_data */);
    }
    path_to_read = default_app_path;
    resize_allowed = true;
  }

  bool request_to_install = path_to_read != path;
  return ArcAppIcon::ReadFile(request_to_install, scale_factor, resize_allowed,
                              path_to_read);
}

// static
std::unique_ptr<ArcAppIcon::ReadResult> ArcAppIcon::ReadAdaptiveIconFiles(
    ui::ResourceScaleFactor scale_factor,
    const std::vector<base::FilePath>& paths,
    const std::vector<base::FilePath>& default_app_paths) {
  DCHECK_EQ(3u, paths.size());

  const base::FilePath& foreground_path = paths[0];
  const base::FilePath& background_path = paths[1];
  if (!base::PathExists(foreground_path)) {
    // For the migration scenario, when the foreground icon file doesn't
    // exist, load the original icon file to resolve the icon lag issue.
    if (!paths[2].empty() && base::PathExists(paths[2])) {
      return ArcAppIcon::ReadFile(true /* request_to_install */, scale_factor,
                                  false /* resize_allowed */, paths[2]);
    }

    // Check and read the default app icon path for the default app if the files
    // exist.
    return ReadDefaultAppAdaptiveIconFiles(scale_factor, default_app_paths);
  }

  if (!base::PathExists(background_path)) {
    // For non-adaptive icon, there could be a |foreground_icon_path| file
    // only without a |background_icon_path| file.
    return ArcAppIcon::ReadFile(false /* request_to_install */, scale_factor,
                                false /* resize_allowed */, foreground_path);
  }

  return ArcAppIcon::ReadFiles(false /* request_to_install */, scale_factor,
                               false /* resize_allowed */, foreground_path,
                               background_path);
}

// static
std::unique_ptr<ArcAppIcon::ReadResult>
ArcAppIcon::ReadDefaultAppAdaptiveIconFiles(
    ui::ResourceScaleFactor scale_factor,
    const std::vector<base::FilePath>& default_app_paths) {
  // Check the default app icon path, and read the icon files for the default
  // app if the files exist.
  DCHECK_EQ(3u, default_app_paths.size());
  const base::FilePath& default_app_foreground_path = default_app_paths[0];
  const base::FilePath& default_app_background_path = default_app_paths[1];
  if (default_app_foreground_path.empty() ||
      !base::PathExists(default_app_foreground_path)) {
    // For the migration scenario, when the foreground icon file doesn't
    // exist, load the original icon file to resolve the icon lag issue for
    // the default app.
    if (default_app_paths.size() == 3u && !default_app_paths[2].empty() &&
        base::PathExists(default_app_paths[2])) {
      return ArcAppIcon::ReadFile(true /* request_to_install */, scale_factor,
                                  true /* resize_allowed */,
                                  default_app_paths[2]);
    }

    return std::make_unique<ArcAppIcon::ReadResult>(
        false /* error */, true /* request_to_install */, scale_factor,
        false /* resize_allowed */,
        std::vector<std::string>() /* unsafe_icon_data */);
  }

  if (default_app_background_path.empty() ||
      !base::PathExists(default_app_background_path)) {
    // For non-adaptive icon, there could be a |default_app_foreground_path|
    // file only without a |default_app_background_path| file.
    return ArcAppIcon::ReadFile(true /* request_to_install */, scale_factor,
                                true /* resize_allowed */,
                                default_app_foreground_path);
  }

  return ArcAppIcon::ReadFiles(
      true /* request_to_install */, scale_factor, true /* resize_allowed */,
      default_app_foreground_path, default_app_background_path);
}

// static
std::unique_ptr<ArcAppIcon::ReadResult> ArcAppIcon::ReadFile(
    bool request_to_install,
    ui::ResourceScaleFactor scale_factor,
    bool resize_allowed,
    const base::FilePath& path) {
  // Read the file from disk. Sometimes, the file could be deleted, e.g. when
  // running unit tests, so check whether the file exists again.
  std::string unsafe_icon_data;
  if (path.empty() || !base::PathExists(path) ||
      !base::ReadFileToString(path, &unsafe_icon_data) ||
      unsafe_icon_data.empty()) {
    VLOG(2) << "Failed to read an ARC icon from file " << path.MaybeAsASCII();

    // If |unsafe_icon_data| is empty typically means we have a file corruption
    // on cached icon file. Send request to re install the icon.
    request_to_install |= unsafe_icon_data.empty();
    return std::make_unique<ArcAppIcon::ReadResult>(
        true /* error */, request_to_install, scale_factor,
        false /* resize_allowed */,
        std::vector<std::string>() /* unsafe_icon_data */);
  }

  return std::make_unique<ArcAppIcon::ReadResult>(
      false /* error */, request_to_install, scale_factor, resize_allowed,
      std::vector<std::string>{std::move(unsafe_icon_data)});
}

// static
std::unique_ptr<ArcAppIcon::ReadResult> ArcAppIcon::ReadFiles(
    bool request_to_install,
    ui::ResourceScaleFactor scale_factor,
    bool resize_allowed,
    const base::FilePath& foreground_path,
    const base::FilePath& background_path) {
  // Read the file from disk. Sometimes, the file could be deleted, e.g. when
  // running unit tests, so check whether the file exists again.
  std::string unsafe_foreground_icon_data;
  std::string unsafe_background_icon_data;
  if (foreground_path.empty() || !base::PathExists(foreground_path) ||
      !base::ReadFileToString(foreground_path, &unsafe_foreground_icon_data) ||
      unsafe_foreground_icon_data.empty()) {
    VLOG(2) << "Failed to read an ARC icon from file "
            << foreground_path.MaybeAsASCII();

    // If |unsafe_icon_data| is empty typically means we have a file corruption
    // on cached icon file. Send request to re install the icon.
    request_to_install |= unsafe_foreground_icon_data.empty();
    return std::make_unique<ArcAppIcon::ReadResult>(
        true /* error */, true /* request_to_install */, scale_factor,
        false /* resize_allowed */,
        std::vector<std::string>() /* unsafe_icon_data */);
  }

  if (background_path.empty() || !base::PathExists(background_path) ||
      !base::ReadFileToString(background_path, &unsafe_background_icon_data) ||
      unsafe_background_icon_data.empty()) {
    VLOG(2) << "Failed to read an ARC icon from file "
            << background_path.MaybeAsASCII();

    // If |unsafe_icon_data| is empty typically means we have a file corruption
    // on cached icon file. Send request to re install the icon.
    request_to_install |= unsafe_background_icon_data.empty();
    return std::make_unique<ArcAppIcon::ReadResult>(
        true /* error */, true /* request_to_install */, scale_factor,
        false /* resize_allowed */,
        std::vector<std::string>() /* unsafe_icon_data */);
  }

  return std::make_unique<ArcAppIcon::ReadResult>(
      false /* error */, request_to_install, scale_factor, resize_allowed,
      std::vector<std::string>{std::move(unsafe_foreground_icon_data),
                               std::move(unsafe_background_icon_data)});
}

void ArcAppIcon::OnIconRead(
    std::unique_ptr<ArcAppIcon::ReadResult> read_result) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  if (read_result->request_to_install)
    MaybeRequestIcon(read_result->scale_factor);

  if (read_result->unsafe_icon_data.empty()) {
    observer_->OnIconFailed(this);
    return;
  }

  switch (icon_type_) {
    case IconType::kUncompressed: {
      DCHECK_EQ(1u, read_result->unsafe_icon_data.size());
      DecodeImage(std::move(read_result->unsafe_icon_data[0]),
                  ArcAppIconDescriptor(resource_size_in_dip_,
                                       read_result->scale_factor),
                  read_result->resize_allowed, false /* retain_padding */,
                  image_skia_, incomplete_scale_factors_);
      return;
    }
    case IconType::kCompressed: {
      DCHECK_EQ(1u, read_result->unsafe_icon_data.size());
      UpdateCompressed(read_result->scale_factor,
                       std::move(read_result->unsafe_icon_data[0]));
      return;
    }
    case IconType::kAdaptive: {
      // For non-adaptive icons, |foreground_icon_path| is used to save the icon
      // without |background_icon_path|, so |unsafe_icon_data| could have one
      // element for |foreground_icon_path| only.
      if (read_result->unsafe_icon_data.size() == 1) {
        is_adaptive_icons_[read_result->scale_factor] = false;
        DecodeImage(std::move(read_result->unsafe_icon_data[0]),
                    ArcAppIconDescriptor(resource_size_in_dip_,
                                         read_result->scale_factor),
                    read_result->resize_allowed, false /* retain_padding */,
                    image_skia_, incomplete_scale_factors_);
        return;
      }

      DCHECK_EQ(2u, read_result->unsafe_icon_data.size());
      DecodeImage(std::move(read_result->unsafe_icon_data[0]),
                  ArcAppIconDescriptor(resource_size_in_dip_,
                                       read_result->scale_factor),
                  read_result->resize_allowed, true /* retain_padding */,
                  foreground_image_skia_, foreground_incomplete_scale_factors_);
      DecodeImage(std::move(read_result->unsafe_icon_data[1]),
                  ArcAppIconDescriptor(resource_size_in_dip_,
                                       read_result->scale_factor),
                  read_result->resize_allowed, true /* retain_padding */,
                  background_image_skia_, background_incomplete_scale_factors_);
      return;
    }
  }
}

void ArcAppIcon::DecodeImage(
    std::string unsafe_icon_data,
    const ArcAppIconDescriptor& descriptor,
    bool resize_allowed,
    bool retain_padding,
    gfx::ImageSkia& image_skia,
    std::map<ui::ResourceScaleFactor, base::Time>& incomplete_scale_factors) {
  decode_requests_.emplace_back(std::make_unique<DecodeRequest>(
      *this, descriptor, resize_allowed, retain_padding, image_skia,
      incomplete_scale_factors));
  if (disable_safe_decoding_for_testing) {
    SkBitmap bitmap;
    if (!unsafe_icon_data.empty() &&
        gfx::PNGCodec::Decode(
            reinterpret_cast<const unsigned char*>(&unsafe_icon_data.front()),
            unsafe_icon_data.length(), &bitmap)) {
      decode_requests_.back()->OnImageDecoded(bitmap);
    } else {
      decode_requests_.back()->OnDecodeImageFailed();
    }
  } else {
    ImageDecoder::Start(decode_requests_.back().get(),
                        std::move(unsafe_icon_data));
  }
}

void ArcAppIcon::UpdateImageSkia(
    ui::ResourceScaleFactor scale_factor,
    const SkBitmap& bitmap,
    gfx::ImageSkia& image_skia,
    std::map<ui::ResourceScaleFactor, base::Time>& incomplete_scale_factors) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  CHECK(ui::IsScaleFactorSupported(scale_factor));

  gfx::ImageSkiaRep image_rep(bitmap,
                              ui::GetScaleForResourceScaleFactor(scale_factor));
  image_skia.RemoveRepresentation(image_rep.scale());
  image_skia.AddRepresentation(image_rep);
  image_skia.RemoveUnsupportedRepresentationsForScale(image_rep.scale());

  // TODO(crbug.com/40131344): Track the adaptive icon load time in a separate
  // UMA.
  if (icon_loaded_count_++ < 5) {
    base::UmaHistogramTimes(
        "Arc.IconLoadFromFileTime.uncompressedFirst5",
        base::Time::Now() - incomplete_scale_factors[scale_factor]);
  } else {
    base::UmaHistogramTimes(
        "Arc.IconLoadFromFileTime.uncompressedOthers",
        base::Time::Now() - incomplete_scale_factors[scale_factor]);
  }
  incomplete_scale_factors.erase(scale_factor);
  observer_->OnIconUpdated(this);
}

void ArcAppIcon::UpdateCompressed(ui::ResourceScaleFactor scale_factor,
                                  std::string data) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  compressed_images_[scale_factor] = std::move(data);

  if (icon_loaded_count_++ < 5) {
    base::UmaHistogramTimes(
        "Arc.IconLoadFromFileTime.compressedFirst5",
        base::Time::Now() - incomplete_scale_factors_[scale_factor]);
  } else {
    base::UmaHistogramTimes(
        "Arc.IconLoadFromFileTime.compressedOthers",
        base::Time::Now() - incomplete_scale_factors_[scale_factor]);
  }
  incomplete_scale_factors_.erase(scale_factor);
  observer_->OnIconUpdated(this);
}

void ArcAppIcon::DiscardDecodeRequest(DecodeRequest* request,
                                      bool is_decode_success) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  auto it = base::ranges::find(decode_requests_, request,
                               &std::unique_ptr<DecodeRequest>::get);
  DCHECK(it != decode_requests_.end());
  decode_requests_.erase(it);

  if (!is_decode_success)
    observer_->OnIconFailed(this);
}