Commit 3195342d authored by Meilin Wang's avatar Meilin Wang Committed by Commit Bot

ambient: Add photo attribution (part 1).

Bug: b/161481195
Test: manually.
Change-Id: Ia86508989c58adb32faef27d74be2910507a382c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2333037
Commit-Queue: Meilin Wang <meilinw@chromium.org>
Reviewed-by: default avatarXiaohui Chen <xiaohuic@chromium.org>
Cr-Commit-Position: refs/heads/master@{#797142}
parent dd201505
......@@ -9,6 +9,7 @@
#include "ash/ambient/ambient_constants.h"
#include "ash/ambient/ambient_controller.h"
#include "ash/ambient/model/ambient_backend_model.h"
#include "ash/public/cpp/ambient/ambient_client.h"
#include "ash/public/cpp/image_downloader.h"
#include "ash/shell.h"
......@@ -328,9 +329,8 @@ void AmbientPhotoController::OnScreenUpdateInfoFetched(
void AmbientPhotoController::TryReadPhotoRawData() {
const AmbientModeTopic& topic = GetNextTopic();
const std::string& image_url = topic.portrait_image_url.value_or(topic.url);
base::FilePath path = photo_path_.Append(ToPhotoFileName(image_url));
base::FilePath path = photo_path_.Append(ToPhotoFileName(topic.GetUrl()));
task_runner_->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(
......@@ -342,25 +342,25 @@ void AmbientPhotoController::TryReadPhotoRawData() {
},
path),
base::BindOnce(&AmbientPhotoController::OnPhotoRawDataRead,
weak_factory_.GetWeakPtr(), image_url));
weak_factory_.GetWeakPtr(), topic));
}
void AmbientPhotoController::OnPhotoRawDataRead(
const std::string& image_url,
const AmbientModeTopic& topic,
std::unique_ptr<std::string> data) {
if (!data || data->empty()) {
url_loader_->Download(
image_url,
topic.GetUrl(),
base::BindOnce(&AmbientPhotoController::OnPhotoRawDataAvailable,
weak_factory_.GetWeakPtr(), image_url,
weak_factory_.GetWeakPtr(), topic,
/*need_to_save=*/true));
} else {
OnPhotoRawDataAvailable(image_url, /*need_to_save=*/false, std::move(data));
OnPhotoRawDataAvailable(topic, /*need_to_save=*/false, std::move(data));
}
}
void AmbientPhotoController::OnPhotoRawDataAvailable(
const std::string& image_url,
const AmbientModeTopic& topic,
bool need_to_save,
std::unique_ptr<std::string> response_body) {
if (!response_body) {
......@@ -377,7 +377,8 @@ void AmbientPhotoController::OnPhotoRawDataAvailable(
return;
}
const base::FilePath path = photo_path_.Append(ToPhotoFileName(image_url));
const base::FilePath path =
photo_path_.Append(ToPhotoFileName(topic.GetUrl()));
task_runner_->PostTaskAndReply(
FROM_HERE,
base::BindOnce(
......@@ -388,24 +389,30 @@ void AmbientPhotoController::OnPhotoRawDataAvailable(
},
path, need_to_save, *response_body),
base::BindOnce(&AmbientPhotoController::DecodePhotoRawData,
weak_factory_.GetWeakPtr(), std::move(response_body)));
weak_factory_.GetWeakPtr(), topic,
std::move(response_body)));
}
void AmbientPhotoController::DecodePhotoRawData(
const AmbientModeTopic& topic,
std::unique_ptr<std::string> data) {
std::vector<uint8_t> image_bytes(data->begin(), data->end());
image_decoder_->Decode(image_bytes,
base::BindOnce(&AmbientPhotoController::OnPhotoDecoded,
weak_factory_.GetWeakPtr()));
weak_factory_.GetWeakPtr(), topic));
}
void AmbientPhotoController::OnPhotoDecoded(const gfx::ImageSkia& image) {
void AmbientPhotoController::OnPhotoDecoded(const AmbientModeTopic& topic,
const gfx::ImageSkia& image) {
base::TimeDelta kDelay;
if (image.isNull()) {
LOG(WARNING) << "Image is null";
kDelay = base::TimeDelta::FromMilliseconds(100);
} else {
ambient_backend_model_.AddNextImage(image);
PhotoWithDetails detailed_photo;
detailed_photo.photo = image;
detailed_photo.details = topic.details;
ambient_backend_model_.AddNextImage(std::move(detailed_photo));
}
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
......
......@@ -117,16 +117,18 @@ class ASH_EXPORT AmbientPhotoController : public AmbientBackendModelObserver {
// 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,
void OnPhotoRawDataRead(const AmbientModeTopic& topic,
std::unique_ptr<std::string> data);
void OnPhotoRawDataAvailable(const std::string& image_url,
void OnPhotoRawDataAvailable(const AmbientModeTopic& topic,
bool need_to_save,
std::unique_ptr<std::string> response_body);
void DecodePhotoRawData(std::unique_ptr<std::string> data);
void DecodePhotoRawData(const AmbientModeTopic& topic,
std::unique_ptr<std::string> data);
void OnPhotoDecoded(const gfx::ImageSkia& image);
void OnPhotoDecoded(const AmbientModeTopic& topic,
const gfx::ImageSkia& image);
void StartDownloadingWeatherConditionIcon(
const base::Optional<WeatherInfo>& weather_info);
......
......@@ -9,6 +9,7 @@
#include "ash/ambient/ambient_constants.h"
#include "ash/ambient/ambient_controller.h"
#include "ash/ambient/model/ambient_backend_model.h"
#include "ash/ambient/test/ambient_ash_test_base.h"
#include "ash/public/cpp/ambient/ambient_backend_controller.h"
#include "ash/shell.h"
......@@ -55,46 +56,46 @@ TEST_F(AmbientPhotoControllerTest, ShouldStartToDownloadTopics) {
// Test that image is downloaded when starting screen update.
TEST_F(AmbientPhotoControllerTest, ShouldStartToDownloadImages) {
auto image = photo_controller()->ambient_backend_model()->GetNextImage();
EXPECT_TRUE(image.isNull());
EXPECT_TRUE(image.IsNull());
// Start to refresh images.
photo_controller()->StartScreenUpdate();
task_environment()->FastForwardBy(1.2 * kPhotoRefreshInterval);
image = photo_controller()->ambient_backend_model()->GetNextImage();
EXPECT_FALSE(image.isNull());
EXPECT_FALSE(image.IsNull());
// Stop to refresh images.
photo_controller()->StopScreenUpdate();
image = photo_controller()->ambient_backend_model()->GetNextImage();
EXPECT_TRUE(image.isNull());
EXPECT_TRUE(image.IsNull());
}
// Tests that photos are updated periodically when starting screen update.
TEST_F(AmbientPhotoControllerTest, ShouldUpdatePhotoPeriodically) {
gfx::ImageSkia image1;
gfx::ImageSkia image2;
gfx::ImageSkia image3;
PhotoWithDetails image1;
PhotoWithDetails image2;
PhotoWithDetails image3;
// Start to refresh images.
photo_controller()->StartScreenUpdate();
task_environment()->FastForwardBy(1.2 * kPhotoRefreshInterval);
image1 = photo_controller()->ambient_backend_model()->GetNextImage();
EXPECT_FALSE(image1.isNull());
EXPECT_TRUE(image2.isNull());
EXPECT_FALSE(image1.IsNull());
EXPECT_TRUE(image2.IsNull());
// Fastforward enough time to update the photo.
task_environment()->FastForwardBy(1.2 * kPhotoRefreshInterval);
image2 = photo_controller()->ambient_backend_model()->GetNextImage();
EXPECT_FALSE(image2.isNull());
EXPECT_FALSE(image1.BackedBySameObjectAs(image2));
EXPECT_TRUE(image3.isNull());
EXPECT_FALSE(image2.IsNull());
EXPECT_FALSE(image1.photo.BackedBySameObjectAs(image2.photo));
EXPECT_TRUE(image3.IsNull());
// Fastforward enough time to update another photo.
task_environment()->FastForwardBy(1.2 * kPhotoRefreshInterval);
image3 = photo_controller()->ambient_backend_model()->GetNextImage();
EXPECT_FALSE(image3.isNull());
EXPECT_FALSE(image1.BackedBySameObjectAs(image3));
EXPECT_FALSE(image2.BackedBySameObjectAs(image3));
EXPECT_FALSE(image3.IsNull());
EXPECT_FALSE(image1.photo.BackedBySameObjectAs(image3.photo));
EXPECT_FALSE(image2.photo.BackedBySameObjectAs(image3.photo));
// Stop to refresh images.
photo_controller()->StopScreenUpdate();
......@@ -139,7 +140,7 @@ TEST_F(AmbientPhotoControllerTest, ShouldSaveAndDeleteImagesOnDisk) {
}
auto image = photo_controller()->ambient_backend_model()->GetNextImage();
EXPECT_FALSE(image.isNull());
EXPECT_FALSE(image.IsNull());
// Stop to refresh images.
photo_controller()->StopScreenUpdate();
......@@ -149,7 +150,7 @@ TEST_F(AmbientPhotoControllerTest, ShouldSaveAndDeleteImagesOnDisk) {
EXPECT_TRUE(base::IsDirectoryEmpty(ambient_image_path));
image = photo_controller()->ambient_backend_model()->GetNextImage();
EXPECT_TRUE(image.isNull());
EXPECT_TRUE(image.IsNull());
}
} // namespace ash
......@@ -4,6 +4,7 @@
#include "ash/ambient/backdrop/ambient_backend_controller_impl.h"
#include <string>
#include <utility>
#include <vector>
......@@ -77,29 +78,66 @@ std::unique_ptr<network::ResourceRequest> CreateResourceRequest(
return resource_request;
}
std::string BuildCuratedTopicDetails(
const backdrop::ScreenUpdate::Topic& topic) {
if (topic.has_metadata_line_1() && topic.has_metadata_line_2()) {
// Uses a space as the separator between.
return topic.metadata_line_1() + " " + topic.metadata_line_2();
} else if (topic.has_metadata_line_1()) {
return topic.metadata_line_1();
} else if (topic.has_metadata_line_2()) {
return topic.metadata_line_2();
} else {
return std::string();
}
}
std::string BuildPersonalTopicDetails(
const backdrop::ScreenUpdate::Topic& topic) {
// |metadata_line_1| contains the album name.
return topic.has_metadata_line_1() ? topic.metadata_line_1() : std::string();
}
void BuildBackdropTopicDetails(
const backdrop::ScreenUpdate::Topic& backdrop_topic,
AmbientModeTopic& ambient_topic) {
switch (backdrop_topic.topic_type()) {
case backdrop::TopicSource::CURATED:
ambient_topic.details = BuildCuratedTopicDetails(backdrop_topic);
break;
case backdrop::TopicSource::PERSONAL_PHOTO:
ambient_topic.details = BuildPersonalTopicDetails(backdrop_topic);
break;
default:
ambient_topic.details = std::string();
break;
}
}
// Helper function to save the information we got from the backdrop server to a
// public struct so that they can be accessed by public codes.
ash::ScreenUpdate ToScreenUpdate(
ScreenUpdate ToScreenUpdate(
const backdrop::ScreenUpdate& backdrop_screen_update) {
ash::ScreenUpdate screen_update;
ScreenUpdate screen_update;
// Parse |AmbientModeTopic|.
int topics_size = backdrop_screen_update.next_topics_size();
if (topics_size > 0) {
for (auto backdrop_topic : backdrop_screen_update.next_topics()) {
ash::AmbientModeTopic topic;
for (auto& backdrop_topic : backdrop_screen_update.next_topics()) {
AmbientModeTopic ambient_topic;
DCHECK(backdrop_topic.has_url());
topic.url = backdrop_topic.url();
ambient_topic.url = backdrop_topic.url();
if (backdrop_topic.has_portrait_image_url())
topic.portrait_image_url = backdrop_topic.portrait_image_url();
screen_update.next_topics.emplace_back(topic);
ambient_topic.portrait_image_url = backdrop_topic.portrait_image_url();
BuildBackdropTopicDetails(backdrop_topic, ambient_topic);
screen_update.next_topics.emplace_back(ambient_topic);
}
}
// Parse |WeatherInfo|.
if (backdrop_screen_update.has_weather_info()) {
const auto& backdrop_weather_info = backdrop_screen_update.weather_info();
ash::WeatherInfo weather_info;
backdrop::WeatherInfo backdrop_weather_info =
backdrop_screen_update.weather_info();
WeatherInfo weather_info;
if (backdrop_weather_info.has_condition_icon_url()) {
weather_info.condition_icon_url =
backdrop_weather_info.condition_icon_url();
......
......@@ -11,6 +11,30 @@
namespace ash {
// PhotoWithDetails------------------------------------------------------------
PhotoWithDetails::PhotoWithDetails() = default;
PhotoWithDetails::PhotoWithDetails(const PhotoWithDetails&) = default;
PhotoWithDetails& PhotoWithDetails::operator=(const PhotoWithDetails&) =
default;
PhotoWithDetails::PhotoWithDetails(PhotoWithDetails&&) = default;
PhotoWithDetails& PhotoWithDetails::operator=(PhotoWithDetails&&) = default;
PhotoWithDetails::~PhotoWithDetails() = default;
void PhotoWithDetails::Clear() {
photo = gfx::ImageSkia();
details = std::string();
}
bool PhotoWithDetails::IsNull() const {
return photo.isNull();
}
// AmbientBackendModel---------------------------------------------------------
AmbientBackendModel::AmbientBackendModel() {
SetPhotoRefreshInterval(kPhotoRefreshInterval);
}
......@@ -34,17 +58,18 @@ void AmbientBackendModel::AppendTopics(
bool AmbientBackendModel::ShouldFetchImmediately() const {
// Prefetch one image |next_image_| for photo transition animation.
return current_image_.isNull() || next_image_.isNull();
return current_image_.IsNull() || next_image_.IsNull();
}
void AmbientBackendModel::AddNextImage(const gfx::ImageSkia& image) {
if (current_image_.isNull()) {
current_image_ = image;
} else if (next_image_.isNull()) {
next_image_ = image;
void AmbientBackendModel::AddNextImage(
const PhotoWithDetails& photo_with_details) {
if (current_image_.IsNull()) {
current_image_ = photo_with_details;
} else if (next_image_.IsNull()) {
next_image_ = photo_with_details;
} else {
current_image_ = next_image_;
next_image_ = image;
next_image_ = photo_with_details;
}
NotifyImagesChanged();
......@@ -63,12 +88,12 @@ void AmbientBackendModel::SetPhotoRefreshInterval(base::TimeDelta interval) {
void AmbientBackendModel::Clear() {
topics_.clear();
current_image_ = gfx::ImageSkia();
next_image_ = gfx::ImageSkia();
current_image_.Clear();
next_image_.Clear();
}
gfx::ImageSkia AmbientBackendModel::GetNextImage() const {
if (!next_image_.isNull())
PhotoWithDetails AmbientBackendModel::GetNextImage() const {
if (!next_image_.IsNull())
return next_image_;
return current_image_;
......
......@@ -5,6 +5,7 @@
#ifndef ASH_AMBIENT_MODEL_AMBIENT_BACKEND_MODEL_H_
#define ASH_AMBIENT_MODEL_AMBIENT_BACKEND_MODEL_H_
#include <string>
#include <vector>
#include "ash/ash_export.h"
......@@ -18,6 +19,24 @@ namespace ash {
class AmbientBackendModelObserver;
// Contains each photo image and its metadata used to show on ambient.
struct ASH_EXPORT PhotoWithDetails {
PhotoWithDetails();
PhotoWithDetails(const PhotoWithDetails&);
PhotoWithDetails& operator=(const PhotoWithDetails&);
PhotoWithDetails(PhotoWithDetails&&);
PhotoWithDetails& operator=(PhotoWithDetails&&);
~PhotoWithDetails();
void Clear();
bool IsNull() const;
gfx::ImageSkia photo;
std::string details;
};
// Stores necessary information fetched from the backdrop server to render
// the photo frame and glanceable weather information on Ambient Mode. Owned
// by |AmbientController|.
......@@ -38,7 +57,7 @@ class ASH_EXPORT AmbientBackendModel {
bool ShouldFetchImmediately() const;
// Add image to local storage.
void AddNextImage(const gfx::ImageSkia& image);
void AddNextImage(const PhotoWithDetails& photo);
// Get/Set the photo refresh interval.
base::TimeDelta GetPhotoRefreshInterval();
......@@ -48,7 +67,7 @@ class ASH_EXPORT AmbientBackendModel {
void Clear();
// Get images from local storage. Could be null image.
gfx::ImageSkia GetNextImage() const;
PhotoWithDetails GetNextImage() const;
// Updates the weather information and notifies observers if the icon image is
// not null.
......@@ -80,8 +99,8 @@ class ASH_EXPORT AmbientBackendModel {
std::vector<AmbientModeTopic> topics_;
// Local cache of downloaded images for photo transition animation.
gfx::ImageSkia current_image_;
gfx::ImageSkia next_image_;
PhotoWithDetails current_image_;
PhotoWithDetails next_image_;
// The index of currently shown image.
int current_image_index_ = 0;
......
......@@ -5,6 +5,7 @@
#include "ash/ambient/model/ambient_backend_model.h"
#include <memory>
#include <string>
#include "ash/ambient/ambient_constants.h"
#include "ash/ambient/model/ambient_backend_model_observer.h"
......@@ -35,19 +36,24 @@ class AmbientBackendModelTest : public AshTestBase {
// Adds n test images to the model.
void AddNTestImages(int n) {
while (n > 0) {
gfx::ImageSkia test_image =
PhotoWithDetails test_detailed_image;
test_detailed_image.photo =
gfx::test::CreateImageSkia(/*width=*/10, /*height=*/10);
ambient_backend_model()->AddNextImage(test_image);
test_detailed_image.details = std::string("fake-photo-attribution");
ambient_backend_model()->AddNextImage(std::move(test_detailed_image));
n--;
}
}
// Returns whether the image is equivalent to the test image.
bool EqualsToTestImage(const gfx::ImageSkia& image) {
// Returns whether the image and its details are equivalent to the test
// detailed image.
bool EqualsToTestImage(const PhotoWithDetails& detailed_image) {
gfx::ImageSkia test_image =
gfx::test::CreateImageSkia(/*width=*/10, /*height=*/10);
return !image.isNull() &&
gfx::test::AreBitmapsEqual(*image.bitmap(), *test_image.bitmap());
return !detailed_image.IsNull() &&
gfx::test::AreBitmapsEqual(*(detailed_image.photo).bitmap(),
*test_image.bitmap()) &&
(detailed_image.details == std::string("fake-photo-attribution"));
}
// Returns whether the image is null.
......@@ -65,7 +71,7 @@ class AmbientBackendModelTest : public AshTestBase {
return ambient_backend_model_.get();
}
gfx::ImageSkia GetNextImage() {
PhotoWithDetails GetNextImage() {
return ambient_backend_model_->GetNextImage();
}
......
......@@ -118,7 +118,7 @@ void PhotoView::Init() {
void PhotoView::UpdateImages() {
auto* model = delegate_->GetAmbientBackendModel();
images_unscaled_[image_index_] = model->GetNextImage();
images_unscaled_[image_index_] = model->GetNextImage().photo;
if (images_unscaled_[image_index_].isNull())
return;
......
......@@ -31,6 +31,10 @@ AmbientModeTopic& AmbientModeTopic::operator=(const AmbientModeTopic&) =
AmbientModeTopic::~AmbientModeTopic() = default;
std::string AmbientModeTopic::GetUrl() const {
return portrait_image_url.value_or(url);
}
// WeatherInfo------------------------------------------------------------------
WeatherInfo::WeatherInfo() = default;
......
......@@ -27,6 +27,13 @@ struct ASH_PUBLIC_EXPORT AmbientModeTopic {
AmbientModeTopic& operator=(const AmbientModeTopic&);
~AmbientModeTopic();
// Returns a non-empty url to load the landscape or portrait image.
std::string GetUrl() const;
// Details, i.e. the attribution, to be displayed for the current photo on
// ambient.
std::string details;
// Image url.
std::string url;
......
......@@ -22,6 +22,8 @@ constexpr AmbientModeTemperatureUnit kTemperatureUnit =
constexpr char kFakeUrl[] = "chrome://ambient";
constexpr char kFakeDetails[] = "fake-photo-attribution";
AmbientSettings CreateFakeSettings() {
AmbientSettings settings;
settings.topic_source = kTopicSource;
......@@ -68,6 +70,7 @@ void FakeAmbientBackendControllerImpl::FetchScreenUpdateInfo(
OnScreenUpdateInfoFetchedCallback callback) {
ash::AmbientModeTopic topic;
topic.url = kFakeUrl;
topic.details = kFakeDetails;
ash::WeatherInfo weather_info;
weather_info.temp_f = .0f;
......
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