chromium/chrome/browser/apps/app_service/app_icon/arc_apps_icon_unittest.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 "ash/components/arc/mojom/app.mojom.h"
#include "ash/components/arc/test/fake_app_instance.h"
#include "base/containers/flat_map.h"
#include "base/scoped_observation.h"
#include "base/test/test_future.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_test_util.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.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_test.h"
#include "chrome/browser/extensions/chrome_app_icon.h"
#include "chrome/test/base/testing_profile.h"
#include "components/services/app_service/public/cpp/features.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/abseil-cpp/absl/utility/utility.h"
#include "ui/base/resource/resource_scale_factor.h"
#include "ui/gfx/codec/png_codec.h"

namespace apps {

class ArcAppsIconFactoryTest : public testing::Test {
 public:
  void SetUp() override {
    testing::Test::SetUp();
    arc_test_.SetUp(profile());
    task_environment_.RunUntilIdle();
  }

  void TearDown() override {
    arc_test_.StopArcInstance();
    arc_test_.TearDown();
  }

  arc::mojom::RawIconPngDataPtr GenerateRawArcAppIcon(
      const std::string app_id,
      ui::ResourceScaleFactor scale_factor) {
    ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile());
    base::test::TestFuture<arc::mojom::RawIconPngDataPtr> result;
    prefs->RequestRawIconData(app_id,
                              ArcAppIconDescriptor(kSizeInDip, scale_factor),
                              result.GetCallback());
    return result.Take();
  }

  IconValuePtr GetArcAppCompressedIconData(
      const std::string& app_id,
      ui::ResourceScaleFactor scale_factor) {
    base::test::TestFuture<IconValuePtr> result;
    apps::GetArcAppCompressedIconData(profile(), app_id, kSizeInDip,
                                      scale_factor, result.GetCallback());
    return result.Take();
  }

  TestingProfile* profile() { return &profile_; }

  ArcAppTest* arc_test() { return &arc_test_; }

 private:
  content::BrowserTaskEnvironment task_environment_;
  ArcAppTest arc_test_;
  TestingProfile profile_;
};

TEST_F(ArcAppsIconFactoryTest, GetArcAppCompressedIconData) {
  const auto& fake_apps = arc_test()->fake_apps();
  std::string package_name = fake_apps[0]->package_name;
  std::string app_id = ArcAppListPrefs::GetAppId(fake_apps[0]->package_name,
                                                 fake_apps[0]->activity);
  arc_test()->app_instance()->SendRefreshAppList(fake_apps);

  // Generate the source raw icon data for comparing.
  auto raw_icon_data =
      GenerateRawArcAppIcon(app_id, ui::ResourceScaleFactor::k100Percent);
  ASSERT_TRUE(raw_icon_data);
  ASSERT_TRUE(raw_icon_data->foreground_icon_png_data.has_value());
  ASSERT_TRUE(raw_icon_data->background_icon_png_data.has_value());

  // Verify the raw icon data.
  auto iv =
      GetArcAppCompressedIconData(app_id, ui::ResourceScaleFactor::k100Percent);
  ASSERT_TRUE(iv);
  ASSERT_EQ(iv->icon_type, IconType::kCompressed);
  ASSERT_EQ(raw_icon_data->foreground_icon_png_data.value(),
            iv->foreground_icon_png_data);
  ASSERT_EQ(raw_icon_data->background_icon_png_data.value(),
            iv->background_icon_png_data);
}

class AppServiceArcAppIconTest : public ArcAppsIconFactoryTest,
                                 public ArcAppListPrefs::Observer {
 public:
  void SetUp() override {
    ArcAppsIconFactoryTest::SetUp();

    proxy_ = AppServiceProxyFactory::GetForProfile(profile());
  }

  void GenerateArcAppUncompressedIcon(const std::string app_id,
                                      gfx::ImageSkia& image_skia) {
    gfx::ImageSkia foreground_image_skia;
    gfx::ImageSkia background_image_skia;
    for (const auto scale_factor : ui::GetSupportedResourceScaleFactors()) {
      auto raw_icon_data = GenerateRawArcAppIcon(app_id, scale_factor);
      ASSERT_TRUE(raw_icon_data);
      ASSERT_TRUE(raw_icon_data->foreground_icon_png_data.has_value());
      ASSERT_TRUE(raw_icon_data->background_icon_png_data.has_value());

      auto foreground_icon_data =
          raw_icon_data->foreground_icon_png_data.value();
      auto background_icon_data =
          raw_icon_data->background_icon_png_data.value();
      SkBitmap foreground_bitmap;
      SkBitmap background_bitmap;
      gfx::PNGCodec::Decode(
          reinterpret_cast<const unsigned char*>(&foreground_icon_data.front()),
          foreground_icon_data.size(), &foreground_bitmap);
      gfx::PNGCodec::Decode(
          reinterpret_cast<const unsigned char*>(&background_icon_data.front()),
          background_icon_data.size(), &background_bitmap);

      foreground_image_skia.AddRepresentation(gfx::ImageSkiaRep(
          foreground_bitmap, ui::GetScaleForResourceScaleFactor(scale_factor)));
      background_image_skia.AddRepresentation(gfx::ImageSkiaRep(
          background_bitmap, ui::GetScaleForResourceScaleFactor(scale_factor)));
    }

    image_skia = apps::CompositeImagesAndApplyMask(foreground_image_skia,
                                                   background_image_skia);
    image_skia.MakeThreadSafe();
  }

  void GenerateArcAppCompressedIcon(const std::string app_id,
                                    float scale,
                                    std::vector<uint8_t>& result) {
    gfx::ImageSkia image_skia;
    GenerateArcAppUncompressedIcon(app_id, image_skia);

    const gfx::ImageSkiaRep& image_skia_rep =
        image_skia.GetRepresentation(scale);
    ASSERT_EQ(image_skia_rep.scale(), scale);

    const SkBitmap& bitmap = image_skia_rep.GetBitmap();
    const bool discard_transparency = false;
    ASSERT_TRUE(gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, discard_transparency,
                                                  &result));
  }

  apps::IconValuePtr LoadIcon(const std::string& app_id, IconType icon_type) {
    base::test::TestFuture<apps::IconValuePtr> result;
    app_service_proxy().LoadIcon(app_id, icon_type, kSizeInDip,
                                 /*allow_placeholder_icon=*/false,
                                 result.GetCallback());
    return result.Take();
  }

  apps::IconValuePtr LoadIconWithIconEffects(const std::string& app_id,
                                             uint32_t icon_effects,
                                             IconType icon_type) {
    base::test::TestFuture<apps::IconValuePtr> result;
    app_service_proxy().LoadIconWithIconEffects(
        app_id, icon_effects, icon_type, kSizeInDip,
        /*allow_placeholder_icon=*/false, result.GetCallback());
    return result.Take();
  }

  AppServiceProxy& app_service_proxy() { return *proxy_; }

  // Calls `closure` once any update is made to `app_id`'s icons in
  // ArcAppListPrefs.
  void AwaitIconUpdate(const std::string& app_id, base::OnceClosure closure) {
    waiting_icon_updates_[app_id] = std::move(closure);
  }

  // ArcAppListPrefs::Observer:
  void OnAppIconUpdated(const std::string& app_id,
                        const ArcAppIconDescriptor& descriptor) override {
    if (base::Contains(waiting_icon_updates_, app_id)) {
      std::move(waiting_icon_updates_[app_id]).Run();
      waiting_icon_updates_.erase(app_id);
    }
  }

 private:
  raw_ptr<AppServiceProxy> proxy_;
  data_decoder::test::InProcessDataDecoder in_process_data_decoder_;

  base::flat_map<std::string, base::OnceClosure> waiting_icon_updates_;
};

TEST_F(AppServiceArcAppIconTest, GetCompressedIconDataForUncompressedIcon) {
  const auto& fake_apps = arc_test()->fake_apps();
  std::string package_name = fake_apps[0]->package_name;
  std::string app_id = ArcAppListPrefs::GetAppId(fake_apps[0]->package_name,
                                                 fake_apps[0]->activity);
  arc_test()->app_instance()->SendRefreshAppList(fake_apps);

  // Generate the source uncompressed icon for comparing.
  gfx::ImageSkia src_image_skia;
  GenerateArcAppUncompressedIcon(app_id, src_image_skia);

  // Verify the icon reading and writing function in AppService for the
  // uncompressed icon.
  IconValuePtr iv = LoadIcon(app_id, IconType::kUncompressed);
  ASSERT_TRUE(iv);
  ASSERT_EQ(iv->icon_type, IconType::kUncompressed);
  VerifyIcon(src_image_skia, iv->uncompressed);
}

TEST_F(AppServiceArcAppIconTest, GetCompressedIconDataForCompressedIcon) {
  const auto& fake_apps = arc_test()->fake_apps();
  std::string package_name = fake_apps[0]->package_name;
  std::string app_id = ArcAppListPrefs::GetAppId(fake_apps[0]->package_name,
                                                 fake_apps[0]->activity);
  arc_test()->app_instance()->SendRefreshAppList(fake_apps);

  // Generate the source compressed icon for comparing.
  std::vector<uint8_t> src_data;
  GenerateArcAppCompressedIcon(app_id, /*scale=*/1.0, src_data);

  // Verify the icon reading and writing function in AppService for the
  // compressed icon.
  VerifyCompressedIcon(src_data, *LoadIcon(app_id, IconType::kCompressed));
}

TEST_F(AppServiceArcAppIconTest, GetCompressedIconDataForStandardIcon) {
  const auto& fake_apps = arc_test()->fake_apps();
  std::string package_name = fake_apps[0]->package_name;
  std::string app_id = ArcAppListPrefs::GetAppId(fake_apps[0]->package_name,
                                                 fake_apps[0]->activity);
  arc_test()->app_instance()->SendRefreshAppList(fake_apps);

  // Generate the source uncompressed icon for comparing.
  gfx::ImageSkia src_image_skia;
  GenerateArcAppUncompressedIcon(app_id, src_image_skia);

  // Apply the paused app badge.
  extensions::ChromeAppIcon::ApplyEffects(
      kSizeInDip, extensions::ChromeAppIcon::ResizeFunction(),
      /*app_launchable=*/false, /*rounded_corners=*/false,
      extensions::ChromeAppIcon::Badge::kPaused, &src_image_skia);
  src_image_skia.MakeThreadSafe();

  // Verify the icon reading and writing function in AppService for the
  // kStandard icon.
  auto ret = LoadIconWithIconEffects(
      app_id, IconEffects::kCrOsStandardIcon | IconEffects::kPaused,
      IconType::kStandard);

  ASSERT_EQ(apps::IconType::kStandard, ret->icon_type);
  VerifyIcon(src_image_skia, ret->uncompressed);
}

TEST_F(AppServiceArcAppIconTest, GetCompressedIconDataFromArcDiskCache) {
  const auto& fake_apps = arc_test()->fake_apps();
  std::string package_name = fake_apps[0]->package_name;
  std::string app_id = ArcAppListPrefs::GetAppId(fake_apps[0]->package_name,
                                                 fake_apps[0]->activity);
  arc_test()->app_instance()->SendRefreshAppList(fake_apps);

  // Ensure that app icons are in the ARC disk cache.
  base::ScopedObservation<ArcAppListPrefs, ArcAppListPrefs::Observer>
      observation(this);
  observation.Observe(arc_test()->arc_app_list_prefs());
  for (const auto scale_factor : ui::GetSupportedResourceScaleFactors()) {
    base::RunLoop run_loop;
    AwaitIconUpdate(app_id, run_loop.QuitClosure());
    arc_test()->arc_app_list_prefs()->MaybeRequestIcon(
        app_id, ArcAppIconDescriptor(kSizeInDip, scale_factor));
    run_loop.Run();
  }

  // Generate the source compressed icon for comparing.
  std::vector<uint8_t> src_data;
  GenerateArcAppCompressedIcon(app_id, /*scale=*/1.0, src_data);

  // Stop ARC from running so that LoadIcon requests must load from the disk
  // cache rather than ARC itself.
  arc_test()->StopArcInstance();

  // Verify the icon reading and writing function in AppService for the
  // compressed icon.
  VerifyCompressedIcon(src_data, *LoadIcon(app_id, IconType::kCompressed));
}

}  // namespace apps