chromium/ash/public/cpp/holding_space/holding_space_image.h

// 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.

#ifndef ASH_PUBLIC_CPP_HOLDING_SPACE_HOLDING_SPACE_IMAGE_H_
#define ASH_PUBLIC_CPP_HOLDING_SPACE_HOLDING_SPACE_IMAGE_H_

#include <optional>

#include "ash/public/cpp/ash_public_export.h"
#include "ash/public/cpp/holding_space/holding_space_item.h"
#include "base/callback_list.h"
#include "base/files/file.h"
#include "base/functional/callback_forward.h"
#include "base/timer/timer.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image_skia.h"

namespace ash {

// TODO(crbug.com/40173943): Rename and move to more generic location.
// A wrapper around a `gfx::ImageSkia` that supports dynamic updates.
class ASH_PUBLIC_EXPORT HoldingSpaceImage {
 public:
  using CallbackList = base::RepeatingClosureList;

  // Returns an `SkBitmap`.
  using BitmapCallback =
      base::OnceCallback<void(const SkBitmap* bitmap, base::File::Error error)>;

  // Returns an `SkBitmap` asynchronously for a given `file_path` and `size`.
  using AsyncBitmapResolver =
      base::RepeatingCallback<void(const base::FilePath& file_path,
                                   const gfx::Size& size,
                                   BitmapCallback callback)>;

  // Returns a `gfx::ImageSkia` to be used as a placeholder prior to the async
  // `SkBitmap` being resolved or when async `SkBitmap` resolution fails.
  // NOTE: The placeholder resolver will be invoked during `HoldingSpaceImage`
  // construction, at which time `dark_background` and `is_folder` are absent.
  using PlaceholderImageSkiaResolver = base::RepeatingCallback<gfx::ImageSkia(
      const base::FilePath& file_path,
      const gfx::Size& size,
      const std::optional<bool>& dark_background,
      const std::optional<bool>& is_folder)>;

  HoldingSpaceImage(const gfx::Size& max_size,
                    const base::FilePath& backing_file_path,
                    AsyncBitmapResolver async_bitmap_resolver);

  HoldingSpaceImage(
      const gfx::Size& max_size,
      const base::FilePath& backing_file_path,
      AsyncBitmapResolver async_bitmap_resolver,
      PlaceholderImageSkiaResolver placeholder_image_skia_resolver);

  HoldingSpaceImage(const HoldingSpaceImage&) = delete;
  HoldingSpaceImage& operator=(const HoldingSpaceImage&) = delete;
  ~HoldingSpaceImage();

  // Returns a placeholder resolver which creates an image corresponding to the
  // file type of the provided `file_path`.
  static PlaceholderImageSkiaResolver CreateDefaultPlaceholderImageSkiaResolver(
      bool use_light_mode_as_default = false);

  // Sets whether image invalidation should be done without delay. This makes it
  // possible to disable invalidation throttling that reduces the number of
  // async bitmap requests in production.
  static void SetUseZeroInvalidationDelayForTesting(bool value);

  bool operator==(const HoldingSpaceImage& rhs) const;

  // Adds `callback` to be notified of changes to the underlying `image_skia_`.
  base::CallbackListSubscription AddImageSkiaChangedCallback(
      CallbackList::CallbackType callback) const;

  // Returns the underlying `gfx::ImageSkia`. If `size` is omitted, the
  // `max_size_` passed in the constructor is used. If `size` is present, it
  // must be less than or equal to the `max_size_` passed in the constructor in
  // order to prevent pixelation that would otherwise occur due to upscaling. If
  // `dark_background` is `true`, file type icons will use lighter foreground
  // colors to ensure sufficient contrast. Note that the image source may be
  // dynamically updated, so UI classes should observe and react to updates.
  gfx::ImageSkia GetImageSkia(
      const std::optional<gfx::Size>& size = std::nullopt,
      bool dark_background = false) const;

  // Creates new image skia for the item, and thus invalidates currently loaded
  // representation. When the image is requested next time, the image
  // representations will be reloaded.
  void Invalidate();

  // Updates the backing file path that should be used to generate image
  // representations for the item. The method will *not* invalidate previously
  // loaded image representations - it assumes that the file contents remained
  // the same, and old representations are thus still valid.
  void UpdateBackingFilePath(const base::FilePath& file_path);

  // Whether the image currently uses the placeholder image skia. True if no
  // bitmaps have been successfully resolved.
  bool UsingPlaceholder() const;

  // Fires the image invalidation timer if it's currently running.
  bool FireInvalidateTimerForTesting();

 private:
  class ImageSkiaSource;

  // Requests a load for the image representation for the provided scale.
  void LoadBitmap(float scale);

  // Response to an image representation load request.
  void OnBitmapLoaded(const base::FilePath& file_path,
                      float scale,
                      const SkBitmap* bitmap,
                      base::File::Error error);

  // Creates `image_skia_` to be used for the holding space image.
  // If `image_skia_` already exists, it will be recreated with a fresh image
  // source.
  void CreateImageSkia();

  // `Invalidate()` requests are handled with a delay to reduce number of image
  // loads if the backing file gets updated multiple times in quick succession.
  void OnInvalidateTimer();

  const gfx::Size max_size_;
  base::FilePath backing_file_path_;
  AsyncBitmapResolver async_bitmap_resolver_;
  std::optional<base::File::Error> async_bitmap_resolver_error_;
  PlaceholderImageSkiaResolver placeholder_image_skia_resolver_;

  gfx::ImageSkia image_skia_;
  gfx::ImageSkia placeholder_;

  // Timer used to throttle image invalidate requests.
  base::OneShotTimer invalidate_timer_;

  // Mutable to allow const access from `AddImageSkiaChangedCallback()`.
  mutable CallbackList callback_list_;

  base::WeakPtrFactory<HoldingSpaceImage> weak_factory_{this};
};

}  // namespace ash

#endif  // ASH_PUBLIC_CPP_HOLDING_SPACE_HOLDING_SPACE_IMAGE_H_