Commit 2c341607 authored by Jeffrey Young's avatar Jeffrey Young Committed by Commit Bot

Reland "ambient: cache two backup photos"

This is a reland of a81c2688

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 <cowmoo@chromium.org>
> Reviewed-by: Xiaohui Chen <xiaohuic@chromium.org>
> Reviewed-by: Tao Wu <wutao@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#813904}

Bug: b:167332126
Bug: b:170312454
Change-Id: If5306f627e1a6dfb5e4eb37842a470d17968ad4e
Cq-Include-Trybots: luci.chrome.try:linux-chromeos-chrome
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2458894
Commit-Queue: Jeffrey Young <cowmoo@chromium.org>
Reviewed-by: default avatarTao Wu <wutao@chromium.org>
Cr-Commit-Position: refs/heads/master@{#814968}
parent b10bf33a
......@@ -24,6 +24,10 @@ constexpr base::TimeDelta kTopicFetchInterval =
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 @@ constexpr char kPhotoDetailsFileExt[] = ".txt";
// 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);
......
......@@ -323,6 +323,11 @@ void AmbientController::OnLockStateChanged(bool locked) {
}
}
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.
......
......@@ -63,6 +63,7 @@ class ASH_EXPORT AmbientController
// SessionObserver:
void OnLockStateChanged(bool locked) override;
void OnFirstSessionStarted() override;
// PowerStatus::Observer:
void OnPowerStatusChanged() override;
......
......@@ -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 @@ void DownloadImageFromUrl(const std::string& url, DownloadCallback callback) {
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::TaskTraits GetTaskTraits() {
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 @@ void WriteFile(const base::FilePath& path, const std::string& data) {
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 @@ class AmbientURLLoaderImpl : public AmbientURLLoader {
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 @@ class AmbientURLLoaderImpl : public AmbientURLLoader {
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,16 +238,40 @@ class AmbientURLLoaderImpl : public AmbientURLLoader {
return;
}
int response_code = -1;
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) {
response_code = simple_loader->ResponseInfo()->headers->response_code();
return simple_loader->ResponseInfo()->headers->response_code();
} else {
return -1;
}
LOG(ERROR) << "Downloading Backdrop proto failed with error code: "
<< response_code << " with network error"
<< simple_loader->NetError();
std::move(callback).Run(std::make_unique<std::string>());
}
base::WeakPtrFactory<AmbientURLLoaderImpl> weak_factory_{this};
......@@ -234,6 +313,12 @@ void AmbientPhotoController::StartScreenUpdate() {
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 @@ void AmbientPhotoController::StopScreenUpdate() {
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::FetchWeather() {
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 @@ void AmbientPhotoController::ScheduleRefreshImage() {
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.
......@@ -385,22 +518,54 @@ void AmbientPhotoController::FetchPhotoRawData() {
}
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) {
LOG(WARNING) << "Failed to read image from cache";
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;
}
......@@ -412,22 +577,18 @@ void AmbientPhotoController::TryReadPhotoRawData() {
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();
}
......@@ -435,7 +596,7 @@ void AmbientPhotoController::TryReadPhotoRawData() {
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)));
}
......@@ -486,8 +647,8 @@ void AmbientPhotoController::OnAllPhotoRawDataAvailable(bool from_downloading) {
[](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);
}
},
......@@ -539,6 +700,8 @@ void AmbientPhotoController::OnAllPhotoDecoded(bool from_downloading) {
}
retries_to_read_from_cache_ = kMaxNumberOfCachedImages;
backup_retries_to_read_from_cache_ = GetBackupPhotoUrls().size();
if (from_downloading)
resume_fetch_image_backoff_.InformOfRequest(/*succeeded=*/true);
......@@ -604,4 +767,8 @@ void AmbientPhotoController::FetchImageForTesting() {
FetchPhotoRawData();
}
void AmbientPhotoController::FetchBackupImagesForTesting() {
PrepareFetchBackupImages();
}
} // namespace ash
......@@ -44,6 +44,11 @@ class ASH_EXPORT AmbientURLLoader {
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 @@ class ASH_EXPORT AmbientPhotoController : public AmbientBackendModelObserver {
void StartScreenUpdate();
void StopScreenUpdate();
void ScheduleFetchBackupImages();
AmbientBackendModel* ambient_backend_model() {
return &ambient_backend_model_;
}
......@@ -95,6 +102,10 @@ class ASH_EXPORT AmbientPhotoController : public AmbientBackendModelObserver {
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 @@ class ASH_EXPORT AmbientPhotoController : public AmbientBackendModelObserver {
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 @@ class ASH_EXPORT AmbientPhotoController : public AmbientBackendModelObserver {
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 @@ class ASH_EXPORT AmbientPhotoController : public AmbientBackendModelObserver {
// 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 @@ class ASH_EXPORT AmbientPhotoController : public AmbientBackendModelObserver {
// 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_;
......
......@@ -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_F(AmbientPhotoControllerTest, ShouldUpdatePhotoPeriodically) {
// 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 @@ TEST_F(AmbientPhotoControllerTest, ShouldNotDeleteImagesOnDisk) {
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_F(AmbientPhotoControllerTest, ShouldReadCacheWhenNoMoreTopics) {
// 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 @@ TEST_F(AmbientPhotoControllerTest,
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 @@ TEST_F(AmbientPhotoControllerTest, ShouldReadCacheWhenImageDownloadingFailed) {
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 =
home_dir.Append(FILE_PATH_LITERAL(kAmbientModeDirectoryName));
base::FilePath ambient_image_path = GetCacheDir();
// 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 @@ TEST_F(AmbientPhotoControllerTest, ShouldReadCacheWhenImageDecodingFailed) {
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 @@ TEST_F(AmbientPhotoControllerTest, ShouldResumWhenHaveMoreTopics) {
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) {
......
......@@ -4,6 +4,7 @@
#include "ash/ambient/backdrop/ambient_backend_controller_impl.h"
#include <array>
#include <string>
#include <utility>
#include <vector>
......@@ -24,6 +25,7 @@
#include "base/logging.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"
......@@ -459,6 +461,11 @@ void AmbientBackendControllerImpl::FetchWeather(FetchWeatherCallback callback) {
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,
......
......@@ -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 @@ class AmbientBackendControllerImpl : public AmbientBackendController {
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;
......
......@@ -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 @@ class TestAmbientURLLoaderImpl : public AmbientURLLoader {
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 @@ void AmbientAshTestBase::FetchImage() {
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 @@ void AmbientAshTestBase::SetUrlLoaderData(std::unique_ptr<std::string> data) {
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());
......
......@@ -141,9 +141,11 @@ class AmbientAshTestBase : public AshTestBase {
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_;
......
......@@ -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 @@ class ASH_PUBLIC_EXPORT AmbientBackendController {
// 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
......
......@@ -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 kFakeUrl[] = "chrome://ambient";
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 @@ void FakeAmbientBackendControllerImpl::FetchWeather(
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_)
......
......@@ -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 @@ class ASH_PUBLIC_EXPORT FakeAmbientBackendControllerImpl
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.
......
......@@ -32,7 +32,6 @@ class AmbientClientImplTest : public ChromeAshTestBase {
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 @@ class AmbientClientImplTest : public ChromeAshTestBase {
std::make_unique<IdentityTestEnvironmentProfileAdaptor>(profile_);
user_manager_enabler_ = std::make_unique<user_manager::ScopedUserManager>(
std::make_unique<chromeos::FakeChromeUserManager>());
AshTestBase::SetUp();
}
void TearDown() override {
......
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