chromium/chrome/browser/ash/app_list/search/local_image_search/image_content_annotator.cc

// 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 "chrome/browser/ash/app_list/search/local_image_search/image_content_annotator.h"

#include "base/files/file_path.h"
#include "base/files/memory_mapped_file.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "chromeos/services/machine_learning/public/cpp/service_connection.h"

namespace app_list {
namespace {

using ImageAnnotationResultPtr =
    ::chromeos::machine_learning::mojom::ImageAnnotationResultPtr;

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class Status {
  kOk = 0,
  kModelSpecError = 1,
  kLoadModelError = 2,
  kFeatureNotSupportedError = 3,
  kLanguageNotSupportedError = 4,
  kMaxValue = kLanguageNotSupportedError,
};

void LogStatusUma(Status status) {
  base::UmaHistogramEnumeration(
      "Apps.AppList.AnnotationStorage.ImageAnnotationWorker."
      "ImageContentAnnotator.Status",
      status);
}

}  // namespace

ImageContentAnnotator::ImageContentAnnotator() {
  DETACH_FROM_SEQUENCE(sequence_checker_);
}

ImageContentAnnotator::~ImageContentAnnotator() = default;

bool ImageContentAnnotator::IsDlcInitialized() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return ica_dlc_initialized_;
}

void ImageContentAnnotator::EnsureAnnotatorIsConnected() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (ml_service_.is_bound() && image_content_annotator_.is_bound()) {
    return;
  }

  if (!ml_service_.is_bound()) {
    chromeos::machine_learning::ServiceConnection::GetInstance()
        ->BindMachineLearningService(ml_service_.BindNewPipeAndPassReceiver());
    ml_service_.reset_on_disconnect();
  }

  if (!image_content_annotator_.is_bound()) {
    ConnectToImageAnnotator();
    image_content_annotator_.reset_on_disconnect();
  }
}

void ImageContentAnnotator::ConnectToImageAnnotator() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  auto config = chromeos::machine_learning::mojom::ImageAnnotatorConfig::New();
  config->locale = "en-US";

  DVLOG(1) << "Binding ICA.";
  ml_service_->LoadImageAnnotator(
      std::move(config), image_content_annotator_.BindNewPipeAndPassReceiver(),
      base::BindOnce(
          [](bool* ica_dlc_initialized, int* num_retries_passed,
             const chromeos::machine_learning::mojom::LoadModelResult result) {
            DVLOG(1) << result;
            if (result ==
                chromeos::machine_learning::mojom::LoadModelResult::OK) {
              DVLOG(1) << "ICA bind is done.";
              LogStatusUma(Status::kOk);
              // Tracks how many retries have been done before the DLC is ready.
              base::UmaHistogramExactLinear(
                  "Apps.AppList.AnnotationStorage.ImageAnnotationWorker."
                  "ImageContentAnnotator.NumberOfRetries",
                  *num_retries_passed, 20);
              *ica_dlc_initialized = true;
              return;
            } else if (result == chromeos::machine_learning::mojom::
                                     LoadModelResult::MODEL_SPEC_ERROR) {
              LOG(ERROR) << "Model spec error.";
              LogStatusUma(Status::kModelSpecError);
            } else if (result == chromeos::machine_learning::mojom::
                                     LoadModelResult::LOAD_MODEL_ERROR) {
              LOG(ERROR) << "Load model error.";
              LogStatusUma(Status::kLoadModelError);
            } else if (result ==
                       chromeos::machine_learning::mojom::LoadModelResult::
                           LANGUAGE_NOT_SUPPORTED_ERROR) {
              LOG(ERROR) << "Language not supported error.";
              LogStatusUma(Status::kLanguageNotSupportedError);
            } else {
              NOTREACHED_IN_MIGRATION()
                  << "Implement logging for new error codes.";
            }
            *ica_dlc_initialized = false;
          },
          &ica_dlc_initialized_, &num_retries_passed_));
}

void ImageContentAnnotator::DisconnectAnnotator() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  image_content_annotator_.reset();
}

void ImageContentAnnotator::AnnotateEncodedImage(
    const base::FilePath& image_path,
    base::OnceCallback<void(ImageAnnotationResultPtr)> callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DVLOG(1) << "Making a MemoryMappedFile.";
  base::MemoryMappedFile data;
  if (!data.Initialize(image_path)) {
    LOG(ERROR) << "Could not create a memory mapped file for an "
                  "image file to generate annotations";
  }
  base::MappedReadOnlyRegion mapped_region =
      base::ReadOnlySharedMemoryRegion::Create(data.length());
  // It's safe to early return here as the caller function
  // `ImageAnnotationWorker::OnDecodeImageFile()` has started the
  // `timeout_timer_` and it will continue the process when the timer gets
  // timeout.
  if (!mapped_region.IsValid()) {
    return;
  }
  base::span(mapped_region.mapping).copy_from(data.bytes());

  EnsureAnnotatorIsConnected();
  image_content_annotator_->AnnotateEncodedImage(
      std::move(mapped_region.region), std::move(callback));
}

}  // namespace app_list