Commit b92e226e authored by wutao's avatar wutao Committed by Commit Bot

ambient: Use cached photos for display

There are several situations we can use cached photos for display:
1. No internet.
2. No access token.
3. Failed to fetch topics.
4. Failed to download photos.

When failure happens, we use cache as a ring buffer to get next
available photo on disk. The cached photos are saved during normal
workflow when fetching topics and photos succeeds.

When reading from cache, for simplicity, we can try to read max times,
e.g. loop from index 0 to 99, for all possible file names. If failed,
means no disk cache available, reading from disk will pause until next
time the photo is downloaded successfully.

Cache will be cleared when Settings changes.

Bug: b/162945419
Test: Added new AmbientPhotoControllerTest.
Change-Id: I7d4d04ee1f6e9f40c64a23f6e04c35d0ce46c01f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2348689
Commit-Queue: Tao Wu <wutao@chromium.org>
Reviewed-by: default avatarXiaohui Chen <xiaohuic@chromium.org>
Cr-Commit-Position: refs/heads/master@{#799540}
parent b2a33988
...@@ -14,11 +14,33 @@ namespace ash { ...@@ -14,11 +14,33 @@ namespace ash {
constexpr base::TimeDelta kAnimationDuration = constexpr base::TimeDelta kAnimationDuration =
base::TimeDelta::FromMilliseconds(500); base::TimeDelta::FromMilliseconds(500);
// Topic related numbers.
// The default interval to fetch Topics.
constexpr base::TimeDelta kTopicFetchInterval =
base::TimeDelta::FromSeconds(30);
// The default interval to refresh photos. // The default interval to refresh photos.
// TODO(b/139953713): Change to a correct time interval.
constexpr base::TimeDelta kPhotoRefreshInterval = constexpr base::TimeDelta kPhotoRefreshInterval =
base::TimeDelta::FromSeconds(60); base::TimeDelta::FromSeconds(60);
// The number of requests to fetch topics.
constexpr int kNumberOfRequests = 50;
// The batch size of topics to fetch in one request.
// Magic number 2 is based on experiments that no curation on Google Photos.
constexpr int kTopicsBatchSize = 2;
// Max cached images.
constexpr int kMaxNumberOfCachedImages = 100;
constexpr int kMaxImageSizeInBytes = 5 * 1024 * 1024;
constexpr int kMaxReservedAvailableDiskSpaceByte = 200 * 1024 * 1024;
constexpr char kPhotoFileExt[] = ".img";
constexpr char kPhotoDetailsFileExt[] = ".txt";
// Directory name of ambient mode. // Directory name of ambient mode.
constexpr char kAmbientModeDirectoryName[] = "ambient-mode"; constexpr char kAmbientModeDirectoryName[] = "ambient-mode";
......
...@@ -94,6 +94,10 @@ class ASH_EXPORT AmbientController ...@@ -94,6 +94,10 @@ class ASH_EXPORT AmbientController
return ambient_backend_controller_.get(); return ambient_backend_controller_.get();
} }
AmbientPhotoController* ambient_photo_controller() {
return &ambient_photo_controller_;
}
AmbientUiModel* ambient_ui_model() { return &ambient_ui_model_; } AmbientUiModel* ambient_ui_model() { return &ambient_ui_model_; }
private: private:
...@@ -131,10 +135,6 @@ class ASH_EXPORT AmbientController ...@@ -131,10 +135,6 @@ class ASH_EXPORT AmbientController
void CloseWidget(bool immediately); void CloseWidget(bool immediately);
AmbientPhotoController* get_ambient_photo_controller_for_testing() {
return &ambient_photo_controller_;
}
AmbientContainerView* get_container_view_for_testing() { AmbientContainerView* get_container_view_for_testing() {
return container_view_; return container_view_;
} }
......
This diff is collapsed.
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "ash/ambient/ambient_constants.h"
#include "ash/ambient/model/ambient_backend_model.h" #include "ash/ambient/model/ambient_backend_model.h"
#include "ash/ambient/model/ambient_backend_model_observer.h" #include "ash/ambient/model/ambient_backend_model_observer.h"
#include "ash/ash_export.h" #include "ash/ash_export.h"
...@@ -21,6 +22,7 @@ ...@@ -21,6 +22,7 @@
#include "base/optional.h" #include "base/optional.h"
#include "base/scoped_observer.h" #include "base/scoped_observer.h"
#include "base/timer/timer.h" #include "base/timer/timer.h"
#include "net/base/backoff_entry.h"
#include "services/network/public/cpp/simple_url_loader.h" #include "services/network/public/cpp/simple_url_loader.h"
namespace gfx { namespace gfx {
...@@ -96,38 +98,42 @@ class ASH_EXPORT AmbientPhotoController : public AmbientBackendModelObserver { ...@@ -96,38 +98,42 @@ class ASH_EXPORT AmbientPhotoController : public AmbientBackendModelObserver {
// AmbientBackendModelObserver: // AmbientBackendModelObserver:
void OnTopicsChanged() override; void OnTopicsChanged() override;
// Clear cache when Settings changes.
void ClearCache();
private: private:
friend class AmbientAshTestBase; friend class AmbientAshTestBase;
void FetchTopics(); void FetchTopics();
void ScheduleFetchTopics(); void ScheduleFetchTopics(bool backoff);
void ScheduleRefreshImage(); void ScheduleRefreshImage();
void GetScreenUpdateInfo(); void GetScreenUpdateInfo();
// Return a topic to download the image. // Return a topic to download the image.
const AmbientModeTopic& GetNextTopic(); // Return nullptr when need to read from disk cache.
const AmbientModeTopic* GetNextTopic();
void OnScreenUpdateInfoFetched(const ash::ScreenUpdate& screen_update); void OnScreenUpdateInfoFetched(const ash::ScreenUpdate& screen_update);
// Try to read photo raw data from disk. // Fetch photo raw data by downloading or reading from cache.
void TryReadPhotoRawData(); void FetchPhotoRawData();
// If photo raw data is read successfully, call OnPhotoRawDataAvailable() to // Try to read photo raw data from cache.
// decode data. Otherwise, download the raw data and save to disk. void TryReadPhotoRawData();
void OnPhotoRawDataRead(const AmbientModeTopic& topic,
std::unique_ptr<std::string> data);
void OnPhotoRawDataAvailable(const AmbientModeTopic& topic, void OnPhotoRawDataAvailable(bool from_downloading,
bool need_to_save, std::unique_ptr<std::string> details,
std::unique_ptr<std::string> response_body); std::unique_ptr<std::string> data);
void DecodePhotoRawData(const AmbientModeTopic& topic, void DecodePhotoRawData(bool from_downloading,
std::unique_ptr<std::string> details,
std::unique_ptr<std::string> data); std::unique_ptr<std::string> data);
void OnPhotoDecoded(const AmbientModeTopic& topic, void OnPhotoDecoded(bool from_downloading,
std::unique_ptr<std::string> details,
const gfx::ImageSkia& image); const gfx::ImageSkia& image);
void StartDownloadingWeatherConditionIcon( void StartDownloadingWeatherConditionIcon(
...@@ -144,6 +150,8 @@ class ASH_EXPORT AmbientPhotoController : public AmbientBackendModelObserver { ...@@ -144,6 +150,8 @@ class ASH_EXPORT AmbientPhotoController : public AmbientBackendModelObserver {
url_loader_ = std::move(url_loader); url_loader_ = std::move(url_loader);
} }
AmbientURLLoader* get_url_loader_for_testing() { return url_loader_.get(); }
void set_image_decoder_for_testing( void set_image_decoder_for_testing(
std::unique_ptr<AmbientImageDecoder> image_decoder) { std::unique_ptr<AmbientImageDecoder> image_decoder) {
image_decoder_ = std::move(image_decoder); image_decoder_ = std::move(image_decoder);
...@@ -153,6 +161,10 @@ class ASH_EXPORT AmbientPhotoController : public AmbientBackendModelObserver { ...@@ -153,6 +161,10 @@ class ASH_EXPORT AmbientPhotoController : public AmbientBackendModelObserver {
return image_decoder_.get(); return image_decoder_.get();
} }
void FetchTopicsForTesting();
void FetchImageForTesting();
AmbientBackendModel ambient_backend_model_; AmbientBackendModel ambient_backend_model_;
// The timer to refresh photos. // The timer to refresh photos.
...@@ -164,11 +176,33 @@ class ASH_EXPORT AmbientPhotoController : public AmbientBackendModelObserver { ...@@ -164,11 +176,33 @@ class ASH_EXPORT AmbientPhotoController : public AmbientBackendModelObserver {
// Tracking how many batches of topics have been fetched. // Tracking how many batches of topics have been fetched.
int topics_batch_fetched_ = 0; int topics_batch_fetched_ = 0;
// Current index of cached image to read and display when failure happens.
// The image file of this index may not exist or may not be valid. It will try
// to read from the next cached file by increasing this index by 1.
int cache_index_for_display_ = 0;
// Current index of cached image to save for the latest downloaded photo.
// The write command could fail. This index will increase 1 no matter writing
// successfully or not. But theoretically we could not to change this index if
// failures happen.
int cache_index_for_store_ = 0;
// Whether the image refresh started or not.
bool image_refresh_started_ = false;
// Cached image may not exist or valid. This is the max times of attempts to
// read cached images.
int retries_to_read_from_cache_ = kMaxNumberOfCachedImages;
// Backoff for fetch topics retries.
net::BackoffEntry fetch_topic_retry_backoff_;
// Backoff to resume fetch images.
net::BackoffEntry resume_fetch_image_backoff_;
ScopedObserver<AmbientBackendModel, AmbientBackendModelObserver> ScopedObserver<AmbientBackendModel, AmbientBackendModelObserver>
ambient_backend_model_observer_{this}; ambient_backend_model_observer_{this};
base::FilePath photo_path_;
std::unique_ptr<AmbientURLLoader> url_loader_; std::unique_ptr<AmbientURLLoader> url_loader_;
std::unique_ptr<AmbientImageDecoder> image_decoder_; std::unique_ptr<AmbientImageDecoder> image_decoder_;
......
...@@ -390,6 +390,17 @@ void AmbientBackendControllerImpl::OnUpdateSettings( ...@@ -390,6 +390,17 @@ void AmbientBackendControllerImpl::OnUpdateSettings(
const bool success = const bool success =
BackdropClientConfig::ParseUpdateSettingsResponse(*response); BackdropClientConfig::ParseUpdateSettingsResponse(*response);
std::move(callback).Run(success); std::move(callback).Run(success);
// Clear disk cache when Settings changes.
// TODO(wutao): Use observer pattern. Need to future narrow down
// the clear up only on albums changes, not on temperature unit
// changes.
if (success) {
Shell::Get()
->ambient_controller()
->ambient_photo_controller()
->ClearCache();
}
} }
void AmbientBackendControllerImpl::FetchSettingPreviewInternal( void AmbientBackendControllerImpl::FetchSettingPreviewInternal(
......
...@@ -41,13 +41,20 @@ class TestAmbientURLLoaderImpl : public AmbientURLLoader { ...@@ -41,13 +41,20 @@ class TestAmbientURLLoaderImpl : public AmbientURLLoader {
void Download( void Download(
const std::string& url, const std::string& url,
network::SimpleURLLoader::BodyAsStringCallback callback) override { network::SimpleURLLoader::BodyAsStringCallback callback) override {
auto data = std::make_unique<std::string>(); std::string data = data_ ? *data_ : "test";
*data = "test";
// Pretend to respond asynchronously. // Pretend to respond asynchronously.
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask( base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(data)), FROM_HERE,
base::BindOnce(std::move(callback),
std::make_unique<std::string>(data)),
base::TimeDelta::FromMilliseconds(1)); base::TimeDelta::FromMilliseconds(1));
} }
void SetData(std::unique_ptr<std::string> data) { data_ = std::move(data); }
private:
// If not null, will return this data.
std::unique_ptr<std::string> data_;
}; };
class TestAmbientImageDecoderImpl : public AmbientImageDecoder { class TestAmbientImageDecoderImpl : public AmbientImageDecoder {
...@@ -59,10 +66,14 @@ class TestAmbientImageDecoderImpl : public AmbientImageDecoder { ...@@ -59,10 +66,14 @@ class TestAmbientImageDecoderImpl : public AmbientImageDecoder {
void Decode( void Decode(
const std::vector<uint8_t>& encoded_bytes, const std::vector<uint8_t>& encoded_bytes,
base::OnceCallback<void(const gfx::ImageSkia&)> callback) override { base::OnceCallback<void(const gfx::ImageSkia&)> callback) override {
gfx::ImageSkia image =
image_ ? *image_ : gfx::test::CreateImageSkia(width_, height_);
// Only use once.
image_.reset();
// Pretend to respond asynchronously. // Pretend to respond asynchronously.
base::SequencedTaskRunnerHandle::Get()->PostTask( base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), FROM_HERE, base::BindOnce(std::move(callback), image));
gfx::test::CreateImageSkia(width_, height_)));
} }
void SetImageSize(int width, int height) { void SetImageSize(int width, int height) {
...@@ -70,10 +81,15 @@ class TestAmbientImageDecoderImpl : public AmbientImageDecoder { ...@@ -70,10 +81,15 @@ class TestAmbientImageDecoderImpl : public AmbientImageDecoder {
height_ = height; height_ = height;
} }
void SetImage(const gfx::ImageSkia& image) { image_ = image; }
private: private:
// Width and height of test images. // Width and height of test images.
int width_ = 10; int width_ = 10;
int height_ = 20; int height_ = 20;
// If set, will replay this image.
base::Optional<gfx::ImageSkia> image_;
}; };
AmbientAshTestBase::AmbientAshTestBase() AmbientAshTestBase::AmbientAshTestBase()
...@@ -186,9 +202,7 @@ void AmbientAshTestBase::SimulateMediaPlaybackStateChanged( ...@@ -186,9 +202,7 @@ void AmbientAshTestBase::SimulateMediaPlaybackStateChanged(
void AmbientAshTestBase::SetPhotoViewImageSize(int width, int height) { void AmbientAshTestBase::SetPhotoViewImageSize(int width, int height) {
auto* image_decoder = static_cast<TestAmbientImageDecoderImpl*>( auto* image_decoder = static_cast<TestAmbientImageDecoderImpl*>(
ambient_controller() photo_controller()->get_image_decoder_for_testing());
->get_ambient_photo_controller_for_testing()
->get_image_decoder_for_testing());
image_decoder->SetImageSize(width, height); image_decoder->SetImageSize(width, height);
} }
...@@ -242,11 +256,33 @@ AmbientController* AmbientAshTestBase::ambient_controller() { ...@@ -242,11 +256,33 @@ AmbientController* AmbientAshTestBase::ambient_controller() {
} }
AmbientPhotoController* AmbientAshTestBase::photo_controller() { AmbientPhotoController* AmbientAshTestBase::photo_controller() {
return ambient_controller()->get_ambient_photo_controller_for_testing(); return ambient_controller()->ambient_photo_controller();
} }
AmbientContainerView* AmbientAshTestBase::container_view() { AmbientContainerView* AmbientAshTestBase::container_view() {
return ambient_controller()->get_container_view_for_testing(); return ambient_controller()->get_container_view_for_testing();
} }
void AmbientAshTestBase::FetchTopics() {
photo_controller()->FetchTopicsForTesting();
}
void AmbientAshTestBase::FetchImage() {
photo_controller()->FetchImageForTesting();
}
void AmbientAshTestBase::SetUrlLoaderData(std::unique_ptr<std::string> data) {
auto* url_loader_ = static_cast<TestAmbientURLLoaderImpl*>(
photo_controller()->get_url_loader_for_testing());
url_loader_->SetData(std::move(data));
}
void AmbientAshTestBase::SeteImageDecoderImage(const gfx::ImageSkia& image) {
auto* image_decoder = static_cast<TestAmbientImageDecoderImpl*>(
photo_controller()->get_image_decoder_for_testing());
image_decoder->SetImage(image);
}
} // namespace ash } // namespace ash
...@@ -103,6 +103,14 @@ class AmbientAshTestBase : public AshTestBase { ...@@ -103,6 +103,14 @@ class AmbientAshTestBase : public AshTestBase {
// Returns the top-level view which contains all the ambient components. // Returns the top-level view which contains all the ambient components.
AmbientContainerView* container_view(); AmbientContainerView* container_view();
void FetchTopics();
void FetchImage();
void SetUrlLoaderData(std::unique_ptr<std::string> data);
void SeteImageDecoderImage(const gfx::ImageSkia& image);
private: private:
base::test::ScopedFeatureList scoped_feature_list_; base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<TestImageDownloader> image_downloader_; std::unique_ptr<TestImageDownloader> image_downloader_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment