chromium/chrome/browser/ash/customization/customization_wallpaper_downloader_browsertest.cc

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>

#include <vector>

#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/wallpaper/wallpaper_controller.h"
#include "ash/public/cpp/wallpaper/wallpaper_controller_observer.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/ranges/algorithm.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/time/time.h"
#include "chrome/browser/ash/customization/customization_document.h"
#include "chrome/browser/ash/customization/customization_wallpaper_downloader.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/testing_browser_process.h"
#include "content/public/test/browser_test.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "net/test/embedded_test_server/http_response.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"

namespace ash {

namespace {

constexpr char kOEMWallpaperRelativeURL[] = "/image.png";

constexpr char kServicesManifest[] =
    "{"
    "  \"version\": \"1.0\","
    "  \"default_wallpaper\": \"\%s\",\n"
    "  \"default_apps\": [\n"
    "    \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n"
    "    \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"\n"
    "  ],\n"
    "  \"localized_content\": {\n"
    "    \"en-US\": {\n"
    "      \"default_apps_folder_name\": \"EN-US OEM Name\"\n"
    "    },\n"
    "    \"en\": {\n"
    "      \"default_apps_folder_name\": \"EN OEM Name\"\n"
    "    },\n"
    "    \"default\": {\n"
    "      \"default_apps_folder_name\": \"Default OEM Name\"\n"
    "    }\n"
    "  }\n"
    "}";

// Expected minimal wallpaper download retry interval in milliseconds.
constexpr int kDownloadRetryIntervalMS = 100;

// Dimension used for width and height of default wallpaper images. A small
// value is used to minimize the amount of time spent compressing and writing
// images.
constexpr int kWallpaperSize = 2;

constexpr SkColor kCustomizedDefaultWallpaperColor = SK_ColorDKGRAY;

std::string ManifestForURL(const std::string& url) {
  return base::StringPrintf(kServicesManifest, url.c_str());
}

// Returns true if the color at the center of |image| is close to
// |expected_color|. (The center is used so small wallpaper images can be
// used.)
bool ImageIsNearColor(gfx::ImageSkia image, SkColor expected_color) {
  if (image.size().IsEmpty()) {
    LOG(ERROR) << "Image is empty";
    return false;
  }

  const SkBitmap* bitmap = image.bitmap();
  if (!bitmap) {
    LOG(ERROR) << "Unable to get bitmap from image";
    return false;
  }

  gfx::Point center = gfx::Rect(image.size()).CenterPoint();
  SkColor image_color = bitmap->getColor(center.x(), center.y());

  const int kDiff = 3;
  if (std::abs(static_cast<int>(SkColorGetA(image_color)) -
               static_cast<int>(SkColorGetA(expected_color))) > kDiff ||
      std::abs(static_cast<int>(SkColorGetR(image_color)) -
               static_cast<int>(SkColorGetR(expected_color))) > kDiff ||
      std::abs(static_cast<int>(SkColorGetG(image_color)) -
               static_cast<int>(SkColorGetG(expected_color))) > kDiff ||
      std::abs(static_cast<int>(SkColorGetB(image_color)) -
               static_cast<int>(SkColorGetB(expected_color))) > kDiff) {
    LOG(ERROR) << "Expected color near 0x" << std::hex << expected_color
               << " but got 0x" << image_color;
    return false;
  }

  return true;
}

// Creates compressed JPEG image of solid color. Result bytes are written to
// |output|. Returns true on success.
bool CreateJPEGImage(int width,
                     int height,
                     SkColor color,
                     std::vector<unsigned char>* output) {
  SkBitmap bitmap;
  bitmap.allocN32Pixels(width, height);
  bitmap.eraseColor(color);
  if (!gfx::JPEGCodec::Encode(bitmap, 80 /*quality=*/, output)) {
    LOG(ERROR) << "Unable to encode " << width << "x" << height << " bitmap";
    return false;
  }
  return true;
}

class TestWallpaperObserver : public WallpaperControllerObserver {
 public:
  TestWallpaperObserver() {
    ash::WallpaperController::Get()->AddObserver(this);
  }

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

  ~TestWallpaperObserver() override {
    ash::WallpaperController::Get()->RemoveObserver(this);
  }

  // WallpaperControllerObserver:
  void OnWallpaperChanged() override {
    finished_ = true;
    if (run_loop_) {
      run_loop_->Quit();
    }
  }

  // Wait until the wallpaper update is completed.
  void WaitForWallpaperChanged() {
    while (!finished_) {
      run_loop_ = std::make_unique<base::RunLoop>();
      run_loop_->Run();
    }
  }

  void Reset() { finished_ = false; }

 private:
  bool finished_ = false;
  std::unique_ptr<base::RunLoop> run_loop_;
};

}  // namespace

class CustomizationWallpaperDownloaderBrowserTest
    : public InProcessBrowserTest {
 public:
  CustomizationWallpaperDownloaderBrowserTest() {}

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

  ~CustomizationWallpaperDownloaderBrowserTest() override {}

  // InProcessBrowserTest overrides:
  void SetUpOnMainThread() override {
    InProcessBrowserTest::SetUpOnMainThread();

    std::vector<unsigned char> oem_wallpaper;
    ASSERT_TRUE(CreateJPEGImage(kWallpaperSize, kWallpaperSize,
                                kCustomizedDefaultWallpaperColor,
                                &oem_wallpaper));
    jpeg_data_.resize(oem_wallpaper.size());
    base::ranges::copy(oem_wallpaper, jpeg_data_.begin());

    // Set up the test server.
    embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
        &CustomizationWallpaperDownloaderBrowserTest::HandleRequest,
        base::Unretained(this)));
    ASSERT_TRUE(embedded_test_server()->Start());
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(switches::kLoginManager);
    command_line->AppendSwitchASCII(switches::kLoginProfile, "user");
  }

  void SetRequiredRetries(size_t retries) { required_retries_ = retries; }

  size_t num_attempts() const { return attempts_.size(); }

 private:
  std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
      const net::test_server::HttpRequest& request) {
    ServicesCustomizationDocument* customization =
        ServicesCustomizationDocument::GetInstance();
    customization->wallpaper_downloader_for_testing()
        ->set_retry_delay_for_testing(
            base::Milliseconds(kDownloadRetryIntervalMS));

    attempts_.push_back(base::TimeTicks::Now());
    if (attempts_.size() > 1) {
      const int retry = num_attempts() - 1;
      const base::TimeDelta current_delay =
          customization->wallpaper_downloader_for_testing()
              ->retry_current_delay_for_testing();
      const double base_interval =
          base::Milliseconds(kDownloadRetryIntervalMS).InSecondsF();
      EXPECT_GE(current_delay, base::Seconds(base_interval * retry * retry))
          << "Retry too fast. Actual interval " << current_delay.InSecondsF()
          << " seconds, but expected at least " << base_interval
          << " * (retry=" << retry
          << " * retry)= " << base_interval * retry * retry << " seconds.";
    }
    if (attempts_.size() > required_retries_) {
      std::unique_ptr<net::test_server::BasicHttpResponse> response =
          std::make_unique<net::test_server::BasicHttpResponse>();
      response->set_content_type("image/jpeg");
      response->set_code(net::HTTP_OK);
      response->set_content(jpeg_data_);
      return std::move(response);
    }
    return nullptr;
  }

  // Sample Wallpaper content.
  std::string jpeg_data_;

  // Number of loads performed.
  std::vector<base::TimeTicks> attempts_;

  // Number of retries required.
  size_t required_retries_ = 0;
};

IN_PROC_BROWSER_TEST_F(CustomizationWallpaperDownloaderBrowserTest,
                       OEMWallpaperIsPresent) {
  TestWallpaperObserver observer;
  // Show a built-in default wallpaper first.
  ash::WallpaperController::Get()->ShowSigninWallpaper();
  observer.WaitForWallpaperChanged();
  observer.Reset();

  // Set the number of required retries.
  SetRequiredRetries(0);

  // Start fetching the customized default wallpaper.
  GURL url = embedded_test_server()->GetURL(kOEMWallpaperRelativeURL);
  ServicesCustomizationDocument* customization =
      ServicesCustomizationDocument::GetInstance();
  EXPECT_TRUE(
      customization->LoadManifestFromString(ManifestForURL(url.spec())));
  observer.WaitForWallpaperChanged();
  observer.Reset();

  // Verify the customized default wallpaper has replaced the built-in default
  // wallpaper.
  gfx::ImageSkia image = ash::WallpaperController::Get()->GetWallpaperImage();
  EXPECT_TRUE(ImageIsNearColor(image, kCustomizedDefaultWallpaperColor));
  EXPECT_EQ(1U, num_attempts());
}

IN_PROC_BROWSER_TEST_F(CustomizationWallpaperDownloaderBrowserTest,
                       OEMWallpaperRetryFetch) {
  TestWallpaperObserver observer;
  // Show a built-in default wallpaper.
  ash::WallpaperController::Get()->ShowSigninWallpaper();
  observer.WaitForWallpaperChanged();
  observer.Reset();

  // Set the number of required retries.
  SetRequiredRetries(1);

  // Start fetching the customized default wallpaper.
  GURL url = embedded_test_server()->GetURL(kOEMWallpaperRelativeURL);
  ServicesCustomizationDocument* customization =
      ServicesCustomizationDocument::GetInstance();
  EXPECT_TRUE(
      customization->LoadManifestFromString(ManifestForURL(url.spec())));
  observer.WaitForWallpaperChanged();
  observer.Reset();

  // Verify the customized default wallpaper has replaced the built-in default
  // wallpaper.
  gfx::ImageSkia image = ash::WallpaperController::Get()->GetWallpaperImage();
  EXPECT_TRUE(ImageIsNearColor(image, kCustomizedDefaultWallpaperColor));
  EXPECT_EQ(2U, num_attempts());
}

}  // namespace ash