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

// Copyright 2023 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_decoder.h"

#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/test/test_future.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/apps/app_service/app_icon/app_icon_factory.h"
#include "chrome/browser/apps/app_service/app_icon/app_icon_test_util.h"
#include "chrome/browser/apps/app_service/app_icon/app_icon_util.h"
#include "chrome/test/base/testing_profile.h"
#include "components/services/app_service/public/cpp/icon_types.h"
#include "content/public/test/browser_task_environment.h"
#include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/resource/resource_scale_factor.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_unittest_util.h"

namespace apps {

namespace {
constexpr char kAppId[] = "foobar";
constexpr int kIconSizeDp = 64;
}  // namespace

class AppIconDecoderTest : public testing::Test {
 public:
  // The type of compressed icon file, which determines the name of the file the
  // icon is stored in.
  enum class StoredIconType {
    kNonMaskable,
    kMaskable,
    kAdaptiveBackground,
    kAdaptiveForeground
  };

  // Stores icons for all scale factors for the given `app_id` and `size_dp` in
  // the App Service icon directory. The icons will be a solid block of the
  // given `color.
  void StoreSolidColorIconsForApp(const std::string& app_id,
                                  int size_dp,
                                  StoredIconType type,
                                  SkColor color) {
    for (const auto scale_factor : ui::GetSupportedResourceScaleFactors()) {
      int icon_size_in_px =
          gfx::ScaleToFlooredSize(gfx::Size(size_dp, size_dp), scale_factor)
              .width();
      SkBitmap bitmap = gfx::test::CreateBitmap(icon_size_in_px, color);

      std::vector<unsigned char> output;
      gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, /*discard_transparency=*/false,
                                        &output);

      base::FilePath path;
      if (type == StoredIconType::kAdaptiveBackground) {
        path = GetBackgroundIconPath(base_path(), app_id, icon_size_in_px);
      } else if (type == StoredIconType::kAdaptiveForeground) {
        path = GetForegroundIconPath(base_path(), app_id, icon_size_in_px);
      } else {
        path = GetIconPath(base_path(), app_id, icon_size_in_px,
                           type == StoredIconType::kMaskable);
      }

      base::ScopedAllowBlockingForTesting scoped_allow_blocking;
      ASSERT_TRUE(base::CreateDirectory(path.DirName()));
      ASSERT_TRUE(base::WriteFile(path, output));
    }
  }

  base::FilePath base_path() { return profile_.GetPath(); }

 private:
  content::BrowserTaskEnvironment task_environment_;
  TestingProfile profile_;
  data_decoder::test::InProcessDataDecoder in_process_data_decoder_;
};

TEST_F(AppIconDecoderTest, DecodeNonMaskable) {
  StoreSolidColorIconsForApp(kAppId, kIconSizeDp, StoredIconType::kNonMaskable,
                             SK_ColorGREEN);

  base::test::TestFuture<AppIconDecoder*, IconValuePtr> icon_future;

  AppIconDecoder decoder(base_path(), kAppId, kIconSizeDp,
                         icon_future.GetCallback());
  decoder.Start();
  ASSERT_TRUE(icon_future.Wait());

  const IconValuePtr& iv = icon_future.Get<1>();
  EXPECT_EQ(iv->icon_type, IconType::kUncompressed);
  EXPECT_FALSE(iv->is_maskable_icon);

  gfx::ImageSkia expected_image =
      CreateSquareIconImageSkia(kIconSizeDp, SK_ColorGREEN);
  VerifyIcon(expected_image, iv->uncompressed);
}

TEST_F(AppIconDecoderTest, DecodeMaskable) {
  StoreSolidColorIconsForApp(kAppId, kIconSizeDp, StoredIconType::kMaskable,
                             SK_ColorGREEN);

  base::test::TestFuture<AppIconDecoder*, IconValuePtr> icon_future;

  AppIconDecoder decoder(base_path(), kAppId, kIconSizeDp,
                         icon_future.GetCallback());
  decoder.Start();
  ASSERT_TRUE(icon_future.Wait());

  const IconValuePtr& iv = icon_future.Get<1>();
  EXPECT_EQ(iv->icon_type, IconType::kUncompressed);
  EXPECT_TRUE(iv->is_maskable_icon);

  gfx::ImageSkia expected_image =
      CreateSquareIconImageSkia(kIconSizeDp, SK_ColorGREEN);
  VerifyIcon(expected_image, iv->uncompressed);
}

TEST_F(AppIconDecoderTest, DecodeAdaptiveIcon) {
  // If the app has both adaptive and non-adaptive icons in storage,
  // non-adaptive icons should be ignored.
  StoreSolidColorIconsForApp(kAppId, kIconSizeDp, StoredIconType::kNonMaskable,
                             SK_ColorRED);
  StoreSolidColorIconsForApp(
      kAppId, kIconSizeDp, StoredIconType::kAdaptiveBackground, SK_ColorGREEN);
  StoreSolidColorIconsForApp(kAppId, kIconSizeDp,
                             StoredIconType::kAdaptiveForeground, SK_ColorBLUE);

  base::test::TestFuture<AppIconDecoder*, IconValuePtr> icon_future;

  AppIconDecoder decoder(base_path(), kAppId, kIconSizeDp,
                         icon_future.GetCallback());
  decoder.Start();
  ASSERT_TRUE(icon_future.Wait());

  const IconValuePtr& iv = icon_future.Get<1>();
  EXPECT_EQ(iv->icon_type, IconType::kUncompressed);
  EXPECT_FALSE(iv->is_maskable_icon);

  gfx::ImageSkia expected_foreground =
      CreateSquareIconImageSkia(kIconSizeDp, SK_ColorBLUE);
  gfx::ImageSkia expected_background =
      CreateSquareIconImageSkia(kIconSizeDp, SK_ColorGREEN);
  gfx::ImageSkia expected_image = apps::CompositeImagesAndApplyMask(
      expected_foreground, expected_background);
  EnsureRepresentationsLoaded(expected_image);

  VerifyIcon(expected_image, iv->uncompressed);
}

TEST_F(AppIconDecoderTest, DecodeFromEmptyStorage) {
  // No icons are placed into storage, which should result in a failed decode.
  base::test::TestFuture<AppIconDecoder*, IconValuePtr> icon_future;

  AppIconDecoder decoder(base_path(), kAppId, kIconSizeDp,
                         icon_future.GetCallback());
  decoder.Start();
  ASSERT_TRUE(icon_future.Wait());

  const IconValuePtr& iv = icon_future.Get<1>();
  ASSERT_EQ(iv->icon_type, IconType::kUnknown);
}

}  // namespace apps