chromium/chromeos/ash/components/phonehub/camera_roll_thumbnail_decoder_impl.cc

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

#include "chromeos/ash/components/phonehub/camera_roll_thumbnail_decoder_impl.h"

#include "base/containers/flat_map.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "chromeos/ash/components/multidevice/logging/logging.h"
#include "chromeos/ash/components/phonehub/camera_roll_item.h"
#include "chromeos/ash/components/phonehub/proto/phonehub_api.pb.h"
#include "services/data_decoder/public/cpp/decode_image.h"
#include "services/data_decoder/public/mojom/image_decoder.mojom.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.h"

namespace ash {
namespace phonehub {

namespace {

using BatchDecodeCallback =
    ::base::OnceCallback<void(CameraRollThumbnailDecoder::BatchDecodeResult,
                              const std::vector<CameraRollItem>&)>;

}  // namespace

CameraRollThumbnailDecoderImpl::DecodeRequest::DecodeRequest(
    const proto::CameraRollItem& item_proto)
    : item_proto_(item_proto) {}

CameraRollThumbnailDecoderImpl::DecodeRequest::~DecodeRequest() = default;

const proto::CameraRollItemMetadata&
CameraRollThumbnailDecoderImpl::DecodeRequest::GetMetadata() const {
  return item_proto_.metadata();
}

void CameraRollThumbnailDecoderImpl::DecodeRequest::CompleteWithDecodedBitmap(
    const SkBitmap& bitmap) {
  gfx::ImageSkia image_skia = gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
  if (!image_skia.isNull()) {
    image_skia.MakeThreadSafe();
    decoded_thumbnail_ = gfx::Image(image_skia);
  }
  is_completed_ = true;
}

void CameraRollThumbnailDecoderImpl::DecodeRequest::CompleteWithExistingImage(
    const gfx::Image& image) {
  decoded_thumbnail_ = image;
  is_completed_ = true;
}

const std::string&
CameraRollThumbnailDecoderImpl::DecodeRequest::GetEncodedThumbnail() const {
  return item_proto_.thumbnail().data();
}

CameraRollThumbnailDecoderImpl::DecoderDelegate::DecoderDelegate() = default;

CameraRollThumbnailDecoderImpl::DecoderDelegate::~DecoderDelegate() = default;

void CameraRollThumbnailDecoderImpl::DecoderDelegate::DecodeThumbnail(
    const DecodeRequest& request,
    data_decoder::DecodeImageCallback callback) {
  const std::string& encoded_thumbnail = request.GetEncodedThumbnail();
  data_decoder::DecodeImage(
      &data_decoder_, base::as_bytes(base::make_span(encoded_thumbnail)),
      data_decoder::mojom::ImageCodec::kDefault,
      /*shrink_to_fit=*/true, data_decoder::kDefaultMaxSizeInBytes,
      /*desired_image_frame_size=*/gfx::Size(), std::move(callback));
}

CameraRollThumbnailDecoderImpl::CameraRollThumbnailDecoderImpl()
    : decoder_delegate_(std::make_unique<DecoderDelegate>()) {}

CameraRollThumbnailDecoderImpl::~CameraRollThumbnailDecoderImpl() = default;

void CameraRollThumbnailDecoderImpl::BatchDecode(
    const proto::FetchCameraRollItemsResponse& response,
    const std::vector<CameraRollItem>& current_items,
    BatchDecodeCallback callback) {
  CancelPendingRequests();

  for (const proto::CameraRollItem& item_proto : response.items()) {
    pending_requests_.emplace_back(item_proto);
  }
  pending_callback_ = std::move(callback);

  base::flat_map<std::string, const gfx::Image*> existing_thumbnail_map;
  for (const CameraRollItem& item : current_items) {
    existing_thumbnail_map[item.metadata().key()] = &item.thumbnail();
  }

  for (DecodeRequest& request : pending_requests_) {
    if (!request.GetEncodedThumbnail().empty()) {
      // Thumbnail of this item is sent by the phone either because it is new or
      // has been changed.
      decoder_delegate_->DecodeThumbnail(
          request, base::BindOnce(
                       &CameraRollThumbnailDecoderImpl::OnThumbnailDecoded,
                       weak_ptr_factory_.GetWeakPtr(), request.GetMetadata()));
    } else if (existing_thumbnail_map.contains(request.GetMetadata().key())) {
      // Existing items that have not changed should have their thumbnails
      // already decoded.
      request.CompleteWithExistingImage(
          *(existing_thumbnail_map.at(request.GetMetadata().key())));
    } else {
      // Thumbnail for this item is not already available but it is not sent by
      // the phone. Most likely that this is an outdated response. Ignore it and
      // wait for the next response.
      CancelPendingRequests();
      return;
    }
  }
  // Check if all requests are already completed at this point.
  CheckPendingThumbnailRequests();
}

void CameraRollThumbnailDecoderImpl::OnThumbnailDecoded(
    const proto::CameraRollItemMetadata& metadata,
    const SkBitmap& thumbnail_bitmap) {
  for (DecodeRequest& request : pending_requests_) {
    if (request.GetMetadata().key() != metadata.key()) {
      continue;
    }

    request.CompleteWithDecodedBitmap(thumbnail_bitmap);
    CheckPendingThumbnailRequests();
    break;
  }
}

void CameraRollThumbnailDecoderImpl::CheckPendingThumbnailRequests() {
  for (const DecodeRequest& request : pending_requests_) {
    if (!request.is_completed()) {
      return;
    }
  }

  // All pending requests have been completed. Replace current items with this
  // new batch.
  std::vector<CameraRollItem> new_items;
  for (const DecodeRequest& request : pending_requests_) {
    // The decoded thumbnail image can be null if the thumbnail bytes cannot be
    // decoded.
    if (!request.decoded_thumbnail().IsEmpty()) {
      new_items.emplace_back(request.GetMetadata(),
                             request.decoded_thumbnail());
    } else {
      PA_LOG(ERROR) << "Failed to decode thumbnail for file "
                    << request.GetMetadata().file_name();
    }
  }
  pending_requests_.clear();

  std::move(pending_callback_).Run(BatchDecodeResult::kCompleted, new_items);
}

void CameraRollThumbnailDecoderImpl::CancelPendingRequests() {
  weak_ptr_factory_.InvalidateWeakPtrs();
  pending_requests_.clear();
  if (!pending_callback_.is_null()) {
    std::vector<CameraRollItem> empty_items;
    std::move(pending_callback_)
        .Run(BatchDecodeResult::kCancelled, empty_items);
  }
}

}  // namespace phonehub
}  // namespace ash