Commit 0a975ef8 authored by wutao's avatar wutao Committed by Commit Bot

ambient: Cache downloaded photo on disk

Previously we download one photo and display it and then discard the
photo. However when the access token expires, we cannot get photo link
info anymore. This patch saves previous downloaded photos to disk and
then cycle through those photos for a longer time span.

Bug: b/156919253
Test: new unittests
Change-Id: I6a0bd0b3f69e1e6c3d950c158960071dfce122c6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2207755
Commit-Queue: Tao Wu <wutao@chromium.org>
Reviewed-by: default avatarXiaohui Chen <xiaohuic@chromium.org>
Cr-Commit-Position: refs/heads/master@{#784498}
parent b15cb5fd
......@@ -2604,6 +2604,7 @@ static_library("test_support") {
"//ui/events:test_support",
"//ui/events/devices",
"//ui/events/devices:test_support",
"//ui/gfx:test_support",
"//ui/gl",
"//ui/gl:test_support",
"//ui/message_center",
......
......@@ -19,6 +19,9 @@ constexpr base::TimeDelta kAnimationDuration =
constexpr base::TimeDelta kPhotoRefreshInterval =
base::TimeDelta::FromSeconds(5);
// Directory name of ambient mode.
constexpr char kAmbientModeDirectoryName[] = "ambient-mode";
} // namespace ash
#endif // ASH_AMBIENT_AMBIENT_CONSTANTS_H_
......@@ -36,6 +36,9 @@ TEST_F(AmbientControllerTest, ShowAmbientScreenUponLock) {
EXPECT_EQ(AmbientUiModel::Get()->ui_visibility(),
AmbientUiVisibility::kShown);
EXPECT_TRUE(ambient_controller()->IsShown());
// Clean up.
CloseAmbientScreen();
}
TEST_F(AmbientControllerTest, HideAmbientScreen) {
......@@ -51,6 +54,9 @@ TEST_F(AmbientControllerTest, HideAmbientScreen) {
EXPECT_EQ(AmbientUiModel::Get()->ui_visibility(),
AmbientUiVisibility::kHidden);
EXPECT_FALSE(container_view()->GetWidget()->IsVisible());
// Clean up.
CloseAmbientScreen();
}
TEST_F(AmbientControllerTest, CloseAmbientScreenUponUnlock) {
......@@ -106,6 +112,9 @@ TEST_F(AmbientControllerTest, ShouldReturnCachedAccessToken) {
}));
EXPECT_FALSE(IsAccessTokenRequestPending());
run_loop.Run();
// Clean up.
CloseAmbientScreen();
}
TEST_F(AmbientControllerTest, ShouldRefreshAccessTokenAfterFailure) {
......@@ -122,6 +131,9 @@ TEST_F(AmbientControllerTest, ShouldRefreshAccessTokenAfterFailure) {
// the returned token would expire again.
task_environment()->FastForwardBy(kDefaultTokenExpirationDelay / 2);
EXPECT_TRUE(IsAccessTokenRequestPending());
// Clean up.
CloseAmbientScreen();
}
TEST_F(AmbientControllerTest,
......@@ -224,6 +236,9 @@ TEST_F(AmbientControllerTest, ShouldDismissContainerViewWhenKeyPressed) {
GetEventGenerator()->PressKey(ui::VKEY_SPACE, /*flags=*/0);
EXPECT_FALSE(container_view()->GetWidget()->IsVisible());
// Clean up.
CloseAmbientScreen();
}
} // namespace ash
This diff is collapsed.
......@@ -5,6 +5,9 @@
#ifndef ASH_AMBIENT_AMBIENT_PHOTO_CONTROLLER_H_
#define ASH_AMBIENT_AMBIENT_PHOTO_CONTROLLER_H_
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "ash/ambient/model/ambient_backend_model.h"
......@@ -13,10 +16,12 @@
#include "ash/public/cpp/ambient/ambient_backend_controller.h"
#include "base/callback_forward.h"
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "base/scoped_observer.h"
#include "base/timer/timer.h"
#include "services/network/public/cpp/simple_url_loader.h"
namespace gfx {
class ImageSkia;
......@@ -24,6 +29,36 @@ class ImageSkia;
namespace ash {
// A wrapper class of SimpleURLLoader to download the photo raw data. In the
// test, this will be used to provide fake data.
class ASH_EXPORT AmbientURLLoader {
public:
AmbientURLLoader() = default;
AmbientURLLoader(const AmbientURLLoader&) = delete;
AmbientURLLoader& operator=(const AmbientURLLoader&) = delete;
virtual ~AmbientURLLoader() = default;
// Download data from the given |url|.
virtual void Download(
const std::string& url,
network::SimpleURLLoader::BodyAsStringCallback callback) = 0;
};
// A wrapper class of |data_decoder| to decode the photo raw data. In the test,
// this will be used to provide fake data.
class ASH_EXPORT AmbientImageDecoder {
public:
AmbientImageDecoder() = default;
AmbientImageDecoder(const AmbientImageDecoder&) = delete;
AmbientImageDecoder& operator=(const AmbientImageDecoder&) = delete;
virtual ~AmbientImageDecoder() = default;
// Decode |encoded_bytes| to ImageSkia.
virtual void Decode(
const std::vector<uint8_t>& encoded_bytes,
base::OnceCallback<void(const gfx::ImageSkia&)> callback) = 0;
};
// Class to handle photos in ambient mode.
class ASH_EXPORT AmbientPhotoController : public AmbientBackendModelObserver {
public:
......@@ -75,20 +110,42 @@ class ASH_EXPORT AmbientPhotoController : public AmbientBackendModelObserver {
// Return a topic to download the image.
const AmbientModeTopic& GetNextTopic();
void GetNextImage();
void OnScreenUpdateInfoFetched(const ash::ScreenUpdate& screen_update);
// Try to read photo raw data from disk.
void TryReadPhotoRawData();
// If photo raw data is read successfully, call OnPhotoRawDataAvailable() to
// decode data. Otherwise, download the raw data and save to disk.
void OnPhotoRawDataRead(const std::string& image_url,
std::unique_ptr<std::string> data);
void OnPhotoRawDataAvailable(const std::string& image_url,
bool need_to_save,
std::unique_ptr<std::string> response_body);
void DecodePhotoRawData(std::unique_ptr<std::string> data);
void OnPhotoDecoded(const gfx::ImageSkia& image);
void StartDownloadingWeatherConditionIcon(
const ash::ScreenUpdate& screen_update);
void OnPhotoDownloaded(const gfx::ImageSkia& image);
// Invoked upon completion of the weather icon download, |icon| can be a null
// image if the download attempt from the url failed.
void OnWeatherConditionIconDownloaded(base::Optional<float> temp_f,
const gfx::ImageSkia& icon);
void set_url_loader_for_testing(
std::unique_ptr<AmbientURLLoader> url_loader) {
url_loader_ = std::move(url_loader);
}
void set_image_decoder_for_testing(
std::unique_ptr<AmbientImageDecoder> image_decoder) {
image_decoder_ = std::move(image_decoder);
}
AmbientBackendModel ambient_backend_model_;
// The timer to refresh photos.
......@@ -101,7 +158,15 @@ class ASH_EXPORT AmbientPhotoController : public AmbientBackendModelObserver {
int topics_batch_fetched_ = 0;
ScopedObserver<AmbientBackendModel, AmbientBackendModelObserver>
ambient_backedn_model_observer_{this};
ambient_backend_model_observer_{this};
base::FilePath root_path_;
std::unique_ptr<AmbientURLLoader> url_loader_;
std::unique_ptr<AmbientImageDecoder> image_decoder_;
scoped_refptr<base::SequencedTaskRunner> task_runner_;
base::WeakPtrFactory<AmbientPhotoController> weak_factory_{this};
......
......@@ -13,9 +13,15 @@
#include "ash/public/cpp/ambient/ambient_backend_controller.h"
#include "ash/shell.h"
#include "base/barrier_closure.h"
#include "base/base_paths.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/hash/sha1.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/system/sys_info.h"
#include "base/test/bind_test_util.h"
#include "base/test/task_environment.h"
#include "base/timer/timer.h"
......@@ -52,7 +58,7 @@ TEST_F(AmbientPhotoControllerTest, ShouldStartToDownloadImages) {
// Start to refresh images.
photo_controller()->StartScreenUpdate();
base::RunLoop().RunUntilIdle();
task_environment()->FastForwardBy(1.2 * kPhotoRefreshInterval);
image = photo_controller()->ambient_backend_model()->GetNextImage();
EXPECT_FALSE(image.isNull());
......@@ -70,7 +76,7 @@ TEST_F(AmbientPhotoControllerTest, ShouldUpdatePhotoPeriodically) {
// Start to refresh images.
photo_controller()->StartScreenUpdate();
base::RunLoop().RunUntilIdle();
task_environment()->FastForwardBy(1.2 * kPhotoRefreshInterval);
image1 = photo_controller()->ambient_backend_model()->GetNextImage();
EXPECT_FALSE(image1.isNull());
EXPECT_TRUE(image2.isNull());
......@@ -88,6 +94,36 @@ TEST_F(AmbientPhotoControllerTest, ShouldUpdatePhotoPeriodically) {
EXPECT_FALSE(image3.isNull());
EXPECT_FALSE(image1.BackedBySameObjectAs(image3));
EXPECT_FALSE(image2.BackedBySameObjectAs(image3));
// Stop to refresh images.
photo_controller()->StopScreenUpdate();
}
// Test that image is saved and deleted when starting/stopping screen update.
TEST_F(AmbientPhotoControllerTest, ShouldSaveAndDeleteImagesOnDisk) {
base::FilePath home_dir;
base::PathService::Get(base::DIR_HOME, &home_dir);
// Start to refresh images.
photo_controller()->StartScreenUpdate();
base::FilePath root_path =
home_dir.Append(FILE_PATH_LITERAL(kAmbientModeDirectoryName));
EXPECT_TRUE(!base::PathExists(root_path) ||
base::IsDirectoryEmpty(root_path));
task_environment()->FastForwardBy(1.2 * kPhotoRefreshInterval);
auto image = photo_controller()->ambient_backend_model()->GetNextImage();
EXPECT_FALSE(image.isNull());
EXPECT_TRUE(base::PathExists(root_path));
EXPECT_FALSE(base::IsDirectoryEmpty(root_path));
// Stop to refresh images.
photo_controller()->StopScreenUpdate();
task_environment()->FastForwardBy(1.2 * kPhotoRefreshInterval);
image = photo_controller()->ambient_backend_model()->GetNextImage();
EXPECT_TRUE(image.isNull());
EXPECT_TRUE(base::PathExists(root_path));
EXPECT_TRUE(base::IsDirectoryEmpty(root_path));
}
} // namespace ash
......@@ -5,18 +5,58 @@
#include "ash/ambient/test/ambient_ash_test_base.h"
#include <memory>
#include <utility>
#include <vector>
#include "ash/ambient/ambient_photo_controller.h"
#include "ash/ambient/fake_ambient_backend_controller_impl.h"
#include "ash/ambient/ui/ambient_container_view.h"
#include "ash/ambient/ui/photo_view.h"
#include "ash/shell.h"
#include "base/callback.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "chromeos/constants/chromeos_features.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_unittest_util.h"
namespace ash {
class TestAmbientURLLoaderImpl : public AmbientURLLoader {
public:
TestAmbientURLLoaderImpl() = default;
~TestAmbientURLLoaderImpl() override = default;
// AmbientURLLoader:
void Download(
const std::string& url,
network::SimpleURLLoader::BodyAsStringCallback callback) override {
auto data = std::make_unique<std::string>();
*data = "test";
// Pretend to respond asynchronously.
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(data)));
}
};
class TestAmbientImageDecoderImpl : public AmbientImageDecoder {
public:
TestAmbientImageDecoderImpl() = default;
~TestAmbientImageDecoderImpl() override = default;
// AmbientImageDecoder:
void Decode(
const std::vector<uint8_t>& encoded_bytes,
base::OnceCallback<void(const gfx::ImageSkia&)> callback) override {
// Pretend to respond asynchronously.
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), gfx::test::CreateImageSkia(
/*width=*/10, /*height=*/10)));
}
};
AmbientAshTestBase::AmbientAshTestBase()
: AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
......@@ -32,10 +72,13 @@ void AmbientAshTestBase::SetUp() {
// Need to reset first and then assign the TestPhotoClient because can only
// have one instance of AmbientBackendController.
Shell::Get()->ambient_controller()->set_backend_controller_for_testing(
nullptr);
Shell::Get()->ambient_controller()->set_backend_controller_for_testing(
ambient_controller()->set_backend_controller_for_testing(nullptr);
ambient_controller()->set_backend_controller_for_testing(
std::make_unique<FakeAmbientBackendControllerImpl>());
photo_controller()->set_url_loader_for_testing(
std::make_unique<TestAmbientURLLoaderImpl>());
photo_controller()->set_image_decoder_for_testing(
std::make_unique<TestAmbientImageDecoderImpl>());
}
void AmbientAshTestBase::TearDown() {
......@@ -57,6 +100,11 @@ void AmbientAshTestBase::HideAmbientScreen() {
ambient_controller()->HideContainerView();
}
void AmbientAshTestBase::CloseAmbientScreen() {
ambient_controller()->ambient_ui_model()->SetUiVisibility(
AmbientUiVisibility::kClosed);
}
void AmbientAshTestBase::LockScreen() {
GetSessionControllerClient()->LockScreen();
}
......
......@@ -42,6 +42,10 @@ class AmbientAshTestBase : public AshTestBase {
// called.
void HideAmbientScreen();
// Closes ambient screen. Can only be called after |ShowAmbientScreen| has
// been called.
void CloseAmbientScreen();
// Simulates user locks/unlocks screen which will result in ambient widget
// shown/closed.
void LockScreen();
......
......@@ -27,6 +27,9 @@ TEST_F(AmbientContainerViewTest, WindowFullscreenSize) {
gfx::Rect container_window_bounds =
widget->GetNativeWindow()->GetBoundsInScreen();
EXPECT_EQ(root_window_bounds, container_window_bounds);
// Clean up.
CloseAmbientScreen();
}
} // namespace ash
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