[M87 merge] Reland "ambient: cache two backup photos"
This is a reland of a81c26884bde81a386ffb2dcbdb7a12f0ecd1215
Original change's description:
> ambient: cache two backup photos
>
> Handle failure cases better by having backup photos on disk.
> Refresh these images after user logs in.
>
> BUG=b:167332126
>
> Cq-Include-Trybots: luci.chrome.try:linux-chromeos-chrome
> Change-Id: I7046a20ed9606638e851247f8c213d112af6c30b
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2429585
> Commit-Queue: Jeffrey Young <[email protected]>
> Reviewed-by: Xiaohui Chen <[email protected]>
> Reviewed-by: Tao Wu <[email protected]>
> Cr-Commit-Position: refs/heads/master@{#813904}
(cherry picked from commit 2c34160725973ce93507b98fde9dfda735fcb565)
Bug: b:167332126
Bug: b:170312454
Change-Id: If5306f627e1a6dfb5e4eb37842a470d17968ad4e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2458894
Commit-Queue: Jeffrey Young <[email protected]>
Reviewed-by: Tao Wu <[email protected]>
Cr-Original-Commit-Position: refs/heads/master@{#814968}
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2464073
Reviewed-by: Xiaohui Chen <[email protected]>
Cr-Commit-Position: refs/branch-heads/4280@{#262}
Cr-Branched-From: ea420fb963f9658c9969b6513c56b8f47efa1a2a-refs/heads/master@{#812852}
diff --git a/ash/ambient/ambient_constants.h b/ash/ambient/ambient_constants.h
index 426a538..0b82785 100644
--- a/ash/ambient/ambient_constants.h
+++ b/ash/ambient/ambient_constants.h
@@ -24,6 +24,10 @@
constexpr base::TimeDelta kPhotoRefreshInterval =
base::TimeDelta::FromSeconds(60);
+// The default interval to fetch backup cache photos.
+constexpr base::TimeDelta kBackupPhotoRefreshDelay =
+ base::TimeDelta::FromMinutes(5);
+
// The default interval to refresh weather.
constexpr base::TimeDelta kWeatherRefreshInterval =
base::TimeDelta::FromMinutes(5);
@@ -47,6 +51,10 @@
// Directory name of ambient mode.
constexpr char kAmbientModeDirectoryName[] = "ambient-mode";
+constexpr char kAmbientModeCacheDirectoryName[] = "cache";
+
+constexpr char kAmbientModeBackupCacheDirectoryName[] = "backup";
+
// The buffer time to use the access token.
constexpr base::TimeDelta kTokenUsageTimeBuffer =
base::TimeDelta::FromMinutes(10);
diff --git a/ash/ambient/ambient_controller.cc b/ash/ambient/ambient_controller.cc
index f0ea88cf..35d6b1b 100644
--- a/ash/ambient/ambient_controller.cc
+++ b/ash/ambient/ambient_controller.cc
@@ -324,6 +324,11 @@
}
}
+void AmbientController::OnFirstSessionStarted() {
+ if (IsAmbientModeEnabled())
+ ambient_photo_controller_.ScheduleFetchBackupImages();
+}
+
void AmbientController::OnPowerStatusChanged() {
if (ambient_ui_model_.ui_visibility() != AmbientUiVisibility::kShown) {
// No action needed if ambient screen is not shown.
diff --git a/ash/ambient/ambient_controller.h b/ash/ambient/ambient_controller.h
index 740f03f..7926945 100644
--- a/ash/ambient/ambient_controller.h
+++ b/ash/ambient/ambient_controller.h
@@ -63,6 +63,7 @@
// SessionObserver:
void OnLockStateChanged(bool locked) override;
+ void OnFirstSessionStarted() override;
// PowerStatus::Observer:
void OnPowerStatusChanged() override;
diff --git a/ash/ambient/ambient_photo_controller.cc b/ash/ambient/ambient_photo_controller.cc
index b5ea5c9..bc5d11e 100644
--- a/ash/ambient/ambient_photo_controller.cc
+++ b/ash/ambient/ambient_photo_controller.cc
@@ -4,8 +4,10 @@
#include "ash/ambient/ambient_photo_controller.h"
+#include <array>
#include <string>
#include <utility>
+#include <vector>
#include "ash/ambient/ambient_constants.h"
#include "ash/ambient/ambient_controller.h"
@@ -81,17 +83,6 @@
base::BindOnce(std::move(callback)));
}
-// Get the root path for ambient mode.
-base::FilePath GetRootPath() {
- base::FilePath home_dir;
- CHECK(base::PathService::Get(base::DIR_HOME, &home_dir));
- return home_dir.Append(FILE_PATH_LITERAL(kAmbientModeDirectoryName));
-}
-
-void DeletePathRecursively(const base::FilePath& path) {
- base::DeletePathRecursively(path);
-}
-
void ToImageSkia(DownloadCallback callback, const SkBitmap& image) {
if (image.isNull()) {
std::move(callback).Run(gfx::ImageSkia());
@@ -109,9 +100,34 @@
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN};
}
+// Get the root path for ambient mode.
+base::FilePath GetRootPath() {
+ base::FilePath home_dir;
+ CHECK(base::PathService::Get(base::DIR_HOME, &home_dir));
+ return home_dir.Append(FILE_PATH_LITERAL(kAmbientModeDirectoryName));
+}
+
+base::FilePath GetCachePath() {
+ return GetRootPath().Append(
+ FILE_PATH_LITERAL(kAmbientModeCacheDirectoryName));
+}
+
+base::FilePath GetBackupCachePath() {
+ return GetRootPath().Append(
+ FILE_PATH_LITERAL(kAmbientModeBackupCacheDirectoryName));
+}
+
+base::FilePath GetBackupFilePath(size_t index) {
+ return GetBackupCachePath().Append(base::NumberToString(index) +
+ kPhotoFileExt);
+}
+
+bool CreateDirIfNotExists(const base::FilePath& path) {
+ return base::DirectoryExists(path) || base::CreateDirectory(path);
+}
+
void WriteFile(const base::FilePath& path, const std::string& data) {
- if (!base::PathExists(GetRootPath()) &&
- !base::CreateDirectory(GetRootPath())) {
+ if (!CreateDirIfNotExists(GetCachePath())) {
LOG(ERROR) << "Cannot create ambient mode directory.";
return;
}
@@ -143,6 +159,13 @@
LOG(ERROR) << "Cannot replace the temporary file.";
}
+const std::array<const char*, 2>& GetBackupPhotoUrls() {
+ return Shell::Get()
+ ->ambient_controller()
+ ->ambient_backend_controller()
+ ->GetBackupPhotoUrls();
+}
+
} // namespace
class AmbientURLLoaderImpl : public AmbientURLLoader {
@@ -154,13 +177,7 @@
void Download(
const std::string& url,
network::SimpleURLLoader::BodyAsStringCallback callback) override {
- auto resource_request = std::make_unique<network::ResourceRequest>();
- resource_request->url = GURL(url);
- resource_request->method = "GET";
- resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
-
- auto simple_loader = network::SimpleURLLoader::Create(
- std::move(resource_request), NO_TRAFFIC_ANNOTATION_YET);
+ auto simple_loader = CreateSimpleURLLoader(url);
auto* loader_ptr = simple_loader.get();
auto loader_factory = AmbientClient::Get()->GetURLLoaderFactory();
loader_ptr->DownloadToString(
@@ -171,7 +188,45 @@
kMaxImageSizeInBytes);
}
+ void DownloadToFile(
+ const std::string& url,
+ network::SimpleURLLoader::DownloadToFileCompleteCallback callback,
+ const base::FilePath& file_path) override {
+ auto simple_loader = CreateSimpleURLLoader(url);
+ auto loader_factory = AmbientClient::Get()->GetURLLoaderFactory();
+ auto* loader_ptr = simple_loader.get();
+ // Download to temp file first to guarantee entire image is written without
+ // errors before attempting to read it.
+
+ // Create a temp file.
+ base::FilePath temp_file;
+ if (!base::CreateTemporaryFileInDir(file_path.DirName(), &temp_file)) {
+ LOG(ERROR) << "Cannot create a temporary file";
+ std::move(callback).Run(base::FilePath());
+ return;
+ }
+
+ loader_ptr->DownloadToFile(
+ loader_factory.get(),
+ base::BindOnce(&AmbientURLLoaderImpl::OnUrlDownloadedToFile,
+ weak_factory_.GetWeakPtr(), std::move(callback),
+ std::move(simple_loader), std::move(loader_factory),
+ file_path),
+ temp_file);
+ }
+
private:
+ std::unique_ptr<network::SimpleURLLoader> CreateSimpleURLLoader(
+ const std::string& url) {
+ auto resource_request = std::make_unique<network::ResourceRequest>();
+ resource_request->url = GURL(url);
+ resource_request->method = "GET";
+ resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+
+ return network::SimpleURLLoader::Create(std::move(resource_request),
+ NO_TRAFFIC_ANNOTATION_YET);
+ }
+
// Called when the download completes.
void OnUrlDownloaded(
network::SimpleURLLoader::BodyAsStringCallback callback,
@@ -183,18 +238,42 @@
return;
}
- int response_code = -1;
- if (simple_loader->ResponseInfo() &&
- simple_loader->ResponseInfo()->headers) {
- response_code = simple_loader->ResponseInfo()->headers->response_code();
- }
-
- LOG(ERROR) << "Downloading Backdrop proto failed with error code: "
- << response_code << " with network error"
+ LOG(ERROR) << "Downloading to string failed with error code: "
+ << GetResponseCode(simple_loader.get()) << " with network error"
<< simple_loader->NetError();
std::move(callback).Run(std::make_unique<std::string>());
}
+ void OnUrlDownloadedToFile(
+ network::SimpleURLLoader::DownloadToFileCompleteCallback callback,
+ std::unique_ptr<network::SimpleURLLoader> simple_loader,
+ scoped_refptr<network::SharedURLLoaderFactory> loader_factory,
+ const base::FilePath& desired_path,
+ base::FilePath temp_path) {
+ if (simple_loader->NetError() != net::OK || temp_path.empty()) {
+ LOG(ERROR) << "Downloading to file failed with error code: "
+ << GetResponseCode(simple_loader.get())
+ << " with network error" << simple_loader->NetError();
+ std::move(callback).Run(base::FilePath());
+ return;
+ }
+ if (!base::ReplaceFile(temp_path, desired_path, /*error=*/nullptr)) {
+ LOG(ERROR) << "Unable to move downloaded file to ambient directory";
+ std::move(callback).Run(base::FilePath());
+ return;
+ }
+ std::move(callback).Run(std::move(desired_path));
+ }
+
+ int GetResponseCode(network::SimpleURLLoader* simple_loader) {
+ if (simple_loader->ResponseInfo() &&
+ simple_loader->ResponseInfo()->headers) {
+ return simple_loader->ResponseInfo()->headers->response_code();
+ } else {
+ return -1;
+ }
+ }
+
base::WeakPtrFactory<AmbientURLLoaderImpl> weak_factory_{this};
};
@@ -234,6 +313,12 @@
FROM_HERE, kWeatherRefreshInterval,
base::BindRepeating(&AmbientPhotoController::FetchWeather,
weak_factory_.GetWeakPtr()));
+ if (backup_photo_refresh_timer_.IsRunning()) {
+ // Would use |timer_.FireNow()| but this does not execute if screen is
+ // locked. Manually call the expected callback instead.
+ backup_photo_refresh_timer_.Stop();
+ PrepareFetchBackupImages();
+ }
}
void AmbientPhotoController::StopScreenUpdate() {
@@ -242,12 +327,25 @@
topic_index_ = 0;
image_refresh_started_ = false;
retries_to_read_from_cache_ = kMaxNumberOfCachedImages;
+ backup_retries_to_read_from_cache_ = GetBackupPhotoUrls().size();
fetch_topic_retry_backoff_.Reset();
resume_fetch_image_backoff_.Reset();
ambient_backend_model_.Clear();
weak_factory_.InvalidateWeakPtrs();
}
+void AmbientPhotoController::ScheduleFetchBackupImages() {
+ if (backup_photo_refresh_timer_.IsRunning())
+ return;
+
+ backup_photo_refresh_timer_.Start(
+ FROM_HERE,
+ std::max(kBackupPhotoRefreshDelay,
+ resume_fetch_image_backoff_.GetTimeUntilRelease()),
+ base::BindOnce(&AmbientPhotoController::PrepareFetchBackupImages,
+ weak_factory_.GetWeakPtr()));
+}
+
void AmbientPhotoController::OnTopicsChanged() {
if (ambient_backend_model_.topics().size() < kMaxNumberOfCachedImages)
ScheduleFetchTopics(/*backoff=*/false);
@@ -279,19 +377,23 @@
void AmbientPhotoController::ClearCache() {
task_runner_->PostTask(FROM_HERE,
- base::BindOnce(&DeletePathRecursively, GetRootPath()));
+ base::BindOnce(
+ [](const base::FilePath& file_path) {
+ base::DeletePathRecursively(file_path);
+ },
+ GetCachePath()));
}
void AmbientPhotoController::ScheduleFetchTopics(bool backoff) {
// If retry, using the backoff delay, otherwise the default delay.
- const base::TimeDelta kDelay =
+ const base::TimeDelta delay =
backoff ? fetch_topic_retry_backoff_.GetTimeUntilRelease()
: kTopicFetchInterval;
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&AmbientPhotoController::FetchTopics,
weak_factory_.GetWeakPtr()),
- kDelay);
+ delay);
}
void AmbientPhotoController::ScheduleRefreshImage() {
@@ -307,6 +409,37 @@
weak_factory_.GetWeakPtr()));
}
+void AmbientPhotoController::PrepareFetchBackupImages() {
+ task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ base::BindOnce([]() { CreateDirIfNotExists(GetBackupCachePath()); }),
+ base::BindOnce(&AmbientPhotoController::FetchBackupImages,
+ weak_factory_.GetWeakPtr()));
+}
+
+void AmbientPhotoController::FetchBackupImages() {
+ const auto& backup_photo_urls = GetBackupPhotoUrls();
+ backup_retries_to_read_from_cache_ = backup_photo_urls.size();
+ for (size_t i = 0; i < backup_photo_urls.size(); i++) {
+ url_loader_->DownloadToFile(
+ backup_photo_urls.at(i),
+ base::BindOnce(&AmbientPhotoController::OnBackupImageFetched,
+ weak_factory_.GetWeakPtr()),
+ GetBackupFilePath(i));
+ }
+}
+
+void AmbientPhotoController::OnBackupImageFetched(base::FilePath file_path) {
+ if (file_path.empty()) {
+ // TODO(b/169807068) Change to retry individual failed images.
+ resume_fetch_image_backoff_.InformOfRequest(/*succeeded=*/false);
+ LOG(WARNING) << "Downloading backup image failed.";
+ ScheduleFetchBackupImages();
+ return;
+ }
+ resume_fetch_image_backoff_.InformOfRequest(/*succeeded=*/true);
+}
+
const AmbientModeTopic* AmbientPhotoController::GetNextTopic() {
const auto& topics = ambient_backend_model_.topics();
// If no more topics, will read from cache.
@@ -383,21 +516,54 @@
}
void AmbientPhotoController::TryReadPhotoRawData() {
+ auto on_done =
+ base::BindRepeating(&AmbientPhotoController::OnAllPhotoRawDataAvailable,
+ weak_factory_.GetWeakPtr(),
+ /*from_downloading=*/false);
+
// Stop reading from cache after the max number of retries.
if (retries_to_read_from_cache_ == 0) {
- if (topic_index_ == ambient_backend_model_.topics().size()) {
- image_refresh_started_ = false;
+ if (backup_retries_to_read_from_cache_ == 0) {
+ LOG(WARNING) << "Failed to read from cache";
+ if (topic_index_ == ambient_backend_model_.topics().size()) {
+ image_refresh_started_ = false;
+ return;
+ }
+
+ // Try to resume normal workflow with backoff.
+ const base::TimeDelta delay =
+ resume_fetch_image_backoff_.GetTimeUntilRelease();
+ base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&AmbientPhotoController::ScheduleRefreshImage,
+ weak_factory_.GetWeakPtr()),
+ delay);
return;
}
- // Try to resume normal workflow with backoff.
- const base::TimeDelta kDelay =
- resume_fetch_image_backoff_.GetTimeUntilRelease();
- base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+ --backup_retries_to_read_from_cache_;
+ // Try to read a backup image.
+ auto photo_data = std::make_unique<std::string>();
+ auto* photo_data_ptr = photo_data.get();
+ task_runner_->PostTaskAndReply(
FROM_HERE,
- base::BindOnce(&AmbientPhotoController::ScheduleRefreshImage,
- weak_factory_.GetWeakPtr()),
- kDelay);
+ base::BindOnce(
+ [](size_t index, std::string* data) {
+ if (!base::ReadFileToString(GetBackupFilePath(index), data)) {
+ LOG(ERROR) << "Unable to read from backup cache.";
+ data->clear();
+ }
+ },
+ backup_cache_index_for_display_, photo_data_ptr),
+ base::BindOnce(&AmbientPhotoController::OnPhotoRawDataAvailable,
+ weak_factory_.GetWeakPtr(), /*from_downloading=*/false,
+ /*is_related_image=*/false, std::move(on_done),
+ /*details=*/std::make_unique<std::string>(),
+ std::move(photo_data)));
+
+ backup_cache_index_for_display_++;
+ if (backup_cache_index_for_display_ == GetBackupPhotoUrls().size())
+ backup_cache_index_for_display_ = 0;
return;
}
@@ -409,22 +575,18 @@
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(
[](const std::string& file_name, std::string* photo_data,
std::string* photo_details) {
if (!base::ReadFileToString(
- GetRootPath().Append(file_name + kPhotoFileExt),
+ GetCachePath().Append(file_name + kPhotoFileExt),
photo_data)) {
photo_data->clear();
}
if (!base::ReadFileToString(
- GetRootPath().Append(file_name + kPhotoDetailsFileExt),
+ GetCachePath().Append(file_name + kPhotoDetailsFileExt),
photo_details)) {
photo_details->clear();
}
@@ -432,7 +594,7 @@
file_name, photo_data.get(), photo_details.get()),
base::BindOnce(&AmbientPhotoController::OnPhotoRawDataAvailable,
weak_factory_.GetWeakPtr(), /*from_downloading=*/false,
- /*is_related_image=*/false, on_done,
+ /*is_related_image=*/false, std::move(on_done),
std::move(photo_details), std::move(photo_data)));
}
@@ -485,8 +647,8 @@
[](const std::string& file_name, bool need_to_save,
const std::string& data, const std::string& details) {
if (need_to_save) {
- WriteFile(GetRootPath().Append(file_name + kPhotoFileExt), data);
- WriteFile(GetRootPath().Append(file_name + kPhotoDetailsFileExt),
+ WriteFile(GetCachePath().Append(file_name + kPhotoFileExt), data);
+ WriteFile(GetCachePath().Append(file_name + kPhotoDetailsFileExt),
details);
}
},
@@ -538,6 +700,8 @@
}
retries_to_read_from_cache_ = kMaxNumberOfCachedImages;
+ backup_retries_to_read_from_cache_ = GetBackupPhotoUrls().size();
+
if (from_downloading)
resume_fetch_image_backoff_.InformOfRequest(/*succeeded=*/true);
@@ -603,4 +767,8 @@
FetchPhotoRawData();
}
+void AmbientPhotoController::FetchBackupImagesForTesting() {
+ PrepareFetchBackupImages();
+}
+
} // namespace ash
diff --git a/ash/ambient/ambient_photo_controller.h b/ash/ambient/ambient_photo_controller.h
index d2894dc0..d8ef32a2 100644
--- a/ash/ambient/ambient_photo_controller.h
+++ b/ash/ambient/ambient_photo_controller.h
@@ -44,6 +44,11 @@
virtual void Download(
const std::string& url,
network::SimpleURLLoader::BodyAsStringCallback callback) = 0;
+
+ virtual void DownloadToFile(
+ const std::string& url,
+ network::SimpleURLLoader::DownloadToFileCompleteCallback callback,
+ const base::FilePath& file_path) = 0;
};
// A wrapper class of |data_decoder| to decode the photo raw data. In the test,
@@ -87,6 +92,8 @@
void StartScreenUpdate();
void StopScreenUpdate();
+ void ScheduleFetchBackupImages();
+
AmbientBackendModel* ambient_backend_model() {
return &ambient_backend_model_;
}
@@ -95,6 +102,10 @@
return photo_refresh_timer_;
}
+ const base::OneShotTimer& backup_photo_refresh_timer_for_testing() const {
+ return backup_photo_refresh_timer_;
+ }
+
// AmbientBackendModelObserver:
void OnTopicsChanged() override;
@@ -112,6 +123,14 @@
void ScheduleRefreshImage();
+ // Create the backup cache directory and start downloading images.
+ void PrepareFetchBackupImages();
+
+ // Download backup cache images.
+ void FetchBackupImages();
+
+ void OnBackupImageFetched(base::FilePath file_path);
+
void GetScreenUpdateInfo();
// Return a topic to download the image.
@@ -178,11 +197,16 @@
void FetchImageForTesting();
+ void FetchBackupImagesForTesting();
+
AmbientBackendModel ambient_backend_model_;
// The timer to refresh photos.
base::OneShotTimer photo_refresh_timer_;
+ // The timer to refresh backup cache photos.
+ base::OneShotTimer backup_photo_refresh_timer_;
+
// The timer to refresh weather information.
base::RepeatingTimer weather_refresh_timer_;
@@ -194,6 +218,10 @@
// to read from the next cached file by increasing this index by 1.
int cache_index_for_display_ = 0;
+ // Current index of backup cached image to display when no other cached images
+ // are available.
+ size_t backup_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
@@ -207,6 +235,8 @@
// read cached images.
int retries_to_read_from_cache_ = kMaxNumberOfCachedImages;
+ int backup_retries_to_read_from_cache_ = 0;
+
// Backoff for fetch topics retries.
net::BackoffEntry fetch_topic_retry_backoff_;
diff --git a/ash/ambient/ambient_photo_controller_unittest.cc b/ash/ambient/ambient_photo_controller_unittest.cc
index 70aa5f5..c56d81c 100644
--- a/ash/ambient/ambient_photo_controller_unittest.cc
+++ b/ash/ambient/ambient_photo_controller_unittest.cc
@@ -31,7 +31,48 @@
namespace ash {
-using AmbientPhotoControllerTest = AmbientAshTestBase;
+class AmbientPhotoControllerTest : public AmbientAshTestBase {
+ public:
+ // AmbientAshTestBase:
+ void SetUp() override {
+ AmbientAshTestBase::SetUp();
+ CleanupAmbientDir();
+ }
+ void TearDown() override {
+ AmbientAshTestBase::TearDown();
+ CleanupAmbientDir();
+ }
+
+ void CleanupAmbientDir() { base::DeletePathRecursively(GetRootDir()); }
+
+ std::vector<base::FilePath> GetFilePathsInDir(const base::FilePath& dir) {
+ std::vector<base::FilePath> result;
+ base::FileEnumerator files(
+ dir, /*recursive=*/false,
+ base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
+ for (base::FilePath current = files.Next(); !current.empty();
+ current = files.Next()) {
+ result.emplace_back(current);
+ }
+ return result;
+ }
+
+ base::FilePath GetRootDir() {
+ base::FilePath home_dir;
+ base::PathService::Get(base::DIR_HOME, &home_dir);
+ return home_dir.Append(FILE_PATH_LITERAL(kAmbientModeDirectoryName));
+ }
+
+ base::FilePath GetCacheDir() {
+ return GetRootDir().Append(
+ FILE_PATH_LITERAL(kAmbientModeCacheDirectoryName));
+ }
+
+ base::FilePath GetBackupCacheDir() {
+ return GetRootDir().Append(
+ FILE_PATH_LITERAL(kAmbientModeBackupCacheDirectoryName));
+ }
+};
// Test that topics are downloaded when starting screen update.
TEST_F(AmbientPhotoControllerTest, ShouldStartToDownloadTopics) {
@@ -101,53 +142,29 @@
// Test that image is saved.
TEST_F(AmbientPhotoControllerTest, ShouldSaveImagesOnDisk) {
- base::FilePath home_dir;
- base::PathService::Get(base::DIR_HOME, &home_dir);
-
- base::FilePath ambient_image_path =
- home_dir.Append(FILE_PATH_LITERAL(kAmbientModeDirectoryName));
-
- // Clean up.
- base::DeletePathRecursively(ambient_image_path);
+ 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.
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
+ // session.
EXPECT_TRUE(base::PathExists(ambient_image_path));
-
- {
- // Count files and directories in root_path. There should only be one file
- // that was just created to save image files for this ambient mode session.
- base::FileEnumerator files(
- ambient_image_path, /*recursive=*/false,
- base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
- int count = 0;
- for (base::FilePath current = files.Next(); !current.empty();
- current = files.Next()) {
- EXPECT_FALSE(files.GetInfo().IsDirectory());
- count++;
- }
-
- // Two image files and two attribution files.
- EXPECT_EQ(count, 4);
+ auto file_paths = GetFilePathsInDir(ambient_image_path);
+ // Two image files and two attribution files.
+ EXPECT_EQ(file_paths.size(), 4u);
+ for (auto& path : file_paths) {
+ // No sub directories.
+ EXPECT_FALSE(base::DirectoryExists(path));
}
-
- // Clean up.
- base::DeletePathRecursively(ambient_image_path);
}
// Test that image is save and will be deleted when stopping ambient mode.
TEST_F(AmbientPhotoControllerTest, ShouldNotDeleteImagesOnDisk) {
- base::FilePath home_dir;
- base::PathService::Get(base::DIR_HOME, &home_dir);
-
- base::FilePath ambient_image_path =
- home_dir.Append(FILE_PATH_LITERAL(kAmbientModeDirectoryName));
-
- // Clean up.
- base::DeletePathRecursively(ambient_image_path);
+ 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.
@@ -169,37 +186,22 @@
image = photo_controller()->ambient_backend_model()->GetNextImage();
EXPECT_TRUE(image.IsNull());
- {
- // Count files and directories in root_path. There should only be one file
- // that was just created to save image files for this ambient mode session.
- base::FileEnumerator files(
- ambient_image_path, /*recursive=*/false,
- base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
- int count = 0;
- for (base::FilePath current = files.Next(); !current.empty();
- current = files.Next()) {
- EXPECT_FALSE(files.GetInfo().IsDirectory());
- count++;
- }
-
- // Two image files and two attribution files.
- EXPECT_EQ(count, 4);
+ // 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.
+ 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);
+ for (auto& path : file_paths) {
+ // No sub directories.
+ EXPECT_FALSE(base::DirectoryExists(path));
}
-
- // Clean up.
- base::DeletePathRecursively(ambient_image_path);
}
// Test that image is read from disk when no more topics.
TEST_F(AmbientPhotoControllerTest, ShouldReadCacheWhenNoMoreTopics) {
- base::FilePath home_dir;
- base::PathService::Get(base::DIR_HOME, &home_dir);
-
- base::FilePath ambient_image_path =
- home_dir.Append(FILE_PATH_LITERAL(kAmbientModeDirectoryName));
-
- // Clean up.
- base::DeletePathRecursively(ambient_image_path);
+ base::FilePath ambient_image_path = GetCacheDir();
FetchImage();
FastForwardToNextImage();
@@ -226,14 +228,7 @@
// Test that will try 100 times to read image from disk when no more topics.
TEST_F(AmbientPhotoControllerTest,
ShouldTry100TimesToReadCacheWhenNoMoreTopics) {
- base::FilePath home_dir;
- base::PathService::Get(base::DIR_HOME, &home_dir);
-
- base::FilePath ambient_image_path =
- home_dir.Append(FILE_PATH_LITERAL(kAmbientModeDirectoryName));
-
- // Clean up.
- base::DeletePathRecursively(ambient_image_path);
+ base::FilePath ambient_image_path = GetCacheDir();
FetchImage();
FastForwardToNextImage();
@@ -253,21 +248,11 @@
FastForwardToNextImage();
image = photo_controller()->ambient_backend_model()->GetNextImage();
EXPECT_FALSE(image.IsNull());
-
- // Clean up.
- base::DeletePathRecursively(ambient_image_path);
}
// Test that image is read from disk when image downloading failed.
TEST_F(AmbientPhotoControllerTest, ShouldReadCacheWhenImageDownloadingFailed) {
- base::FilePath home_dir;
- base::PathService::Get(base::DIR_HOME, &home_dir);
-
- base::FilePath ambient_image_path =
- home_dir.Append(FILE_PATH_LITERAL(kAmbientModeDirectoryName));
-
- // Clean up.
- base::DeletePathRecursively(ambient_image_path);
+ base::FilePath ambient_image_path = GetCacheDir();
SetUrlLoaderData(std::make_unique<std::string>());
FetchTopics();
@@ -290,23 +275,13 @@
task_environment()->FastForwardBy(0.2 * kTopicFetchInterval);
image = photo_controller()->ambient_backend_model()->GetNextImage();
EXPECT_FALSE(image.IsNull());
-
- // Clean up.
- base::DeletePathRecursively(ambient_image_path);
}
// Test that image is read from disk when image decoding failed.
TEST_F(AmbientPhotoControllerTest, ShouldReadCacheWhenImageDecodingFailed) {
- base::FilePath home_dir;
- base::PathService::Get(base::DIR_HOME, &home_dir);
+ base::FilePath ambient_image_path = GetCacheDir();
- base::FilePath ambient_image_path =
- home_dir.Append(FILE_PATH_LITERAL(kAmbientModeDirectoryName));
-
- // Clean up.
- base::DeletePathRecursively(ambient_image_path);
-
- SeteImageDecoderImage(gfx::ImageSkia());
+ SetImageDecoderImage(gfx::ImageSkia());
FetchTopics();
// Forward a little bit time. FetchTopics() will succeed.
// Downloading succeed and save the data to disk.
@@ -314,21 +289,11 @@
task_environment()->FastForwardBy(0.2 * kTopicFetchInterval);
auto image = photo_controller()->ambient_backend_model()->GetNextImage();
EXPECT_FALSE(image.IsNull());
-
- // Clean up.
- base::DeletePathRecursively(ambient_image_path);
}
// Test that image will refresh when have more topics.
TEST_F(AmbientPhotoControllerTest, ShouldResumWhenHaveMoreTopics) {
- base::FilePath home_dir;
- base::PathService::Get(base::DIR_HOME, &home_dir);
-
- base::FilePath ambient_image_path =
- home_dir.Append(FILE_PATH_LITERAL(kAmbientModeDirectoryName));
-
- // Clean up.
- base::DeletePathRecursively(ambient_image_path);
+ base::FilePath ambient_image_path = GetCacheDir();
FetchImage();
FastForwardToNextImage();
@@ -341,9 +306,95 @@
task_environment()->FastForwardBy(0.2 * kTopicFetchInterval);
image = photo_controller()->ambient_backend_model()->GetNextImage();
EXPECT_FALSE(image.IsNull());
+}
- // Clean up.
- base::DeletePathRecursively(ambient_image_path);
+TEST_F(AmbientPhotoControllerTest, ShouldDownloadBackupImagesWhenScheduled) {
+ base::FilePath backup_image_path = GetBackupCacheDir();
+
+ std::string expected_data = "backup data";
+ SetUrlLoaderData(std::make_unique<std::string>(expected_data));
+
+ photo_controller()->ScheduleFetchBackupImages();
+
+ EXPECT_TRUE(
+ photo_controller()->backup_photo_refresh_timer_for_testing().IsRunning());
+
+ // TImer is running but download has not started yet.
+ EXPECT_FALSE(base::DirectoryExists(GetBackupCacheDir()));
+ task_environment()->FastForwardBy(kBackupPhotoRefreshDelay);
+
+ // Timer should have stopped.
+ EXPECT_FALSE(
+ photo_controller()->backup_photo_refresh_timer_for_testing().IsRunning());
+
+ // Download has triggered and backup cache directory is created.
+ EXPECT_TRUE(base::DirectoryExists(backup_image_path));
+
+ // Should be two files in backup cache directory.
+ auto paths = GetFilePathsInDir(backup_image_path);
+ std::sort(paths.begin(), paths.end());
+ EXPECT_EQ(paths.size(), 2u);
+ EXPECT_EQ(paths[0].BaseName().value(), "0.img");
+ EXPECT_EQ(paths[1].BaseName().value(), "1.img");
+ for (const auto& path : paths) {
+ std::string data;
+ base::ReadFileToString(path, &data);
+ EXPECT_EQ(data, expected_data);
+ }
+}
+
+TEST_F(AmbientPhotoControllerTest, ShouldResetTimerWhenBackupImagesFail) {
+ photo_controller()->ScheduleFetchBackupImages();
+
+ EXPECT_TRUE(
+ photo_controller()->backup_photo_refresh_timer_for_testing().IsRunning());
+
+ // Simulate an error in DownloadToFile.
+ SetUrlLoaderData(nullptr);
+ task_environment()->FastForwardBy(kBackupPhotoRefreshDelay);
+
+ // Directory should have been created, but with no files in it.
+ EXPECT_TRUE(base::DirectoryExists(GetBackupCacheDir()));
+
+ auto paths = GetFilePathsInDir(GetBackupCacheDir());
+ EXPECT_EQ(paths.size(), 0u);
+
+ // Timer should have restarted.
+ EXPECT_TRUE(
+ photo_controller()->backup_photo_refresh_timer_for_testing().IsRunning());
+}
+
+TEST_F(AmbientPhotoControllerTest,
+ ShouldStartDownloadBackupImagesOnAmbientModeStart) {
+ photo_controller()->ScheduleFetchBackupImages();
+
+ EXPECT_TRUE(
+ photo_controller()->backup_photo_refresh_timer_for_testing().IsRunning());
+
+ SetUrlLoaderData(std::make_unique<std::string>("image data"));
+
+ photo_controller()->StartScreenUpdate();
+
+ // Download should have started immediately.
+ EXPECT_FALSE(
+ photo_controller()->backup_photo_refresh_timer_for_testing().IsRunning());
+
+ task_environment()->RunUntilIdle();
+
+ // Download has triggered and backup cache directory is created.
+ EXPECT_TRUE(base::DirectoryExists(GetBackupCacheDir()));
+
+ // Should be two files in backup cache directory.
+ auto paths = GetFilePathsInDir(GetBackupCacheDir());
+ std::sort(paths.begin(), paths.end());
+ EXPECT_EQ(paths.size(), 2u);
+ EXPECT_EQ(paths[0].BaseName().value(), "0.img");
+ EXPECT_EQ(paths[1].BaseName().value(), "1.img");
+ for (const auto& path : paths) {
+ std::string data;
+ base::ReadFileToString(path, &data);
+ EXPECT_EQ(data, "image data");
+ }
}
TEST_F(AmbientPhotoControllerTest, ShouldStartToRefreshWeather) {
diff --git a/ash/ambient/backdrop/ambient_backend_controller_impl.cc b/ash/ambient/backdrop/ambient_backend_controller_impl.cc
index d7fc74a..b9e343d 100644
--- a/ash/ambient/backdrop/ambient_backend_controller_impl.cc
+++ b/ash/ambient/backdrop/ambient_backend_controller_impl.cc
@@ -4,6 +4,7 @@
#include "ash/ambient/backdrop/ambient_backend_controller_impl.h"
+#include <array>
#include <string>
#include <utility>
#include <vector>
@@ -23,6 +24,7 @@
#include "base/guid.h"
#include "base/optional.h"
#include "base/time/time.h"
+#include "chromeos/assistant/internal/ambient/backdrop_client_config.h"
#include "chromeos/assistant/internal/proto/google3/backdrop/backdrop.pb.h"
#include "chromeos/constants/chromeos_features.h"
#include "components/prefs/pref_service.h"
@@ -455,6 +457,11 @@
std::move(backdrop_url_loader)));
}
+const std::array<const char*, 2>&
+AmbientBackendControllerImpl::GetBackupPhotoUrls() const {
+ return chromeos::ambient::kBackupPhotoUrls;
+}
+
void AmbientBackendControllerImpl::FetchScreenUpdateInfoInternal(
int num_topics,
OnScreenUpdateInfoFetchedCallback callback,
diff --git a/ash/ambient/backdrop/ambient_backend_controller_impl.h b/ash/ambient/backdrop/ambient_backend_controller_impl.h
index be25bd60..8fd9d3b3 100644
--- a/ash/ambient/backdrop/ambient_backend_controller_impl.h
+++ b/ash/ambient/backdrop/ambient_backend_controller_impl.h
@@ -5,6 +5,7 @@
#ifndef ASH_AMBIENT_BACKDROP_AMBIENT_BACKEND_CONTROLLER_IMPL_H_
#define ASH_AMBIENT_BACKDROP_AMBIENT_BACKEND_CONTROLLER_IMPL_H_
+#include <array>
#include <memory>
#include <string>
#include <utility>
@@ -48,6 +49,7 @@
OnSettingsAndAlbumsFetchedCallback callback) override;
void SetPhotoRefreshInterval(base::TimeDelta interval) override;
void FetchWeather(FetchWeatherCallback callback) override;
+ const std::array<const char*, 2>& GetBackupPhotoUrls() const override;
private:
using BackdropClientConfig = chromeos::ambient::BackdropClientConfig;
diff --git a/ash/ambient/test/ambient_ash_test_base.cc b/ash/ambient/test/ambient_ash_test_base.cc
index 90b08f4..9b86a31 100644
--- a/ash/ambient/test/ambient_ash_test_base.cc
+++ b/ash/ambient/test/ambient_ash_test_base.cc
@@ -21,9 +21,11 @@
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "base/callback.h"
+#include "base/files/file_util.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/sequenced_task_runner.h"
+#include "base/threading/scoped_blocking_call.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/time/time.h"
#include "chromeos/constants/chromeos_features.h"
@@ -48,18 +50,42 @@
void Download(
const std::string& url,
network::SimpleURLLoader::BodyAsStringCallback callback) override {
- std::string data = data_ ? *data_ : "test";
// Pretend to respond asynchronously.
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(std::move(callback),
- std::make_unique<std::string>(data)),
+ std::make_unique<std::string>(data_ ? *data_ : "test")),
base::TimeDelta::FromMilliseconds(1));
}
+ void DownloadToFile(
+ const std::string& url,
+ network::SimpleURLLoader::DownloadToFileCompleteCallback callback,
+ const base::FilePath& file_path) override {
+ if (!data_) {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), base::FilePath()));
+ return;
+ }
+
+ if (!WriteFile(file_path, *data_)) {
+ LOG(WARNING) << "error writing file to file_path: " << file_path;
+
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), base::FilePath()));
+ return;
+ }
+
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), file_path));
+ }
void SetData(std::unique_ptr<std::string> data) { data_ = std::move(data); }
private:
+ bool WriteFile(const base::FilePath& file_path, const std::string& data) {
+ base::ScopedBlockingCall blocking(FROM_HERE, base::BlockingType::MAY_BLOCK);
+ return base::WriteFile(file_path, data);
+ }
// If not null, will return this data.
std::unique_ptr<std::string> data_;
};
@@ -352,6 +378,10 @@
photo_controller()->FetchImageForTesting();
}
+void AmbientAshTestBase::FetchBackupImages() {
+ photo_controller()->FetchBackupImagesForTesting();
+}
+
void AmbientAshTestBase::SetUrlLoaderData(std::unique_ptr<std::string> data) {
auto* url_loader_ = static_cast<TestAmbientURLLoaderImpl*>(
photo_controller()->get_url_loader_for_testing());
@@ -359,7 +389,7 @@
url_loader_->SetData(std::move(data));
}
-void AmbientAshTestBase::SeteImageDecoderImage(const gfx::ImageSkia& image) {
+void AmbientAshTestBase::SetImageDecoderImage(const gfx::ImageSkia& image) {
auto* image_decoder = static_cast<TestAmbientImageDecoderImpl*>(
photo_controller()->get_image_decoder_for_testing());
diff --git a/ash/ambient/test/ambient_ash_test_base.h b/ash/ambient/test/ambient_ash_test_base.h
index a863d78..35a8e7b 100644
--- a/ash/ambient/test/ambient_ash_test_base.h
+++ b/ash/ambient/test/ambient_ash_test_base.h
@@ -141,9 +141,11 @@
void FetchImage();
+ void FetchBackupImages();
+
void SetUrlLoaderData(std::unique_ptr<std::string> data);
- void SeteImageDecoderImage(const gfx::ImageSkia& image);
+ void SetImageDecoderImage(const gfx::ImageSkia& image);
private:
base::test::ScopedFeatureList scoped_feature_list_;
diff --git a/ash/public/cpp/ambient/ambient_backend_controller.h b/ash/public/cpp/ambient/ambient_backend_controller.h
index b7b7132..77f5931 100644
--- a/ash/public/cpp/ambient/ambient_backend_controller.h
+++ b/ash/public/cpp/ambient/ambient_backend_controller.h
@@ -5,6 +5,7 @@
#ifndef ASH_PUBLIC_CPP_AMBIENT_AMBIENT_BACKEND_CONTROLLER_H_
#define ASH_PUBLIC_CPP_AMBIENT_AMBIENT_BACKEND_CONTROLLER_H_
+#include <array>
#include <string>
#include <vector>
@@ -156,6 +157,10 @@
// Fetch the weather information.
virtual void FetchWeather(FetchWeatherCallback) = 0;
+
+ // Get stock photo urls to cache in advance in case Ambient mode is started
+ // without internet access.
+ virtual const std::array<const char*, 2>& GetBackupPhotoUrls() const = 0;
};
} // namespace ash
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 8a9a1eb..f54c394 100644
--- a/ash/public/cpp/ambient/fake_ambient_backend_controller_impl.cc
+++ b/ash/public/cpp/ambient/fake_ambient_backend_controller_impl.cc
@@ -4,6 +4,7 @@
#include "ash/public/cpp/ambient/fake_ambient_backend_controller_impl.h"
+#include <array>
#include <utility>
#include "ash/public/cpp/ambient/ambient_backend_controller.h"
@@ -26,6 +27,9 @@
constexpr char kFakeDetails[] = "fake-photo-attribution";
+constexpr std::array<const char*, 2> kFakeBackupPhotoUrls = {kFakeUrl,
+ kFakeUrl};
+
AmbientSettings CreateFakeSettings() {
AmbientSettings settings;
settings.topic_source = kTopicSource;
@@ -154,6 +158,11 @@
std::move(callback).Run(weather_info_);
}
+const std::array<const char*, 2>&
+FakeAmbientBackendControllerImpl::GetBackupPhotoUrls() const {
+ return kFakeBackupPhotoUrls;
+}
+
void FakeAmbientBackendControllerImpl::ReplyFetchSettingsAndAlbums(
bool success) {
if (!pending_fetch_settings_albums_callback_)
diff --git a/ash/public/cpp/ambient/fake_ambient_backend_controller_impl.h b/ash/public/cpp/ambient/fake_ambient_backend_controller_impl.h
index c217a9f..39ffabf9 100644
--- a/ash/public/cpp/ambient/fake_ambient_backend_controller_impl.h
+++ b/ash/public/cpp/ambient/fake_ambient_backend_controller_impl.h
@@ -5,6 +5,8 @@
#ifndef ASH_PUBLIC_CPP_AMBIENT_FAKE_AMBIENT_BACKEND_CONTROLLER_IMPL_H_
#define ASH_PUBLIC_CPP_AMBIENT_FAKE_AMBIENT_BACKEND_CONTROLLER_IMPL_H_
+#include <array>
+
#include "ash/public/cpp/ambient/ambient_backend_controller.h"
#include "ash/public/cpp/ash_public_export.h"
#include "base/callback.h"
@@ -40,6 +42,7 @@
OnSettingsAndAlbumsFetchedCallback callback) override;
void SetPhotoRefreshInterval(base::TimeDelta interval) override;
void FetchWeather(FetchWeatherCallback callback) override;
+ const std::array<const char*, 2>& GetBackupPhotoUrls() const override;
// Simulate to reply the request of FetchSettingsAndAlbums().
// If |success| is true, will return fake data.
diff --git a/chrome/browser/ui/ash/ambient/ambient_client_impl_unittest.cc b/chrome/browser/ui/ash/ambient/ambient_client_impl_unittest.cc
index 348ba85..7cf4213 100644
--- a/chrome/browser/ui/ash/ambient/ambient_client_impl_unittest.cc
+++ b/chrome/browser/ui/ash/ambient/ambient_client_impl_unittest.cc
@@ -32,7 +32,6 @@
chromeos::features::kAmbientModeFeature);
// Needed by ash.
ambient_client_ = std::make_unique<AmbientClientImpl>();
- AshTestBase::SetUp();
ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
profile_manager_ = std::make_unique<TestingProfileManager>(
@@ -48,6 +47,8 @@
std::make_unique<IdentityTestEnvironmentProfileAdaptor>(profile_);
user_manager_enabler_ = std::make_unique<user_manager::ScopedUserManager>(
std::make_unique<chromeos::FakeChromeUserManager>());
+
+ AshTestBase::SetUp();
}
void TearDown() override {