// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/ambient/backdrop/ambient_backend_controller_impl.h"
#include <array>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "ash/ambient/ambient_controller.h"
#include "ash/ambient/ambient_photo_cache.h"
#include "ash/ambient/metrics/ambient_metrics.h"
#include "ash/ambient/util/ambient_util.h"
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/ambient/ambient_backend_controller.h"
#include "ash/public/cpp/ambient/ambient_client.h"
#include "ash/public/cpp/ambient/ambient_prefs.h"
#include "ash/public/cpp/ambient/common/ambient_settings.h"
#include "ash/public/cpp/ambient/proto/photo_cache_entry.pb.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/wallpaper/wallpaper_utils/wallpaper_language.h"
#include "base/barrier_closure.h"
#include "base/base64.h"
#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/logging.h"
#include "base/time/time.h"
#include "base/uuid.h"
#include "chromeos/assistant/internal/ambient/backdrop_client_config.h"
#include "chromeos/assistant/internal/ambient/utils.h"
#include "chromeos/assistant/internal/proto/backdrop/backdrop.pb.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user_manager.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/base/url_util.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/data_decoder/public/cpp/data_decoder.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "ui/gfx/geometry/size.h"
#include "url/gurl.h"
namespace ash {
namespace {
using BackdropClientConfig = chromeos::ambient::BackdropClientConfig;
constexpr char kProtoMimeType[] = "application/protobuf";
constexpr int kMaxPreviewImages = 4;
// Max body size in bytes to download.
constexpr int kMaxBodySizeBytes = 1 * 1024 * 1024; // 1 MiB
constexpr net::NetworkTrafficAnnotationTag kAmbientBackendControllerNetworkTag =
net::DefineNetworkTrafficAnnotation("ambient_backend_controller", R"(
semantics {
sender: "Ambient photo"
description:
"Download ambient image weather icon from Google."
trigger:
"Triggered periodically when the battery is charged and the user "
"is idle."
data: "None."
destination: GOOGLE_OWNED_SERVICE
internal {
contacts {
email: "[email protected]"
}
}
user_data {
type: NONE
}
last_reviewed: "2023-01-13"
}
policy {
cookies_allowed: NO
setting:
"This feature is off by default and can be overridden by user."
policy_exception_justification:
"This feature is set by user settings.ambient_mode.enabled pref. "
"The user setting is per device and cannot be overriden by admin."
})");
std::string GetClientId() {
PrefService* prefs =
Shell::Get()->session_controller()->GetPrimaryUserPrefService();
DCHECK(prefs);
std::string client_id =
prefs->GetString(ambient::prefs::kAmbientBackdropClientId);
if (client_id.empty()) {
client_id = base::Uuid::GenerateRandomV4().AsLowercaseString();
prefs->SetString(ambient::prefs::kAmbientBackdropClientId, client_id);
}
return client_id;
}
std::unique_ptr<network::ResourceRequest> CreateResourceRequest(
const BackdropClientConfig::Request& request) {
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = GURL(request.url);
resource_request->method = request.method;
resource_request->load_flags =
net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
auto language_tag = ash::GetLanguageTag();
if (!language_tag.empty()) {
resource_request->headers.SetHeader(
net::HttpRequestHeaders::kAcceptLanguage, language_tag);
}
for (const auto& header : request.headers) {
std::string encoded_value;
if (header.needs_base_64_encoded)
encoded_value = base::Base64Encode(header.value);
else
encoded_value = header.value;
resource_request->headers.SetHeader(header.name, encoded_value);
}
return resource_request;
}
std::string BuildBackdropTopicDetails(
const backdrop::ScreenUpdate::Topic& backdrop_topic) {
std::string result;
if (backdrop_topic.has_metadata_line_1())
result += backdrop_topic.metadata_line_1();
if (backdrop_topic.has_metadata_line_2()) {
if (!result.empty())
result += " ";
result += backdrop_topic.metadata_line_2();
}
// Do not include metadata_line_3.
return result;
}
::ambient::TopicType ToAmbientModeTopicType(
const backdrop::ScreenUpdate_Topic& topic) {
if (!topic.has_topic_type())
return ::ambient::TopicType::kOther;
switch (topic.topic_type()) {
case backdrop::CURATED:
return ::ambient::TopicType::kCurated;
case backdrop::PERSONAL_PHOTO:
return ::ambient::TopicType::kPersonal;
case backdrop::FEATURED_PHOTO:
return ::ambient::TopicType::kFeatured;
case backdrop::GEO_PHOTO:
return ::ambient::TopicType::kGeo;
case backdrop::CULTURAL_INSTITUTE:
return ::ambient::TopicType::kCulturalInstitute;
case backdrop::RSS_TOPIC:
return ::ambient::TopicType::kRss;
case backdrop::CAPTURED_ON_PIXEL:
return ::ambient::TopicType::kCapturedOnPixel;
default:
return ::ambient::TopicType::kOther;
}
}
std::optional<std::string> GetStringValue(const base::Value::List& values,
size_t field_number) {
if (values.empty() || values.size() < field_number)
return std::nullopt;
const base::Value& v = values[field_number - 1];
if (!v.is_string())
return std::nullopt;
return v.GetString();
}
std::optional<double> GetDoubleValue(const base::Value::List& values,
size_t field_number) {
if (values.empty() || values.size() < field_number)
return std::nullopt;
const base::Value& v = values[field_number - 1];
if (!v.is_double() && !v.is_int())
return std::nullopt;
return v.GetDouble();
}
std::optional<bool> GetBoolValue(const base::Value::List& values,
size_t field_number) {
if (values.empty() || values.size() < field_number)
return std::nullopt;
const base::Value& v = values[field_number - 1];
if (v.is_bool())
return v.GetBool();
if (v.is_int())
return v.GetInt() > 0;
return std::nullopt;
}
std::optional<WeatherInfo> ToWeatherInfo(const base::Value& result) {
DCHECK(result.is_list());
if (!result.is_list())
return std::nullopt;
WeatherInfo weather_info;
const auto& list_result = result.GetList();
weather_info.condition_description = GetStringValue(
list_result, backdrop::WeatherInfo::kConditionDescriptionFieldNumber);
weather_info.condition_icon_url = GetStringValue(
list_result, backdrop::WeatherInfo::kConditionIconUrlFieldNumber);
weather_info.temp_f =
GetDoubleValue(list_result, backdrop::WeatherInfo::kTempFFieldNumber);
weather_info.show_celsius =
GetBoolValue(list_result, backdrop::WeatherInfo::kShowCelsiusFieldNumber)
.value_or(false);
return weather_info;
}
// 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.
ScreenUpdate ToScreenUpdate(
const backdrop::ScreenUpdate& backdrop_screen_update) {
ScreenUpdate screen_update;
// Parse |AmbientModeTopic|.
for (const auto& backdrop_topic : backdrop_screen_update.next_topics()) {
DCHECK(backdrop_topic.has_url());
auto topic_type = ToAmbientModeTopicType(backdrop_topic);
if (!ambient::util::IsAmbientModeTopicTypeAllowed(topic_type)) {
DVLOG(3) << "Filtering topic_type: "
<< backdrop::TopicSource_Name(backdrop_topic.topic_type());
continue;
}
AmbientModeTopic ambient_topic;
ambient_topic.topic_type = topic_type;
// If the |portrait_image_url| field is not empty, we assume the image is
// portrait.
if (backdrop_topic.has_portrait_image_url()) {
ambient_topic.url = backdrop_topic.portrait_image_url();
ambient_topic.is_portrait = true;
} else {
ambient_topic.url = backdrop_topic.url();
}
if (backdrop_topic.has_related_topic()) {
if (backdrop_topic.related_topic().has_portrait_image_url()) {
ambient_topic.related_image_url =
backdrop_topic.related_topic().portrait_image_url();
} else {
ambient_topic.related_image_url = backdrop_topic.related_topic().url();
}
}
ambient_topic.details = BuildBackdropTopicDetails(backdrop_topic);
ambient_topic.related_details =
BuildBackdropTopicDetails(backdrop_topic.related_topic());
screen_update.next_topics.emplace_back(ambient_topic);
}
// Parse |WeatherInfo|.
if (backdrop_screen_update.has_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();
}
if (backdrop_weather_info.has_temp_f())
weather_info.temp_f = backdrop_weather_info.temp_f();
if (backdrop_weather_info.has_show_celsius())
weather_info.show_celsius = backdrop_weather_info.show_celsius();
screen_update.weather_info = weather_info;
}
return screen_update;
}
// Helper function to extract image URLs from screen update response for
// screen saver preview.
std::vector<GURL> ToPreviewUrls(const ScreenUpdate& screen_update) {
std::vector<GURL> preview_urls;
for (const auto& topic : screen_update.next_topics) {
preview_urls.emplace_back(topic.url);
if (preview_urls.size() == kMaxPreviewImages) {
break;
}
}
return preview_urls;
}
bool IsArtSettingVisible(const ArtSetting& art_setting) {
const auto& album_id = art_setting.album_id;
return album_id == kAmbientModeEarthAndSpaceAlbumId ||
album_id == kAmbientModeFeaturedPhotoAlbumId;
}
} // namespace
// Helper class for handling Backdrop service requests.
class BackdropURLLoader {
public:
BackdropURLLoader() = default;
BackdropURLLoader(const BackdropURLLoader&) = delete;
BackdropURLLoader& operator=(const BackdropURLLoader&) = delete;
~BackdropURLLoader() = default;
// Starts downloading the proto. |request_body| is a serialized proto and
// will be used as the upload body if it is a POST request.
void Start(
std::unique_ptr<network::ResourceRequest> resource_request,
const std::optional<std::string>& request_body,
const net::NetworkTrafficAnnotationTag& traffic_annotation,
network::SimpleURLLoader::BodyAsStringCallbackDeprecated callback) {
// No ongoing downloading task.
DCHECK(!simple_loader_);
loader_factory_ = AmbientClient::Get()->GetURLLoaderFactory();
simple_loader_ = network::SimpleURLLoader::Create(
std::move(resource_request), traffic_annotation);
if (request_body)
simple_loader_->AttachStringForUpload(*request_body, kProtoMimeType);
// |base::Unretained| is safe because this instance outlives
// |simple_loader_|.
simple_loader_->DownloadToString(
loader_factory_.get(),
base::BindOnce(&BackdropURLLoader::OnUrlDownloaded,
base::Unretained(this), std::move(callback)),
kMaxBodySizeBytes);
}
private:
// Called when the download completes.
void OnUrlDownloaded(
network::SimpleURLLoader::BodyAsStringCallbackDeprecated callback,
std::unique_ptr<std::string> response_body) {
loader_factory_.reset();
if (simple_loader_->NetError() == net::OK && response_body) {
simple_loader_.reset();
std::move(callback).Run(std::move(response_body));
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"
<< simple_loader_->NetError();
simple_loader_.reset();
std::move(callback).Run(std::make_unique<std::string>());
}
std::unique_ptr<network::SimpleURLLoader> simple_loader_;
scoped_refptr<network::SharedURLLoaderFactory> loader_factory_;
};
AmbientBackendControllerImpl::AmbientBackendControllerImpl()
: backdrop_client_config_(ash::AmbientClient::Get()->ShouldUseProdServer()
? BackdropClientConfig::ServerType::kProd
: BackdropClientConfig::ServerType::kDev) {}
AmbientBackendControllerImpl::~AmbientBackendControllerImpl() = default;
void AmbientBackendControllerImpl::FetchScreenUpdateInfo(
int num_topics,
bool show_pair_personal_portraits,
const gfx::Size& screen_size,
OnScreenUpdateInfoFetchedCallback callback) {
Shell::Get()->ambient_controller()->RequestAccessToken(base::BindOnce(
&AmbientBackendControllerImpl::FetchScreenUpdateInfoInternal,
weak_factory_.GetWeakPtr(), num_topics, show_pair_personal_portraits,
screen_size, std::move(callback)));
}
void AmbientBackendControllerImpl::FetchPreviewImages(
const gfx::Size& preview_size,
OnPreviewImagesFetchedCallback callback) {
constexpr int num_topics = kMaxPreviewImages;
OnScreenUpdateInfoFetchedCallback combined_callback =
base::BindOnce(&ToPreviewUrls).Then(std::move(callback));
Shell::Get()->ambient_controller()->RequestAccessToken(base::BindOnce(
&AmbientBackendControllerImpl::FetchScreenUpdateInfoInternal,
weak_factory_.GetWeakPtr(), num_topics, false, preview_size,
std::move(combined_callback)));
}
void AmbientBackendControllerImpl::GetSettings(GetSettingsCallback callback) {
Shell::Get()->ambient_controller()->RequestAccessToken(
base::BindOnce(&AmbientBackendControllerImpl::StartToGetSettings,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void AmbientBackendControllerImpl::UpdateSettings(
const AmbientSettings settings,
UpdateSettingsCallback callback) {
auto* ambient_controller = Shell::Get()->ambient_controller();
// Clear disk cache when Settings changes.
// TODO(wutao): Use observer pattern. Need to future narrow down
// the clear up only on albums changes, not on temperature unit
// changes.
ambient_photo_cache::Clear(ambient_photo_cache::Store::kPrimary);
ambient_controller->RequestAccessToken(base::BindOnce(
&AmbientBackendControllerImpl::StartToUpdateSettings,
weak_factory_.GetWeakPtr(), settings, std::move(callback)));
}
void AmbientBackendControllerImpl::FetchPersonalAlbums(
int banner_width,
int banner_height,
int num_albums,
const std::string& resume_token,
OnPersonalAlbumsFetchedCallback callback) {
Shell::Get()->ambient_controller()->RequestAccessToken(
base::BindOnce(&AmbientBackendControllerImpl::FetchPersonalAlbumsInternal,
weak_factory_.GetWeakPtr(), banner_width, banner_height,
num_albums, resume_token, std::move(callback)));
}
void AmbientBackendControllerImpl::FetchWeather(
std::optional<std::string> weather_client_id,
bool prefer_alpha_endpoint,
FetchWeatherCallback callback) {
auto response_handler =
[](FetchWeatherCallback callback,
std::unique_ptr<BackdropURLLoader> backdrop_url_loader,
std::unique_ptr<std::string> response) {
constexpr char kJsonPrefix[] = ")]}'\n";
if (response && response->length() > strlen(kJsonPrefix)) {
auto json_handler =
[](FetchWeatherCallback callback,
data_decoder::DataDecoder::ValueOrError result) {
if (result.has_value()) {
std::move(callback).Run(ToWeatherInfo(*result));
} else {
DVLOG(1) << "Failed to parse weather json.";
std::move(callback).Run(std::nullopt);
}
};
data_decoder::DataDecoder::ParseJsonIsolated(
response->substr(strlen(kJsonPrefix)),
base::BindOnce(json_handler, std::move(callback)));
} else {
std::move(callback).Run(std::nullopt);
}
};
const auto* user = user_manager::UserManager::Get()->GetActiveUser();
DCHECK(user->HasGaiaAccount());
BackdropClientConfig::Request request =
backdrop_client_config_.CreateFetchWeatherInfoRequest(
user->GetAccountId().GetGaiaId(), GetClientId(), weather_client_id,
prefer_alpha_endpoint);
std::unique_ptr<network::ResourceRequest> resource_request =
CreateResourceRequest(request);
auto backdrop_url_loader = std::make_unique<BackdropURLLoader>();
auto* loader_ptr = backdrop_url_loader.get();
loader_ptr->Start(std::move(resource_request), /*request_body=*/std::nullopt,
kAmbientBackendControllerNetworkTag,
base::BindOnce(response_handler, std::move(callback),
std::move(backdrop_url_loader)));
}
const std::array<const char*, 2>&
AmbientBackendControllerImpl::GetBackupPhotoUrls() const {
return chromeos::ambient::kBackupPhotoUrls;
}
std::array<const char*, 2>
AmbientBackendControllerImpl::GetTimeOfDayVideoPreviewImageUrls(
AmbientVideo video) const {
return chromeos::ambient::GetTimeOfDayVideoPreviewImageUrls(video);
}
const char* AmbientBackendControllerImpl::GetPromoBannerUrl() const {
return chromeos::ambient::kTimeOfDayBannerImageUrl;
}
const char* AmbientBackendControllerImpl::GetTimeOfDayProductName() const {
return chromeos::ambient::kTimeOfDayProductName;
}
void AmbientBackendControllerImpl::FetchScreenUpdateInfoInternal(
int num_topics,
bool show_pair_personal_portraits,
const gfx::Size& screen_size,
OnScreenUpdateInfoFetchedCallback callback,
const std::string& gaia_id,
const std::string& access_token) {
if (gaia_id.empty() || access_token.empty()) {
LOG(ERROR) << "Failed to fetch access token for ScreenUpdate";
std::move(callback).Run(ash::ScreenUpdate());
return;
}
BackdropClientConfig::Request request =
backdrop_client_config_.CreateFetchScreenUpdateRequest({
{/*gaia_id*/ gaia_id,
/*token*/ access_token,
/*client_id*/ GetClientId()},
/*num_topics*/ num_topics,
/*show_pair_personal_portraits*/ show_pair_personal_portraits,
});
auto resource_request = CreateResourceRequest(request);
DCHECK(!screen_size.IsEmpty());
resource_request->url =
net::AppendQueryParameter(resource_request->url, "device-screen-width",
base::NumberToString(screen_size.width()));
resource_request->url =
net::AppendQueryParameter(resource_request->url, "device-screen-height",
base::NumberToString(screen_size.height()));
auto backdrop_url_loader = std::make_unique<BackdropURLLoader>();
auto* loader_ptr = backdrop_url_loader.get();
loader_ptr->Start(
std::move(resource_request), request.body,
kAmbientBackendControllerNetworkTag,
base::BindOnce(&AmbientBackendControllerImpl::OnScreenUpdateInfoFetched,
weak_factory_.GetWeakPtr(), std::move(callback),
std::move(backdrop_url_loader)));
}
void AmbientBackendControllerImpl::OnScreenUpdateInfoFetched(
OnScreenUpdateInfoFetchedCallback callback,
std::unique_ptr<BackdropURLLoader> backdrop_url_loader,
std::unique_ptr<std::string> response) {
DCHECK(backdrop_url_loader);
// Parse the |ScreenUpdate| out from the response string.
// Note that the |backdrop_screen_update| can be an empty instance if the
// parsing has failed.
backdrop::ScreenUpdate backdrop_screen_update =
BackdropClientConfig::ParseScreenUpdateFromResponse(*response);
// Store the information to a public struct and notify the caller.
auto screen_update = ToScreenUpdate(backdrop_screen_update);
std::move(callback).Run(screen_update);
}
void AmbientBackendControllerImpl::StartToGetSettings(
GetSettingsCallback callback,
const std::string& gaia_id,
const std::string& access_token) {
if (gaia_id.empty() || access_token.empty()) {
std::move(callback).Run(/*topic_source=*/std::nullopt);
return;
}
std::string client_id = GetClientId();
BackdropClientConfig::Request request =
backdrop_client_config_.CreateGetSettingsRequest(gaia_id, access_token,
client_id);
auto resource_request = CreateResourceRequest(request);
auto backdrop_url_loader = std::make_unique<BackdropURLLoader>();
auto* loader_ptr = backdrop_url_loader.get();
loader_ptr->Start(
std::move(resource_request), request.body,
kAmbientBackendControllerNetworkTag,
base::BindOnce(&AmbientBackendControllerImpl::OnGetSettings,
weak_factory_.GetWeakPtr(), std::move(callback),
std::move(backdrop_url_loader)));
}
void AmbientBackendControllerImpl::OnGetSettings(
GetSettingsCallback callback,
std::unique_ptr<BackdropURLLoader> backdrop_url_loader,
std::unique_ptr<std::string> response) {
DCHECK(backdrop_url_loader);
auto settings = BackdropClientConfig::ParseGetSettingsResponse(*response);
// |art_settings| should not be empty if parsed successfully.
if (settings.art_settings.empty()) {
std::move(callback).Run(std::nullopt);
} else {
for (auto& art_setting : settings.art_settings) {
art_setting.visible = IsArtSettingVisible(art_setting);
art_setting.enabled = art_setting.enabled && art_setting.visible;
}
std::move(callback).Run(settings);
}
}
void AmbientBackendControllerImpl::StartToUpdateSettings(
const AmbientSettings& settings,
UpdateSettingsCallback callback,
const std::string& gaia_id,
const std::string& access_token) {
if (gaia_id.empty() || access_token.empty()) {
std::move(callback).Run(/*success=*/false, settings);
return;
}
std::string client_id = GetClientId();
BackdropClientConfig::Request request =
backdrop_client_config_.CreateUpdateSettingsRequest(gaia_id, access_token,
client_id, settings);
auto resource_request = CreateResourceRequest(request);
auto backdrop_url_loader = std::make_unique<BackdropURLLoader>();
auto* loader_ptr = backdrop_url_loader.get();
loader_ptr->Start(
std::move(resource_request), request.body,
kAmbientBackendControllerNetworkTag,
base::BindOnce(&AmbientBackendControllerImpl::OnUpdateSettings,
weak_factory_.GetWeakPtr(), std::move(callback), settings,
std::move(backdrop_url_loader)));
}
void AmbientBackendControllerImpl::OnUpdateSettings(
UpdateSettingsCallback callback,
const AmbientSettings& settings,
std::unique_ptr<BackdropURLLoader> backdrop_url_loader,
std::unique_ptr<std::string> response) {
DCHECK(backdrop_url_loader);
const bool success =
BackdropClientConfig::ParseUpdateSettingsResponse(*response);
if (success) {
// Store information about the ambient mode settings in a user pref so that
// it can be uploaded as a histogram.
Shell::Get()->session_controller()->GetPrimaryUserPrefService()->SetInteger(
ambient::prefs::kAmbientModePhotoSourcePref,
static_cast<int>(ambient::AmbientSettingsToPhotoSource(settings)));
}
std::move(callback).Run(success, settings);
}
void AmbientBackendControllerImpl::FetchPersonalAlbumsInternal(
int banner_width,
int banner_height,
int num_albums,
const std::string& resume_token,
OnPersonalAlbumsFetchedCallback callback,
const std::string& gaia_id,
const std::string& access_token) {
if (gaia_id.empty() || access_token.empty()) {
DVLOG(2) << "Failed to fetch access token";
// Returns an empty instance to indicate the failure.
std::move(callback).Run(ash::PersonalAlbums());
return;
}
BackdropClientConfig::Request request =
backdrop_client_config_.CreateFetchPersonalAlbumsRequest(
banner_width, banner_height, num_albums, resume_token, gaia_id,
access_token);
std::unique_ptr<network::ResourceRequest> resource_request =
CreateResourceRequest(request);
auto backdrop_url_loader = std::make_unique<BackdropURLLoader>();
auto* loader_ptr = backdrop_url_loader.get();
loader_ptr->Start(
std::move(resource_request), /*request_body=*/std::nullopt,
kAmbientBackendControllerNetworkTag,
base::BindOnce(&AmbientBackendControllerImpl::OnPersonalAlbumsFetched,
weak_factory_.GetWeakPtr(), std::move(callback),
std::move(backdrop_url_loader)));
}
void AmbientBackendControllerImpl::OnPersonalAlbumsFetched(
OnPersonalAlbumsFetchedCallback callback,
std::unique_ptr<BackdropURLLoader> backdrop_url_loader,
std::unique_ptr<std::string> response) {
DCHECK(backdrop_url_loader);
// Parse the |PersonalAlbumsResponse| out from the response string.
// Note that the |personal_albums| can be an empty instance if the parsing has
// failed.
ash::PersonalAlbums personal_albums =
BackdropClientConfig::ParsePersonalAlbumsResponse(*response);
std::move(callback).Run(std::move(personal_albums));
}
void AmbientBackendControllerImpl::FetchSettingsAndAlbums(
int banner_width,
int banner_height,
int num_albums,
OnSettingsAndAlbumsFetchedCallback callback) {
auto on_done = base::BarrierClosure(
/*num_callbacks=*/2,
base::BindOnce(&AmbientBackendControllerImpl::OnSettingsAndAlbumsFetched,
weak_factory_.GetWeakPtr(), std::move(callback)));
GetSettings(base::BindOnce(&AmbientBackendControllerImpl::OnSettingsFetched,
weak_factory_.GetWeakPtr(), on_done));
FetchPersonalAlbums(
banner_width, banner_height, num_albums, /*resume_token=*/"",
base::BindOnce(&AmbientBackendControllerImpl::OnAlbumsFetched,
weak_factory_.GetWeakPtr(), on_done));
}
void AmbientBackendControllerImpl::OnSettingsFetched(
base::RepeatingClosure on_done,
const std::optional<ash::AmbientSettings>& settings) {
settings_ = settings;
std::move(on_done).Run();
}
void AmbientBackendControllerImpl::OnAlbumsFetched(
base::RepeatingClosure on_done,
ash::PersonalAlbums personal_albums) {
personal_albums_ = std::move(personal_albums);
std::move(on_done).Run();
}
void AmbientBackendControllerImpl::OnSettingsAndAlbumsFetched(
OnSettingsAndAlbumsFetchedCallback callback) {
std::move(callback).Run(settings_, std::move(personal_albums_));
}
} // namespace ash