chromium/ash/ambient/model/ambient_backend_model.h

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef ASH_AMBIENT_MODEL_AMBIENT_BACKEND_MODEL_H_
#define ASH_AMBIENT_MODEL_AMBIENT_BACKEND_MODEL_H_

#include <string>
#include <vector>

#include "ash/ambient/ambient_constants.h"
#include "ash/ambient/model/ambient_photo_config.h"
#include "ash/ash_export.h"
#include "ash/public/cpp/ambient/ambient_backend_controller.h"
#include "base/containers/circular_deque.h"
#include "base/observer_list.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "ui/gfx/image/image_skia.h"

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;
  gfx::ImageSkia related_photo;
  std::string details;
  std::string related_details;
  // Hash of this image data. Used for de-duping images.
  std::string hash;
  // Whether the image is portrait or not.
  bool is_portrait = false;
  ::ambient::TopicType topic_type = ::ambient::TopicType::kOther;
};

// Stores necessary information fetched from the backdrop server to render
// the photo frame in Ambient Mode. Owned by |AmbientController|.
class ASH_EXPORT AmbientBackendModel {
 public:
  explicit AmbientBackendModel(AmbientPhotoConfig photo_config);
  AmbientBackendModel(const AmbientBackendModel&) = delete;
  AmbientBackendModel& operator=(AmbientBackendModel&) = delete;
  ~AmbientBackendModel();

  void AddObserver(AmbientBackendModelObserver* observer);
  void RemoveObserver(AmbientBackendModelObserver* observer);

  // If enough images are loaded to start ambient mode.
  bool ImagesReady() const;

  // Add image to local storage.
  void AddNextImage(const PhotoWithDetails& photo);

  // Returns true if |hash| would cause an identical image to appear twice in a
  // row. For example:
  // {A, B} + B => true
  // {A, B} + A => false
  // {A, _} + B => false
  // {A, _} + A => true
  bool IsHashDuplicate(const std::string& hash) const;

  // Record that fetching an image has failed.
  void AddImageFailure();

  void ResetImageFailures();

  bool ImageLoadingFailed();

  // Clear local storage.
  void Clear();

  // Sets the new AmbientPhotoConfig to use. This automatically |Clear()|s the
  // model of any existing topics.
  void SetPhotoConfig(AmbientPhotoConfig photo_config);

  // Returns all available decoded topics. The number of decoded topics in the
  // output will always be <= |AmbientPhotoConfig.num_decoded_topics_to_buffer|.
  //
  // Every PhotoWithDetails instance in the output shall be non-null.
  const base::circular_deque<PhotoWithDetails>& all_decoded_topics() const {
    return all_decoded_topics_;
  }

  // Gets the 2 oldest decoded topics. It's possible to accomplish this as well
  // by calling GetAllAvailableDecodedTopics() directly, but this wrapper
  // function is provided as a convenience.
  //
  // If an output PhotoWithDetails argument is nullptr, that specific topic is
  // ignored and not fetched.
  //
  // If one of the requested topics is unavailable, its corresponding output
  // argument is set to an empty PhotoWithDetails instance.
  void GetCurrentAndNextImages(PhotoWithDetails* current_image_out,
                               PhotoWithDetails* next_image_out) const;

  base::TimeDelta GetPhotoRefreshInterval() const;

  const AmbientPhotoConfig& photo_config() const { return photo_config_; }

 private:
  friend class AmbientBackendModelTest;
  friend class AmbientAshTestBase;

  void NotifyImageAdded();
  void NotifyImagesReady();
  void OnImagesReadyTimeoutFired();

  AmbientPhotoConfig photo_config_;
  std::vector<AmbientModeTopic> topics_;

  // All available decoded topics. The size of the ring buffer is capped
  // according to |AmbientPhotoConfig.num_decoded_topics_to_buffer|. The most
  // recently decoded topics are pushed to the back of the ring buffer and the
  // oldest topics are popped from the front.
  base::circular_deque<PhotoWithDetails> all_decoded_topics_;

  base::OneShotTimer images_ready_timeout_timer_;
  bool images_ready_timed_out_ = false;

  // The number of consecutive failures to load the next image.
  int failures_ = 0;

  base::ObserverList<AmbientBackendModelObserver> observers_;

  int buffer_length_for_testing_ = -1;
};

}  // namespace ash

#endif  // ASH_AMBIENT_MODEL_AMBIENT_BACKEND_MODEL_H_