blob: 3084971d7303adb185c930436c09f5177346efaa [file] [log] [blame]
// Copyright 2023 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/ambient_managed_photo_controller.h"
#include <utility>
#include <vector>
#include "ash/ambient/metrics/managed_screensaver_metrics.h"
#include "ash/ambient/model/ambient_backend_model.h"
#include "ash/public/cpp/image_util.h"
#include "base/check.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "ui/gfx/image/image_skia.h"
namespace ash {
namespace {
constexpr size_t kMinImagesRequired = 2u;
} // namespace
AmbientManagedPhotoController::AmbientManagedPhotoController(
AmbientViewDelegate& view_delegate,
AmbientPhotoConfig photo_config)
: ambient_backend_model_(std::move(photo_config)) {
scoped_view_delegate_observation_.Observe(&view_delegate);
}
AmbientManagedPhotoController::~AmbientManagedPhotoController() = default;
void AmbientManagedPhotoController::StartScreenUpdate() {
if (IsScreenUpdateActive()) {
LOG(ERROR) << "AmbientManagedPhotoController is already active. Ignoring "
"StartScreenUpdate().";
return;
}
is_active_ = true;
image_attempt_no_ = 0;
LoadImages();
}
void AmbientManagedPhotoController::UpdateImageFilePaths(
const std::vector<base::FilePath>& images) {
RecordManagedScreensaverImageCount(images.size());
// Reset `error_state_` when a sufficient number of new images are received
if (images.size() < kMinImagesRequired) {
// TODO(b/269579804): Add Metrics
SetErrorState(ErrorState::kInsufficientImages);
return;
}
SetErrorState(ErrorState::kNone);
images_file_paths_ = images;
image_attempt_no_ = 0;
if (IsScreenUpdateActive()) {
// Invalidate the weak pointers to make sure that any in-flight decoding
// operations become no-ops.
weak_factory_.InvalidateWeakPtrs();
current_image_index_ = 0;
// Note: We do not clear the backend model here but rather just load
// the next topic buffer size images from disk, this will automatically
// fill the backend model with only the latest images.
LoadImages();
}
}
bool AmbientManagedPhotoController::HasScreenUpdateErrors() const {
return error_state_ != ErrorState::kNone;
}
void AmbientManagedPhotoController::SetErrorState(ErrorState error_state) {
if (error_state == error_state_) {
return;
}
error_state_ = error_state;
if (observer_) {
observer_->OnErrorStateChanged();
}
}
void AmbientManagedPhotoController::StopScreenUpdate() {
is_active_ = false;
images_file_paths_.clear();
ambient_backend_model_.Clear();
weak_factory_.InvalidateWeakPtrs();
current_image_index_ = 0;
image_attempt_no_ = 0;
}
bool AmbientManagedPhotoController::IsScreenUpdateActive() const {
return is_active_;
}
void AmbientManagedPhotoController::OnMarkerHit(
AmbientPhotoConfig::Marker marker) {
if (!ambient_backend_model_.photo_config().refresh_topic_markers.contains(
marker)) {
DVLOG(3) << "UI event " << marker
<< " does not trigger a image refresh. Ignoring...";
return;
}
if (error_state_ == ErrorState::kPhotoLoadFailure) {
LOG(WARNING) << "Not loading the next image for the UI marker " << marker
<< " as maximum photo loading attempts reached";
return;
}
DVLOG(3) << "UI event " << marker << " triggering image load";
if (!is_active_) {
LOG(DFATAL) << "Received unexpected UI marker " << marker
<< " while inactive";
return;
}
LoadImagesInternal(/*images_to_load=*/1, /*success=*/true);
}
void AmbientManagedPhotoController::LoadImages() {
if (images_file_paths_.size() < kMinImagesRequired) {
// TODO(b/269579804): Log metrics to detect if the images are not enough.
LOG(WARNING)
<< "AmbientManagedPhotoController does not have enough images.";
return;
}
// Initially load a total of backend model buffer size images in the backend
// model. Note: This should not be lower than 2 because the photo view code
// right now loads the current and the next image simultaneously, and in case
// of a buffer size equal to 1 it never triggers the images ready callback.
LoadImagesInternal(
ambient_backend_model_.photo_config().GetNumDecodedTopicsToBuffer(),
true);
}
void AmbientManagedPhotoController::LoadImagesInternal(size_t images_to_load,
bool success) {
if (images_to_load == 0 || !success) {
return;
}
// Use a partial function application to store the no of remaining images to
// load.
base::OnceCallback<void(bool)> done_callback = base::BindOnce(
&AmbientManagedPhotoController::LoadImagesInternal,
weak_factory_.GetWeakPtr(), /*images_to_load=*/images_to_load - 1);
LoadNextImage(std::move(done_callback));
}
void AmbientManagedPhotoController::LoadNextImage(
base::OnceCallback<void(bool success)> done_callback) {
CHECK(images_file_paths_.size() >= kMinImagesRequired);
image_attempt_no_++;
current_image_index_ = (current_image_index_ + 1) % images_file_paths_.size();
image_util::DecodeImageFile(
base::BindOnce(&AmbientManagedPhotoController::OnPhotoDecoded,
weak_factory_.GetWeakPtr(), std::move(done_callback)),
images_file_paths_.at(current_image_index_),
data_decoder::mojom::ImageCodec::kDefault);
}
void AmbientManagedPhotoController::HandlePhotoDecodingFailure(
base::OnceCallback<void(bool success)> done_callback) {
if (image_attempt_no_ >= GetMaxImageAttempts()) {
LOG(ERROR) << "Image decoding failed, no valid image was decoded";
SetErrorState(ErrorState::kPhotoLoadFailure);
std::move(done_callback).Run(false);
return;
}
LOG(WARNING) << "Image decoding failed, attempting to load next image";
LoadNextImage(std::move(done_callback));
}
void AmbientManagedPhotoController::OnPhotoDecoded(
base::OnceCallback<void(bool success)> done_callback,
const gfx::ImageSkia& image) {
DVLOG(3) << __func__;
if (image.isNull()) {
HandlePhotoDecodingFailure(std::move(done_callback));
return;
}
image_attempt_no_ = 0;
PhotoWithDetails detailed_photo;
detailed_photo.photo = image;
// Note: There is no surefire way of determining the orientation of the image
// so for now we assume and document that all admin provided images are
// landscape.
detailed_photo.is_portrait = false;
ambient_backend_model_.AddNextImage(std::move(detailed_photo));
// Notify the caller that photo loading was successful.
std::move(done_callback).Run(true);
}
size_t AmbientManagedPhotoController::GetMaxImageAttempts() const {
CHECK_GE(images_file_paths_.size(), kMinImagesRequired);
return images_file_paths_.size() - 1;
}
void AmbientManagedPhotoController::SetObserver(Observer* observer) {
CHECK(!observer_);
observer_ = observer;
}
} // namespace ash