| // 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 "chrome/browser/ash/customization/customization_wallpaper_downloader.h" |
| |
| #include <stddef.h> |
| |
| #include <algorithm> |
| #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/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/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. |
| std::vector<uint8_t> CreateJPEGImage(int width, int height, SkColor color) { |
| SkBitmap bitmap; |
| bitmap.allocN32Pixels(width, height); |
| bitmap.eraseColor(color); |
| return gfx::JPEGCodec::Encode(bitmap, 80 /*quality=*/).value(); |
| } |
| |
| 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() = default; |
| |
| CustomizationWallpaperDownloaderBrowserTest( |
| const CustomizationWallpaperDownloaderBrowserTest&) = delete; |
| CustomizationWallpaperDownloaderBrowserTest& operator=( |
| const CustomizationWallpaperDownloaderBrowserTest&) = delete; |
| |
| ~CustomizationWallpaperDownloaderBrowserTest() override = default; |
| |
| // InProcessBrowserTest overrides: |
| void SetUpOnMainThread() override { |
| InProcessBrowserTest::SetUpOnMainThread(); |
| |
| std::vector<uint8_t> oem_wallpaper = CreateJPEGImage( |
| kWallpaperSize, kWallpaperSize, kCustomizedDefaultWallpaperColor); |
| jpeg_data_.resize(oem_wallpaper.size()); |
| std::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 |