[M87 merge] Reland "ambient: start when two images loaded"
This is a reland of 581c91ec3cd79a1b88d161276aadd144771d012e
Original change's description:
> ambient: start when two images loaded
>
> Construct ambient widget when two images are in memory to prevent burn
> in.
>
> BUG=b:167332126
>
> Cq-Include-Trybots: luci.chrome.try:linux-chromeos-chrome
> Change-Id: I520ada00429125d35bda9ef66089ca9a9c8d4440
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2440306
> Commit-Queue: Jeffrey Young <[email protected]>
> Reviewed-by: Tao Wu <[email protected]>
> Reviewed-by: Xiaohui Chen <[email protected]>
> Cr-Commit-Position: refs/heads/master@{#814711}
(cherry picked from commit 01cf328d5f1824d7e11d3f4c407c3b41084e9dac)
Bug: b:167332126
Change-Id: I7fe0cca3ecb15262666eee1067f57bc21b150468
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2459399
Reviewed-by: Xiaohui Chen <[email protected]>
Commit-Queue: Jeffrey Young <[email protected]>
Cr-Original-Commit-Position: refs/heads/master@{#815206}
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2465064
Reviewed-by: Tao Wu <[email protected]>
Cr-Commit-Position: refs/branch-heads/4280@{#282}
Cr-Branched-From: ea420fb963f9658c9969b6513c56b8f47efa1a2a-refs/heads/master@{#812852}
diff --git a/ash/ambient/ambient_controller.cc b/ash/ambient/ambient_controller.cc
index 35d6b1b..7e95dfa 100644
--- a/ash/ambient/ambient_controller.cc
+++ b/ash/ambient/ambient_controller.cc
@@ -532,9 +532,8 @@
return ambient_photo_controller_.ambient_backend_model();
}
-void AmbientController::OnImagesChanged() {
- if (!container_view_)
- CreateAndShowWidget();
+void AmbientController::OnImagesReady() {
+ CreateAndShowWidget();
}
std::unique_ptr<AmbientContainerView> AmbientController::CreateContainerView() {
diff --git a/ash/ambient/ambient_controller.h b/ash/ambient/ambient_controller.h
index 7926945..0b38734 100644
--- a/ash/ambient/ambient_controller.h
+++ b/ash/ambient/ambient_controller.h
@@ -126,7 +126,7 @@
void DismissUI();
// AmbientBackendModelObserver overrides:
- void OnImagesChanged() override;
+ void OnImagesReady() override;
// Initializes the |container_view_|. Called in |CreateWidget()| to create the
// contents view.
diff --git a/ash/ambient/ambient_photo_controller.cc b/ash/ambient/ambient_photo_controller.cc
index bc5d11e..797ba5a 100644
--- a/ash/ambient/ambient_photo_controller.cc
+++ b/ash/ambient/ambient_photo_controller.cc
@@ -397,14 +397,8 @@
}
void AmbientPhotoController::ScheduleRefreshImage() {
- base::TimeDelta refresh_interval;
- if (!ambient_backend_model_.ShouldFetchImmediately())
- refresh_interval = kPhotoRefreshInterval;
-
- // |photo_refresh_timer_| will start immediately if ShouldFetchImmediately()
- // is true.
photo_refresh_timer_.Start(
- FROM_HERE, refresh_interval,
+ FROM_HERE, ambient_backend_model_.GetPhotoRefreshInterval(),
base::BindOnce(&AmbientPhotoController::FetchPhotoRawData,
weak_factory_.GetWeakPtr()));
}
@@ -464,7 +458,6 @@
}
return;
}
-
fetch_topic_retry_backoff_.InformOfRequest(/*succeeded=*/true);
ambient_backend_model_.AppendTopics(screen_update.next_topics);
StartDownloadingWeatherConditionIcon(screen_update.weather_info);
@@ -516,11 +509,7 @@
}
void AmbientPhotoController::TryReadPhotoRawData() {
- auto on_done =
- base::BindRepeating(&AmbientPhotoController::OnAllPhotoRawDataAvailable,
- weak_factory_.GetWeakPtr(),
- /*from_downloading=*/false);
-
+ ResetImageData();
// Stop reading from cache after the max number of retries.
if (retries_to_read_from_cache_ == 0) {
if (backup_retries_to_read_from_cache_ == 0) {
@@ -545,6 +534,10 @@
// Try to read a backup image.
auto photo_data = std::make_unique<std::string>();
auto* photo_data_ptr = photo_data.get();
+ auto on_done = base::BindRepeating(
+ &AmbientPhotoController::OnAllPhotoRawDataAvailable,
+ weak_factory_.GetWeakPtr(), /*from_downloading=*/false);
+
task_runner_->PostTaskAndReply(
FROM_HERE,
base::BindOnce(
@@ -569,12 +562,19 @@
--retries_to_read_from_cache_;
std::string file_name = base::NumberToString(cache_index_for_display_);
+
++cache_index_for_display_;
if (cache_index_for_display_ == kMaxNumberOfCachedImages)
cache_index_for_display_ = 0;
auto photo_data = std::make_unique<std::string>();
auto photo_details = std::make_unique<std::string>();
+
+ auto on_done =
+ base::BindRepeating(&AmbientPhotoController::OnAllPhotoRawDataAvailable,
+ weak_factory_.GetWeakPtr(),
+ /*from_downloading=*/false);
+
task_runner_->PostTaskAndReply(
FROM_HERE,
base::BindOnce(
@@ -639,7 +639,8 @@
auto on_done = base::BarrierClosure(
num_callbacks,
base::BindOnce(&AmbientPhotoController::OnAllPhotoDecoded,
- weak_factory_.GetWeakPtr(), from_downloading));
+ weak_factory_.GetWeakPtr(), from_downloading,
+ /*hash=*/base::SHA1HashString(*image_data_)));
task_runner_->PostTaskAndReply(
FROM_HERE,
@@ -673,7 +674,7 @@
image_decoder_->Decode(
image_bytes, base::BindOnce(&AmbientPhotoController::OnPhotoDecoded,
weak_factory_.GetWeakPtr(), from_downloading,
- is_related_image, on_done));
+ is_related_image, std::move(on_done)));
}
void AmbientPhotoController::OnPhotoDecoded(bool from_downloading,
@@ -688,7 +689,8 @@
std::move(on_done).Run();
}
-void AmbientPhotoController::OnAllPhotoDecoded(bool from_downloading) {
+void AmbientPhotoController::OnAllPhotoDecoded(bool from_downloading,
+ const std::string& hash) {
if (image_.isNull()) {
LOG(WARNING) << "Image is null";
if (from_downloading)
@@ -697,6 +699,10 @@
// Try to read from cache when failure happens.
TryReadPhotoRawData();
return;
+ } else if (ambient_backend_model_.HashMatchesNextImage(hash)) {
+ LOG(WARNING) << "Skipping loading duplicate image.";
+ TryReadPhotoRawData();
+ return;
}
retries_to_read_from_cache_ = kMaxNumberOfCachedImages;
@@ -709,6 +715,7 @@
detailed_photo.photo = image_;
detailed_photo.related_photo = related_image_;
detailed_photo.details = *image_details_;
+ detailed_photo.hash = hash;
ResetImageData();
diff --git a/ash/ambient/ambient_photo_controller.h b/ash/ambient/ambient_photo_controller.h
index d8ef32a2..6bce4e88 100644
--- a/ash/ambient/ambient_photo_controller.h
+++ b/ash/ambient/ambient_photo_controller.h
@@ -166,7 +166,7 @@
base::RepeatingClosure on_done,
const gfx::ImageSkia& image);
- void OnAllPhotoDecoded(bool from_downloading);
+ void OnAllPhotoDecoded(bool from_downloading, const std::string& hash);
void StartDownloadingWeatherConditionIcon(
const base::Optional<WeatherInfo>& weather_info);
diff --git a/ash/ambient/ambient_photo_controller_unittest.cc b/ash/ambient/ambient_photo_controller_unittest.cc
index c56d81c..e86d68b 100644
--- a/ash/ambient/ambient_photo_controller_unittest.cc
+++ b/ash/ambient/ambient_photo_controller_unittest.cc
@@ -10,6 +10,7 @@
#include "ash/ambient/ambient_constants.h"
#include "ash/ambient/ambient_controller.h"
#include "ash/ambient/model/ambient_backend_model.h"
+#include "ash/ambient/model/ambient_backend_model_observer.h"
#include "ash/ambient/test/ambient_ash_test_base.h"
#include "ash/public/cpp/ambient/ambient_backend_controller.h"
#include "ash/public/cpp/ambient/fake_ambient_backend_controller_impl.h"
@@ -24,13 +25,28 @@
#include "base/hash/sha1.h"
#include "base/path_service.h"
#include "base/run_loop.h"
+#include "base/scoped_observer.h"
#include "base/system/sys_info.h"
#include "base/test/bind_test_util.h"
#include "base/timer/timer.h"
+#include "testing/gmock/include/gmock/gmock.h"
#include "ui/gfx/image/image_skia.h"
namespace ash {
+namespace {
+class MockAmbientBackendModelObserver : public AmbientBackendModelObserver {
+ public:
+ MockAmbientBackendModelObserver() = default;
+ ~MockAmbientBackendModelObserver() override = default;
+
+ // AmbientBackendModelObserver:
+ MOCK_METHOD(void, OnImagesChanged, (), (override));
+ MOCK_METHOD(void, OnImagesReady, (), (override));
+};
+
+} // namespace
+
class AmbientPhotoControllerTest : public AmbientAshTestBase {
public:
// AmbientAshTestBase:
@@ -144,30 +160,32 @@
TEST_F(AmbientPhotoControllerTest, ShouldSaveImagesOnDisk) {
base::FilePath ambient_image_path = GetCacheDir();
- // Start to refresh images. It will download a test image and write it in
- // |ambient_image_path| in a delayed task.
+ // Start to refresh images. It will download two images immediately and write
+ // them in |ambient_image_path|. It will also download one more image after
+ // fast forward. It will also download the related images and not cache them.
photo_controller()->StartScreenUpdate();
FastForwardToNextImage();
- // Count files and directories in ambient_image_path. There should only be
- // four files that were just created to save image files for this ambient mode
+ // Count files and directories in ambient_image_path. There should be six
+ // files that were just created to save image files for this ambient mode
// session.
EXPECT_TRUE(base::PathExists(ambient_image_path));
auto file_paths = GetFilePathsInDir(ambient_image_path);
- // Two image files and two attribution files.
- EXPECT_EQ(file_paths.size(), 4u);
+ // Three image files and three attribution files.
+ EXPECT_EQ(file_paths.size(), 6u);
for (auto& path : file_paths) {
// No sub directories.
EXPECT_FALSE(base::DirectoryExists(path));
}
}
-// Test that image is save and will be deleted when stopping ambient mode.
+// Test that image is save and will not be deleted when stopping ambient mode.
TEST_F(AmbientPhotoControllerTest, ShouldNotDeleteImagesOnDisk) {
base::FilePath ambient_image_path = GetCacheDir();
- // Start to refresh images. It will download a test image and write it in
- // |ambient_image_path| in a delayed task.
+ // Start to refresh images. It will download two images immediately and write
+ // them in |ambient_image_path|. It will also download one more image after
+ // fast forward. It will also download the related images and not cache them.
photo_controller()->StartScreenUpdate();
FastForwardToNextImage();
@@ -186,13 +204,13 @@
image = photo_controller()->ambient_backend_model()->GetNextImage();
EXPECT_TRUE(image.IsNull());
- // Count files and directories in ambient_image_path. There should only be
- // four files that were just created to save image files for the prior ambient
- // mode session.
+ // Count files and directories in ambient_image_path. There should be six
+ // files that were just created to save image files for the prior ambient mode
+ // session.
EXPECT_TRUE(base::PathExists(ambient_image_path));
auto file_paths = GetFilePathsInDir(ambient_image_path);
- // Two image files and two attribution files.
- EXPECT_EQ(file_paths.size(), 4u);
+ // Three image files and three attribution files.
+ EXPECT_EQ(file_paths.size(), 6u);
for (auto& path : file_paths) {
// No sub directories.
EXPECT_FALSE(base::DirectoryExists(path));
@@ -397,6 +415,37 @@
}
}
+TEST_F(AmbientPhotoControllerTest, ShouldNotLoadDuplicateImages) {
+ testing::NiceMock<MockAmbientBackendModelObserver> mock_backend_observer;
+ ScopedObserver<AmbientBackendModel, AmbientBackendModelObserver>
+ scoped_observer{&mock_backend_observer};
+
+ scoped_observer.Add(photo_controller()->ambient_backend_model());
+
+ // All images downloaded will be identical.
+ SetUrlLoaderData(std::make_unique<std::string>("image data"));
+
+ photo_controller()->StartScreenUpdate();
+ // Run the clock so the first photo is loaded.
+ FastForwardTiny();
+
+ // Should contain hash of downloaded data.
+ EXPECT_TRUE(photo_controller()->ambient_backend_model()->HashMatchesNextImage(
+ base::SHA1HashString("image data")));
+ // Only one image should have been loaded.
+ EXPECT_FALSE(photo_controller()->ambient_backend_model()->ImagesReady());
+
+ // Now expect a call because second image is loaded.
+ EXPECT_CALL(mock_backend_observer, OnImagesChanged).Times(1);
+ SetUrlLoaderData(std::make_unique<std::string>("image data 2"));
+ FastForwardToNextImage();
+
+ // Second image should have been loaded.
+ EXPECT_TRUE(photo_controller()->ambient_backend_model()->HashMatchesNextImage(
+ base::SHA1HashString("image data 2")));
+ EXPECT_TRUE(photo_controller()->ambient_backend_model()->ImagesReady());
+}
+
TEST_F(AmbientPhotoControllerTest, ShouldStartToRefreshWeather) {
auto* model = photo_controller()->ambient_backend_model();
EXPECT_FALSE(model->show_celsius());
diff --git a/ash/ambient/model/ambient_backend_model.cc b/ash/ambient/model/ambient_backend_model.cc
index c9f7374..3e3b0f34 100644
--- a/ash/ambient/model/ambient_backend_model.cc
+++ b/ash/ambient/model/ambient_backend_model.cc
@@ -56,9 +56,8 @@
NotifyTopicsChanged();
}
-bool AmbientBackendModel::ShouldFetchImmediately() const {
- // Prefetch one image |next_image_| for photo transition animation.
- return current_image_.IsNull() || next_image_.IsNull();
+bool AmbientBackendModel::ImagesReady() const {
+ return !current_image_.IsNull() && !next_image_.IsNull();
}
void AmbientBackendModel::AddNextImage(
@@ -67,6 +66,7 @@
current_image_ = photo_with_details;
} else if (next_image_.IsNull()) {
next_image_ = photo_with_details;
+ NotifyImagesReady();
} else {
current_image_ = next_image_;
next_image_ = photo_with_details;
@@ -75,8 +75,12 @@
NotifyImagesChanged();
}
+bool AmbientBackendModel::HashMatchesNextImage(const std::string& hash) const {
+ return GetNextImage().hash == hash;
+}
+
base::TimeDelta AmbientBackendModel::GetPhotoRefreshInterval() {
- if (ShouldFetchImmediately())
+ if (!ImagesReady())
return base::TimeDelta();
return photo_refresh_interval_;
@@ -125,6 +129,11 @@
observer.OnImagesChanged();
}
+void AmbientBackendModel::NotifyImagesReady() {
+ for (auto& observer : observers_)
+ observer.OnImagesReady();
+}
+
void AmbientBackendModel::NotifyWeatherInfoUpdated() {
for (auto& observer : observers_)
observer.OnWeatherInfoUpdated();
diff --git a/ash/ambient/model/ambient_backend_model.h b/ash/ambient/model/ambient_backend_model.h
index bdad83d..27ae662 100644
--- a/ash/ambient/model/ambient_backend_model.h
+++ b/ash/ambient/model/ambient_backend_model.h
@@ -36,6 +36,8 @@
gfx::ImageSkia photo;
gfx::ImageSkia related_photo;
std::string details;
+ // Hash of this image data. Used for de-duping images.
+ std::string hash;
};
// Stores necessary information fetched from the backdrop server to render
@@ -54,12 +56,15 @@
void AppendTopics(const std::vector<AmbientModeTopic>& topics);
const std::vector<AmbientModeTopic>& topics() const { return topics_; }
- // Prefetch one more image for ShowNextImage animations.
- bool ShouldFetchImmediately() const;
+ // If enough images are loaded to start ambient mode.
+ bool ImagesReady() const;
// Add image to local storage.
void AddNextImage(const PhotoWithDetails& photo);
+ // If the hash matches the hash of the next image to be displayed.
+ bool HashMatchesNextImage(const std::string& hash) const;
+
// Get/Set the photo refresh interval.
base::TimeDelta GetPhotoRefreshInterval();
void SetPhotoRefreshInterval(base::TimeDelta interval);
@@ -69,6 +74,7 @@
// Get images from local storage. Could be null image.
const PhotoWithDetails& GetNextImage() const;
+ const PhotoWithDetails& GetCurrentImage() const { return current_image_; }
// Updates the weather information and notifies observers if the icon image is
// not null.
@@ -95,6 +101,7 @@
void NotifyTopicsChanged();
void NotifyImagesChanged();
+ void NotifyImagesReady();
void NotifyWeatherInfoUpdated();
std::vector<AmbientModeTopic> topics_;
@@ -103,9 +110,6 @@
PhotoWithDetails current_image_;
PhotoWithDetails next_image_;
- // The index of currently shown image.
- int current_image_index_ = 0;
-
// Current weather information.
gfx::ImageSkia weather_condition_icon_;
float temperature_fahrenheit_ = 0.0f;
diff --git a/ash/ambient/model/ambient_backend_model_observer.h b/ash/ambient/model/ambient_backend_model_observer.h
index 6fea709..2468235 100644
--- a/ash/ambient/model/ambient_backend_model_observer.h
+++ b/ash/ambient/model/ambient_backend_model_observer.h
@@ -21,6 +21,9 @@
// Invoked when prev/current/next images changed.
virtual void OnImagesChanged() {}
+ // Invoked when enough images are loaded in memory to start ambient mode.
+ virtual void OnImagesReady() {}
+
// Invoked when the weather info (condition icon or temperature) stored in the
// model has been updated.
virtual void OnWeatherInfoUpdated() {}
diff --git a/ash/ambient/test/ambient_ash_test_base.cc b/ash/ambient/test/ambient_ash_test_base.cc
index 9b86a31..4d66fe3b 100644
--- a/ash/ambient/test/ambient_ash_test_base.cc
+++ b/ash/ambient/test/ambient_ash_test_base.cc
@@ -50,11 +50,14 @@
void Download(
const std::string& url,
network::SimpleURLLoader::BodyAsStringCallback callback) override {
+ // Reply with a unique string each time to avoid check to skip loading
+ // duplicate images.
+ std::unique_ptr<std::string> data = std::make_unique<std::string>(
+ data_ ? *data_ : base::StringPrintf("test_image_%i", count_));
+ count_++;
// Pretend to respond asynchronously.
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
- FROM_HERE,
- base::BindOnce(std::move(callback),
- std::make_unique<std::string>(data_ ? *data_ : "test")),
+ FROM_HERE, base::BindOnce(std::move(callback), std::move(data)),
base::TimeDelta::FromMilliseconds(1));
}
void DownloadToFile(
@@ -86,6 +89,7 @@
base::ScopedBlockingCall blocking(FROM_HERE, base::BlockingType::MAY_BLOCK);
return base::WriteFile(file_path, data);
}
+ int count_ = 0;
// If not null, will return this data.
std::unique_ptr<std::string> data_;
};
diff --git a/ash/ambient/ui/photo_view.cc b/ash/ambient/ui/photo_view.cc
index 6b5cd5a..c566f16 100644
--- a/ash/ambient/ui/photo_view.cc
+++ b/ash/ambient/ui/photo_view.cc
@@ -35,7 +35,6 @@
base::UmaHistogramPercentage(kPhotoTransitionSmoothness, value);
}
-
} // namespace
// PhotoView ------------------------------------------------------------------
@@ -62,7 +61,7 @@
return;
}
- UpdateImages();
+ UpdateImage(delegate_->GetAmbientBackendModel()->GetNextImage());
}
void PhotoView::Init() {
@@ -82,12 +81,13 @@
// Hides one image view initially for fade in animation.
image_views_[1]->layer()->SetOpacity(0.0f);
- delegate_->GetAmbientBackendModel()->AddObserver(this);
+ auto* model = delegate_->GetAmbientBackendModel();
+ model->AddObserver(this);
+
+ UpdateImage(model->GetCurrentImage());
}
-void PhotoView::UpdateImages() {
- auto* model = delegate_->GetAmbientBackendModel();
- auto& next_image = model->GetNextImage();
+void PhotoView::UpdateImage(const PhotoWithDetails& next_image) {
if (next_image.photo.isNull())
return;
@@ -135,7 +135,7 @@
}
void PhotoView::OnImplicitAnimationsCompleted() {
- UpdateImages();
+ UpdateImage(delegate_->GetAmbientBackendModel()->GetNextImage());
delegate_->OnPhotoTransitionAnimationCompleted();
}
diff --git a/ash/ambient/ui/photo_view.h b/ash/ambient/ui/photo_view.h
index 457b043..9ecdf88 100644
--- a/ash/ambient/ui/photo_view.h
+++ b/ash/ambient/ui/photo_view.h
@@ -22,6 +22,7 @@
class AmbientBackgroundImageView;
class AmbientViewDelegate;
+struct PhotoWithDetails;
// View to display photos in ambient mode.
class ASH_EXPORT PhotoView : public views::View,
@@ -47,7 +48,7 @@
void Init();
- void UpdateImages();
+ void UpdateImage(const PhotoWithDetails& image);
void StartTransitionAnimation();
diff --git a/ash/public/cpp/ambient/fake_ambient_backend_controller_impl.cc b/ash/public/cpp/ambient/fake_ambient_backend_controller_impl.cc
index f54c394..ed52158 100644
--- a/ash/public/cpp/ambient/fake_ambient_backend_controller_impl.cc
+++ b/ash/public/cpp/ambient/fake_ambient_backend_controller_impl.cc
@@ -83,14 +83,17 @@
void FakeAmbientBackendControllerImpl::FetchScreenUpdateInfo(
int num_topics,
OnScreenUpdateInfoFetchedCallback callback) {
- ash::AmbientModeTopic topic;
- topic.url = kFakeUrl;
- topic.details = kFakeDetails;
- topic.related_image_url = kFakeUrl;
- topic.topic_type = AmbientModeTopicType::kCulturalInstitute;
-
ash::ScreenUpdate update;
- update.next_topics.emplace_back(topic);
+
+ for (int i = 0; i < num_topics; i++) {
+ ash::AmbientModeTopic topic;
+ topic.url = kFakeUrl;
+ topic.details = kFakeDetails;
+ topic.related_image_url = kFakeUrl;
+ topic.topic_type = AmbientModeTopicType::kCulturalInstitute;
+
+ update.next_topics.emplace_back(topic);
+ }
// Only respond weather info when there is no active weather testing.
if (!weather_info_) {