chromium/chrome/browser/apps/app_service/promise_apps/promise_app_icon_cache_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/promise_apps/promise_app_icon_cache.h"

#include "base/test/test_future.h"
#include "chrome/browser/apps/app_service/app_icon/app_icon_loader.h"
#include "chrome/browser/apps/app_service/app_icon/dip_px_util.h"
#include "chrome/browser/apps/app_service/promise_apps/promise_app.h"
#include "chrome/grit/app_icon_resources.h"
#include "components/services/app_service/public/cpp/icon_effects.h"
#include "components/services/app_service/public/cpp/package_id.h"
#include "content/public/test/browser_task_environment.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/image/image_skia_rep_default.h"
#include "ui/gfx/image/image_unittest_util.h"
#include "ui/gfx/skia_util.h"

namespace apps {

const PackageId kTestPackageId(PackageType::kArc, "test.package.name");

SkColor kRed = SkColorSetRGB(255, 0, 0);
SkColor kGreen = SkColorSetRGB(0, 255, 0);
SkColor kBlue = SkColorSetRGB(0, 0, 255);

class PromiseAppIconCacheTest : public testing::Test {
 public:
  void SetUp() override { cache_ = std::make_unique<PromiseAppIconCache>(); }

  PromiseAppIconCache* icon_cache() { return cache_.get(); }

  PromiseAppIconPtr CreatePromiseAppIcon(int width, SkColor color = kRed) {
    PromiseAppIconPtr icon = std::make_unique<PromiseAppIcon>();
    icon->icon = gfx::test::CreateBitmap(width, color);
    icon->width_in_pixels = width;
    return icon;
  }

  const SkBitmap GetPlaceholderIconBitmap(int size_in_dip,
                                          IconEffects icon_effects) {
    base::test::TestFuture<std::unique_ptr<apps::IconValue>> callback;
    apps::LoadIconFromResource(
        /*profile=*/nullptr, std::nullopt, IconType::kStandard, size_in_dip,
        IDR_APP_ICON_PLACEHOLDER_CUBE,
        /*is_placeholder_icon=*/true, icon_effects, callback.GetCallback());
    apps::IconValue* placeholder_iv = callback.Get().get();
    return *placeholder_iv->uncompressed.bitmap();
  }

  SkBitmap ApplyEffectsToBitmap(SkBitmap bitmap, IconEffects effects) {
    auto iv = std::make_unique<apps::IconValue>();
    iv->uncompressed = gfx::ImageSkia::CreateFromBitmap(bitmap, 1.0f);
    iv->icon_type = apps::IconType::kUncompressed;

    base::test::TestFuture<IconValuePtr> image_with_effects;
    ApplyIconEffects(/*profile=*/nullptr, /*app_id=*/std::nullopt, effects,
                     bitmap.width(), std::move(iv),
                     image_with_effects.GetCallback());

    return *image_with_effects.Get()->uncompressed.bitmap();
  }

 private:
  content::BrowserTaskEnvironment task_environment_;
  std::unique_ptr<PromiseAppIconCache> cache_;
};

TEST_F(PromiseAppIconCacheTest, SaveIcon) {
  PromiseAppIconPtr icon = CreatePromiseAppIcon(/*width=*/50, kRed);
  EXPECT_FALSE(icon_cache()->DoesPackageIdHaveIcons(kTestPackageId));

  icon_cache()->SaveIcon(kTestPackageId, std::move(icon));
  EXPECT_TRUE(icon_cache()->DoesPackageIdHaveIcons(kTestPackageId));
  std::vector<PromiseAppIcon*> icons_saved =
      icon_cache()->GetIconsForTesting(kTestPackageId);
  EXPECT_EQ(icons_saved.size(), 1u);
  EXPECT_EQ(icons_saved[0]->width_in_pixels, 50);
  EXPECT_TRUE(gfx::BitmapsAreEqual(icons_saved[0]->icon,
                                   gfx::test::CreateBitmap(/*size=*/50, kRed)));
}

TEST_F(PromiseAppIconCacheTest, SaveMultipleIcons) {
  PromiseAppIconPtr icon_small = CreatePromiseAppIcon(/*width=*/512, kRed);
  PromiseAppIconPtr icon_large = CreatePromiseAppIcon(/*width=*/1024, kGreen);
  PromiseAppIconPtr icon_smallest = CreatePromiseAppIcon(/*width=*/128, kBlue);

  EXPECT_FALSE(icon_cache()->DoesPackageIdHaveIcons(kTestPackageId));

  icon_cache()->SaveIcon(kTestPackageId, std::move(icon_small));
  EXPECT_EQ(icon_cache()->GetIconsForTesting(kTestPackageId).size(), 1u);

  icon_cache()->SaveIcon(kTestPackageId, std::move(icon_large));
  EXPECT_EQ(icon_cache()->GetIconsForTesting(kTestPackageId).size(), 2u);

  icon_cache()->SaveIcon(kTestPackageId, std::move(icon_smallest));

  // We should have 3 icons for the same package ID in ascending order.
  std::vector<PromiseAppIcon*> icons_saved =
      icon_cache()->GetIconsForTesting(kTestPackageId);
  EXPECT_EQ(icons_saved.size(), 3u);
  EXPECT_EQ(icons_saved[0]->width_in_pixels, 128);
  EXPECT_TRUE(gfx::BitmapsAreEqual(
      icons_saved[0]->icon, gfx::test::CreateBitmap(/*size=*/128, kBlue)));

  EXPECT_EQ(icons_saved[1]->width_in_pixels, 512);
  EXPECT_TRUE(gfx::BitmapsAreEqual(
      icons_saved[1]->icon, gfx::test::CreateBitmap(/*size=*/512, kRed)));

  EXPECT_EQ(icons_saved[2]->width_in_pixels, 1024);
  EXPECT_TRUE(gfx::BitmapsAreEqual(
      icons_saved[2]->icon, gfx::test::CreateBitmap(/*size=*/1024, kGreen)));
}

TEST_F(PromiseAppIconCacheTest, GetIconWithEffects) {
  PromiseAppIconPtr icon = CreatePromiseAppIcon(512, kRed);
  icon_cache()->SaveIcon(kTestPackageId, std::move(icon));

  // Confirm that we have an icon in the icon cache.
  std::vector<PromiseAppIcon*> icons_saved =
      icon_cache()->GetIconsForTesting(kTestPackageId);
  EXPECT_EQ(icons_saved.size(), 1u);

  // Attempt to load icon for test package.
  base::test::TestFuture<std::unique_ptr<apps::IconValue>> test_callback;
  icon_cache()->GetIconAndApplyEffects(kTestPackageId, 128,
                                       IconEffects::kCrOsStandardMask,
                                       test_callback.GetCallback());

  // Confirm the details of the icon in the callback.
  IconValue* result_icon = test_callback.Get().get();
  EXPECT_FALSE(result_icon->uncompressed.isNull());
  EXPECT_EQ(result_icon->icon_type, IconType::kStandard);
  EXPECT_FALSE(result_icon->is_placeholder_icon);
  EXPECT_TRUE(result_icon->is_maskable_icon);
  EXPECT_EQ(result_icon->uncompressed.bitmap()->width(), 128);

  // Confirm that the icon has the correct effects applied to it.
  SkBitmap expected_bitmap = ApplyEffectsToBitmap(
      gfx::test::CreateBitmap(/*size=*/128, kRed), kCrOsStandardMask);
  EXPECT_TRUE(gfx::BitmapsAreEqual(*result_icon->uncompressed.bitmap(),
                                   expected_bitmap));
}

TEST_F(PromiseAppIconCacheTest, GetPlaceholderIconWhenNoIconsAvailable) {
  // Confirm that we have no icons in the icon cache.
  std::vector<PromiseAppIcon*> icons_saved =
      icon_cache()->GetIconsForTesting(kTestPackageId);
  EXPECT_EQ(icons_saved.size(), 0u);

  // Load icon for test package.
  base::test::TestFuture<std::unique_ptr<apps::IconValue>> test_callback;
  icon_cache()->GetIconAndApplyEffects(kTestPackageId, 96,
                                       IconEffects::kCrOsStandardMask,
                                       test_callback.GetCallback());

  // Confirm the details of the icon in the callback.
  IconValue* result_icon = test_callback.Get().get();
  EXPECT_FALSE(result_icon->uncompressed.isNull());
  EXPECT_EQ(result_icon->icon_type, IconType::kStandard);
  EXPECT_TRUE(result_icon->is_placeholder_icon);
  EXPECT_EQ(result_icon->uncompressed.bitmap()->width(), 96);

  // Confirm that the icon is the correct one.
  EXPECT_TRUE(gfx::BitmapsAreEqual(
      *result_icon->uncompressed.bitmap(),
      GetPlaceholderIconBitmap(/*size_in_dip=*/96,
                               IconEffects::kCrOsStandardMask)));
}

TEST_F(PromiseAppIconCacheTest, GetLargestIconIfAllIconsTooSmall) {
  PromiseAppIconPtr icon_small = CreatePromiseAppIcon(/*width=*/10, kRed);
  PromiseAppIconPtr icon_small_2 = CreatePromiseAppIcon(/*width=*/30, kGreen);
  PromiseAppIconPtr icon_small_3 = CreatePromiseAppIcon(/*width=*/50, kBlue);
  icon_cache()->SaveIcon(kTestPackageId, std::move(icon_small));
  icon_cache()->SaveIcon(kTestPackageId, std::move(icon_small_2));
  icon_cache()->SaveIcon(kTestPackageId, std::move(icon_small_3));

  std::vector<PromiseAppIcon*> icons_saved =
      icon_cache()->GetIconsForTesting(kTestPackageId);
  EXPECT_EQ(icons_saved.size(), 3u);

  // All icons returned should be the largest icon resized for our requested
  // scales.
  base::test::TestFuture<std::unique_ptr<apps::IconValue>> test_callback;
  icon_cache()->GetIconAndApplyEffects(kTestPackageId, 128, IconEffects::kNone,
                                       test_callback.GetCallback());
  IconValue* icon_value = test_callback.Get().get();
  gfx::ImageSkia icon = icon_value->uncompressed;
  EXPECT_FALSE(icon.isNull());

  gfx::ImageSkiaRep image_rep = icon.GetRepresentation(1.0f);
  EXPECT_EQ(image_rep.pixel_width(), 128);
  EXPECT_TRUE(gfx::BitmapsAreEqual(
      image_rep.GetBitmap(), gfx::test::CreateBitmap(/*size=*/128, kBlue)));

  image_rep = icon.GetRepresentation(2.0f);
  EXPECT_EQ(image_rep.pixel_width(), 256);
  EXPECT_TRUE(gfx::BitmapsAreEqual(
      image_rep.GetBitmap(), gfx::test::CreateBitmap(/*size=*/256, kBlue)));
}

TEST_F(PromiseAppIconCacheTest, GetCorrectIconRepresentationsForScaleFactors) {
  PromiseAppIconPtr icon_small = CreatePromiseAppIcon(/*width=*/128, kRed);
  icon_cache()->SaveIcon(kTestPackageId, std::move(icon_small));
  PromiseAppIconPtr icon_large = CreatePromiseAppIcon(/*width=*/512, kGreen);
  icon_cache()->SaveIcon(kTestPackageId, std::move(icon_large));

  EXPECT_EQ(icon_cache()->GetIconsForTesting(kTestPackageId).size(), 2u);

  base::test::TestFuture<std::unique_ptr<apps::IconValue>> test_callback;
  icon_cache()->GetIconAndApplyEffects(kTestPackageId, 128, IconEffects::kNone,
                                       test_callback.GetCallback());
  IconValue* icon_value = test_callback.Get().get();
  gfx::ImageSkia icon = icon_value->uncompressed;
  EXPECT_FALSE(icon.isNull());

  gfx::ImageSkiaRep image_rep_default = icon.GetRepresentation(1.0f);
  EXPECT_FALSE(image_rep_default.is_null());
  EXPECT_EQ(image_rep_default.pixel_width(), 128);
  EXPECT_TRUE(
      gfx::BitmapsAreEqual(image_rep_default.GetBitmap(),
                           gfx::test::CreateBitmap(/*size=*/128, kRed)));

  // Verify that the large icon gets resized to become smaller for the 2.0 scale
  // factor (instead of the small icon being resized up).
  gfx::ImageSkiaRep image_rep_larger = icon.GetRepresentation(2.0f);
  EXPECT_FALSE(image_rep_larger.is_null());
  EXPECT_EQ(image_rep_larger.pixel_width(), 256);
  EXPECT_TRUE(
      gfx::BitmapsAreEqual(image_rep_larger.GetBitmap(),
                           gfx::test::CreateBitmap(/*size=*/256, kGreen)));
}

TEST_F(PromiseAppIconCacheTest, RemoveIconsForPackageId) {
  PromiseAppIconPtr icon_small = CreatePromiseAppIcon(/*width=*/100);
  PromiseAppIconPtr icon_med = CreatePromiseAppIcon(/*width=*/200);
  PromiseAppIconPtr icon_large = CreatePromiseAppIcon(/*width=*/300);

  icon_cache()->SaveIcon(kTestPackageId, std::move(icon_small));
  icon_cache()->SaveIcon(kTestPackageId, std::move(icon_med));
  icon_cache()->SaveIcon(kTestPackageId, std::move(icon_large));

  // Confirm we have 3 icons.
  EXPECT_EQ(icon_cache()->GetIconsForTesting(kTestPackageId).size(), 3u);

  // Remove all icons for package ID.
  icon_cache()->RemoveIconsForPackageId(kTestPackageId);

  // Confirm we have no icons.
  EXPECT_EQ(icon_cache()->GetIconsForTesting(kTestPackageId).size(), 0u);
}

}  // namespace apps