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

#include "ui/base/clipboard/clipboard_android.h"

#include <algorithm>
#include <cstdint>
#include <map>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "base/base64.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/ref_counted_memory.h"
#include "base/no_destructor.h"
#include "base/notimplemented.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/string_view_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h"
#include "base/task/thread_pool.h"
#include "base/thread_annotations.h"
#include "base/time/time.h"
#include "base/types/optional_util.h"
#include "third_party/jni_zero/default_conversions.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/clipboard/clipboard_change_notifier.h"
#include "ui/base/clipboard/clipboard_constants.h"
#include "ui/base/clipboard/clipboard_format_type.h"
#include "ui/base/clipboard/clipboard_metrics.h"
#include "ui/base/clipboard/clipboard_monitor.h"
#include "ui/base/clipboard/clipboard_util.h"
#include "ui/base/clipboard/custom_data_helper.h"
#include "ui/base/clipboard_jni_headers/Clipboard_jni.h"
#include "ui/base/data_transfer_policy/data_transfer_endpoint.h"
#include "ui/base/ui_base_features.h"
#include "ui/gfx/android/java_bitmap.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/image/image.h"

// TODO:(andrewhayden) Support additional formats in Android: URI, HTML,
// HTML+text now that Android's clipboard system supports them, then nuke the
// legacy implementation note below.

// Legacy implementation note:
// The Android clipboard, and hence `ClipboardAndroid` as well, used to only
// support text. Since then, bitmap support has been added, but not support
// for any other formats.
//
// Therefore, Clipboard data is stored:
// - on the Android system, for text and bitmaps.
// - in a process-wide static variable protected by a lock, for other formats.
//
// These "other formats" only work within the same process, and can't be copied
// between Android applications.

using base::android::ConvertJavaStringToUTF8;
using base::android::ConvertUTF8ToJavaString;
using base::android::JavaByteArrayToByteVector;
using base::android::ScopedJavaGlobalRef;
using base::android::ScopedJavaLocalRef;
using base::android::ToJavaByteArray;
using jni_zero::AttachCurrentThread;
using jni_zero::ClearException;

namespace ui {

namespace {

constexpr char kPngExtension[] = ".png";

using ReadPngCallback = ClipboardAndroid::ReadPngCallback;

// Fetching image data from Java as PNG bytes.
std::vector<uint8_t> GetPngData(
    const base::android::ScopedJavaGlobalRef<jobject>& clipboard_manager) {
  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jbyteArray> jimage_data =
      Java_Clipboard_getPng(env, clipboard_manager);
  if (jimage_data.is_null()) {
    return std::vector<uint8_t>();
  }
  DCHECK(jimage_data.obj());

  std::vector<uint8_t> png_data;
  JavaByteArrayToByteVector(env, jimage_data, &png_data);
  return png_data;
}

// Add a format:jstr pair to map, if jstr is null or is empty, then remove that
// entry.
static void JNI_Clipboard_AddMapEntry(
    JNIEnv* env,
    std::map<ClipboardFormatType, std::string>* map,
    const ClipboardFormatType& format,
    const ScopedJavaLocalRef<jstring>& jstr) {
  if (jstr.is_null()) {
    map->erase(format);
    return;
  }

  std::string str = ConvertJavaStringToUTF8(env, jstr.obj());
  if (!str.empty()) {
    (*map)[format] = str;
  } else {
    map->erase(format);
  }
}

class ClipboardMap {
 public:
  ClipboardMap();
  void SetModifiedCallback(ClipboardAndroid::ModifiedCallback cb);
  void SetJavaSideNativePtr(Clipboard* clipboard);
  std::string Get(const ClipboardFormatType& format);
  void GetPng(ReadPngCallback callback);
  void DidGetPng(ReadPngCallback callback, std::vector<uint8_t> result);
  const ClipboardSequenceNumberToken& GetSequenceNumber() const;
  base::Time GetLastModifiedTime() const;
  void ClearLastModifiedTime();
  bool HasFormat(const ClipboardFormatType& format);
  base::flat_set<ClipboardFormatType> GetAllAvailableFormats(
      ClipboardBuffer buffer);
  void OnPrimaryClipboardChanged();
  void OnPrimaryClipTimestampInvalidated(base::Time timestamp);
  void Set(const ClipboardFormatType& format, std::string_view data);
  const std::vector<ui::FileInfo>& GetFilenames();
  void SetFilenames(std::vector<ui::FileInfo> filenames);
  void CommitToAndroidClipboard(GURL data_source);
  void GetSource(Clipboard::GetSourceCallback callback);
  void Clear();
  std::vector<std::u16string> GetCustomTypes();
  void MarkPasswordData();

  // Unlike the functions above, does not call |modified_cb_|.
  void SetLastModifiedTimeWithoutRunningCallback(base::Time time);

  void SetCustomClipDataForTesting(std::optional<std::string> data) {
    has_test_custom_data_ = true;
    test_custom_data_ = std::move(data);
  }

 private:
  enum class MapState {
    kOutOfDate,
    kUpToDate,
    kPreparingCommit,
  };

  // Updates |last_modified_time_| to |time| and writes it to |local_state_|.
  void UpdateLastModifiedTime(base::Time time);

  // Updates 'map_' and 'map_state_' if necessary by fetching data from Java.
  // Start from Android S, accessing the system clipboard will cause a
  // pop up notification showing that the app copied content from clipboard. To
  // avoid that, This function should only be called when we really need to read
  // the data.
  void UpdateFromAndroidClipboard();
  void ReadWebCustomData(JNIEnv* env);
  std::optional<std::string> GetCustomClipData(
      JNIEnv* env,
      const std::string& custom_type_name);

  std::map<ClipboardFormatType, std::string> map_ GUARDED_BY(lock_);
  MapState map_state_;
  bool has_test_custom_data_ = false;
  std::optional<std::string> test_custom_data_;
  std::vector<ui::FileInfo> filenames_;

  // This lock is for read/write |map_|.
  base::Lock lock_;

  ClipboardSequenceNumberToken sequence_number_;
  base::Time last_modified_time_;
  bool mark_password_data_ = false;

  ClipboardAndroid::ModifiedCallback modified_cb_;

  // Java class and methods for the Android ClipboardManager.
  ScopedJavaGlobalRef<jobject> clipboard_manager_;
};

ClipboardMap& GetClipboardMap() {
  static base::NoDestructor<ClipboardMap> map;
  return *map;
}

ClipboardMap::ClipboardMap() : map_state_(MapState::kOutOfDate) {
  clipboard_manager_.Reset(Java_Clipboard_getInstance(AttachCurrentThread()));
  DCHECK(clipboard_manager_.obj());
}

void ClipboardMap::SetModifiedCallback(ClipboardAndroid::ModifiedCallback cb) {
  modified_cb_ = std::move(cb);
}

void ClipboardMap::SetJavaSideNativePtr(Clipboard* clipboard) {
  JNIEnv* env = AttachCurrentThread();
  Java_Clipboard_setNativePtr(env, clipboard_manager_,
                              reinterpret_cast<intptr_t>(clipboard));
}

std::string ClipboardMap::Get(const ClipboardFormatType& format) {
  base::AutoLock lock(lock_);
  UpdateFromAndroidClipboard();
  auto it = map_.find(format);
  return it == map_.end() ? std::string() : it->second;
}

void ClipboardMap::GetPng(ReadPngCallback callback) {
  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
      base::BindOnce(&GetPngData, clipboard_manager_),
      base::BindOnce(&ClipboardMap::DidGetPng, base::Unretained(this),
                     std::move(callback)));
}

void ClipboardMap::DidGetPng(ReadPngCallback callback,
                             std::vector<uint8_t> result) {
  // GetPngData attempts to read from the Java Clipboard, which sometimes is
  // not available (ex. the app is not in focus, such as in unit tests).
  if (!result.empty()) {
    std::move(callback).Run(std::move(result));
    return;
  }

  // Since the The Java Clipboard did not provide a valid bitmap, attempt to
  // read from our in-memory clipboard map if the map is up-to-date.
  if (map_state_ != MapState::kUpToDate) {
    std::move(callback).Run(std::vector<uint8_t>());
    return;
  }
  std::string png_str = GetClipboardMap().Get(ClipboardFormatType::PngType());
  std::vector<uint8_t> png_data{png_str.begin(), png_str.end()};
  std::move(callback).Run(png_data);
}

const ClipboardSequenceNumberToken& ClipboardMap::GetSequenceNumber() const {
  return sequence_number_;
}

base::Time ClipboardMap::GetLastModifiedTime() const {
  return last_modified_time_;
}

void ClipboardMap::ClearLastModifiedTime() {
  UpdateLastModifiedTime(base::Time());
}

bool ClipboardMap::HasFormat(const ClipboardFormatType& format) {
  base::AutoLock lock(lock_);
  if (map_state_ == MapState::kUpToDate) {
    // If the 'map_' is up to date, we can just check with it.
    // Images can be read if either bitmap or PNG types are available.
    if (format == ClipboardFormatType::PngType() ||
        format == ClipboardFormatType::BitmapType()) {
      return map_.contains(ClipboardFormatType::PngType()) ||
             map_.contains(ClipboardFormatType::BitmapType());
    }
    // Files are stored outside of `map_` in `filenames_`.
    if (format == ClipboardFormatType::FilenamesType()) {
      return !filenames_.empty();
    }
    return map_.contains(format);
  }

  // If the 'map_' is not up to date, we need to check with the system if the
  // formats are supported by Android clipboard.
  // We will not update the map from here since update the map will update both
  // formats and data, but we only need format info here. Also, reading data
  // from the system clipboard will cause clipboard access notification popping
  // up.
  JNIEnv* env = AttachCurrentThread();
  // TODO(crbug.com/40175699): Create a single method for the follow JNI calls.
  if (format == ClipboardFormatType::PlainTextType()) {
    return Java_Clipboard_hasCoercedText(env, clipboard_manager_);
  } else if (format == ClipboardFormatType::HtmlType()) {
    return Java_Clipboard_hasHTMLOrStyledText(env, clipboard_manager_);
  } else if (format == ClipboardFormatType::UrlType()) {
    return Java_Clipboard_hasUrl(env, clipboard_manager_);
  } else if (format == ClipboardFormatType::PngType() ||
             format == ClipboardFormatType::BitmapType()) {
    return Java_Clipboard_hasImage(env, clipboard_manager_);
  } else if (format == ClipboardFormatType::FilenamesType()) {
    return Java_Clipboard_hasFilenames(env, clipboard_manager_);
  }

  // Android unsupported format types, check local only.
  return map_.contains(format);
}

base::flat_set<ClipboardFormatType> ClipboardMap::GetAllAvailableFormats(
    ClipboardBuffer buffer) {
  base::AutoLock lock(lock_);
  base::flat_set<ClipboardFormatType> formats;
  if (map_state_ == MapState::kUpToDate) {
    for (const auto& entry : map_) {
      formats.insert(entry.first);
    }
    // Images can be read if either bitmap or PNG types are available.
    if (map_.contains(ClipboardFormatType::PngType()) ||
        map_.contains(ClipboardFormatType::BitmapType())) {
      formats.insert(ClipboardFormatType::PngType());
      formats.insert(ClipboardFormatType::BitmapType());
    }
    if (!filenames_.empty()) {
      formats.insert(ClipboardFormatType::FilenamesType());
    }
    return formats;
  }

  JNIEnv* env = AttachCurrentThread();
  if (Java_Clipboard_hasCoercedText(env, clipboard_manager_)) {
    formats.insert(ClipboardFormatType::PlainTextType());
  }
  if (Java_Clipboard_hasHTMLOrStyledText(env, clipboard_manager_)) {
    formats.insert(ClipboardFormatType::HtmlType());
  }
  if (Java_Clipboard_hasUrl(env, clipboard_manager_)) {
    formats.insert(ClipboardFormatType::UrlType());
  }
  if (Java_Clipboard_hasImage(env, clipboard_manager_)) {
    formats.insert(ClipboardFormatType::PngType());
    formats.insert(ClipboardFormatType::BitmapType());
  }
  if (Java_Clipboard_hasFilenames(env, clipboard_manager_)) {
    formats.insert(ClipboardFormatType::FilenamesType());
  }

  for (const auto& entry : map_) {
    formats.insert(entry.first);
  }

  return formats;
}

void ClipboardMap::OnPrimaryClipboardChanged() {
  sequence_number_ = ClipboardSequenceNumberToken();
  UpdateLastModifiedTime(base::Time::Now());
  map_state_ = MapState::kOutOfDate;
}

void ClipboardMap::OnPrimaryClipTimestampInvalidated(base::Time timestamp) {
  sequence_number_ = ClipboardSequenceNumberToken();
  UpdateLastModifiedTime(timestamp);
  map_state_ = MapState::kOutOfDate;
}

void ClipboardMap::Set(const ClipboardFormatType& format,
                       std::string_view data) {
  base::AutoLock lock(lock_);
  map_[format] = data;
  map_state_ = MapState::kPreparingCommit;
}

const std::vector<ui::FileInfo>& ClipboardMap::GetFilenames() {
  base::AutoLock lock(lock_);
  UpdateFromAndroidClipboard();
  return filenames_;
}

void ClipboardMap::SetFilenames(std::vector<ui::FileInfo> filenames) {
  filenames_ = std::move(filenames);
}

void ClipboardMap::CommitToAndroidClipboard(GURL data_source) {
  JNIEnv* env = AttachCurrentThread();
  base::AutoLock lock(lock_);

  bool add_data_source = data_source.is_valid();
  if (mark_password_data_ &&
      map_.contains(ClipboardFormatType::PlainTextType())) {
    ScopedJavaLocalRef<jstring> str = ConvertUTF8ToJavaString(
        env, map_[ClipboardFormatType::PlainTextType()]);
    DCHECK(str.obj());
    Java_Clipboard_setPassword(env, clipboard_manager_, str);
    mark_password_data_ = false;
  } else if (map_.contains(ClipboardFormatType::PngType())) {
    // Committing the PNG data to the Android clipboard will create an image
    // with a corresponding URI. Once this has been created, update the local
    // clipboard with this URI.
    ScopedJavaLocalRef<jbyteArray> image_data =
        ToJavaByteArray(env, map_[ClipboardFormatType::PngType()]);
    ScopedJavaLocalRef<jstring> image_extension =
        ConvertUTF8ToJavaString(env, kPngExtension);
    DCHECK(image_data.obj());
    // TODO(crbug.com/40187527) In unit tests, `jimageuri` is empty.
    Java_Clipboard_setImage(env, clipboard_manager_, image_data,
                            image_extension);
    ScopedJavaLocalRef<jstring> jimageuri =
        Java_Clipboard_getImageUriString(env, clipboard_manager_);
    JNI_Clipboard_AddMapEntry(env, &map_, ClipboardFormatType::BitmapType(),
                              jimageuri);
  } else if (!filenames_.empty()) {
    // Files are stored outside of `map_` in `filenames_`.
    std::vector<std::string> paths;
    for (const auto& filename : filenames_) {
      paths.push_back(filename.path.value());
    }
    ScopedJavaLocalRef<jobjectArray> arr =
        base::android::ToJavaArrayOfStrings(env, paths);
    DCHECK(arr.obj());
    Java_Clipboard_setFilenames(env, clipboard_manager_, arr);
  } else {
    // Collect PlainTextType, HtmlType and DataTransferCustomType from map_.
    std::map<std::string, std::string> text_data;
    for (const auto& entry : map_) {
      if (entry.first == ClipboardFormatType::PlainTextType() ||
          entry.first == ClipboardFormatType::HtmlType()) {
        text_data[entry.first.GetName()] = entry.second;
      } else if (entry.first == ClipboardFormatType::DataTransferCustomType()) {
        text_data[entry.first.GetName()] =
            base::Base64Encode(base::as_byte_span(entry.second));
      }
    }

    if (!text_data.empty()) {
      Java_Clipboard_setClipboardText(env, clipboard_manager_, text_data);
    } else {
      Java_Clipboard_clear(env, clipboard_manager_);
      NOTIMPLEMENTED();
      add_data_source = false;
    }
  }

  if (add_data_source) {
    map_[ClipboardFormatType::InternalSourceUrlType()] = data_source.spec();
  } else {
    map_.erase(ClipboardFormatType::InternalSourceUrlType());
  }

  map_state_ = MapState::kUpToDate;
  sequence_number_ = ClipboardSequenceNumberToken();
  UpdateLastModifiedTime(base::Time::Now());
}

void ClipboardMap::GetSource(Clipboard::GetSourceCallback callback) {
  base::AutoLock lock(lock_);

  auto iter = map_.find(ClipboardFormatType::InternalSourceUrlType());
  if (iter == map_.end()) {
    std::move(callback).Run(std::nullopt);
    return;
  }

  std::move(callback).Run(DataTransferEndpoint(GURL(iter->second)));
}

void ClipboardMap::Clear() {
  JNIEnv* env = AttachCurrentThread();
  base::AutoLock lock(lock_);
  map_.clear();
  filenames_.clear();
  Java_Clipboard_clear(env, clipboard_manager_);
  map_state_ = MapState::kUpToDate;
  sequence_number_ = ClipboardSequenceNumberToken();
  UpdateLastModifiedTime(base::Time::Now());
}

std::vector<std::u16string> ClipboardMap::GetCustomTypes() {
  base::AutoLock lock(lock_);
  // Ensure map_ is up-to-date with Android system clipboard.
  UpdateFromAndroidClipboard();
  std::vector<std::u16string> types;
  if (auto it = map_.find(ClipboardFormatType::DataTransferCustomType());
      it != map_.end()) {
    std::vector<std::u16string> custom_types;
    ui::ReadCustomDataTypes(base::as_byte_span(it->second), &custom_types);
    for (const auto& type : custom_types) {
      if (std::find(types.begin(), types.end(), type) == types.end()) {
        types.push_back(type);
      }
    }
  }
  return types;
}

void ClipboardMap::MarkPasswordData() {
  mark_password_data_ = true;
}

void ClipboardMap::SetLastModifiedTimeWithoutRunningCallback(base::Time time) {
  last_modified_time_ = time;
}

void ClipboardMap::UpdateLastModifiedTime(base::Time time) {
  last_modified_time_ = time;
  // |modified_cb_| may be null in tests.
  if (modified_cb_)
    modified_cb_.Run(time);
}

void ClipboardMap::UpdateFromAndroidClipboard() {
  DCHECK_NE(MapState::kPreparingCommit, map_state_);
  if (map_state_ == MapState::kUpToDate)
    return;

  // Fetch the current Android clipboard state.
  lock_.AssertAcquired();
  JNIEnv* env = AttachCurrentThread();

  ScopedJavaLocalRef<jstring> jtext =
      Java_Clipboard_getCoercedText(env, clipboard_manager_);
  ScopedJavaLocalRef<jstring> jhtml =
      Java_Clipboard_getHTMLText(env, clipboard_manager_);
  ScopedJavaLocalRef<jstring> jurl =
      Java_Clipboard_getUrl(env, clipboard_manager_);
  ScopedJavaLocalRef<jstring> jimageuri =
      Java_Clipboard_getImageUriString(env, clipboard_manager_);
  filenames_.clear();
  std::vector<std::vector<std::string>> filenames;
  base::android::Java2dStringArrayTo2dStringVector(
      env, Java_Clipboard_getFilenames(env, clipboard_manager_), &filenames);
  for (const auto& info : filenames) {
    // The first elemennt is the file path, the second is the display name.
    CHECK_EQ(info.size(), 2u);
    filenames_.emplace_back(base::FilePath(info[0]), base::FilePath(info[1]));
  }

  JNI_Clipboard_AddMapEntry(env, &map_, ClipboardFormatType::PlainTextType(),
                            jtext);
  JNI_Clipboard_AddMapEntry(env, &map_, ClipboardFormatType::HtmlType(), jhtml);
  JNI_Clipboard_AddMapEntry(env, &map_, ClipboardFormatType::UrlType(), jurl);
  JNI_Clipboard_AddMapEntry(env, &map_, ClipboardFormatType::BitmapType(),
                            jimageuri);

  ReadWebCustomData(env);

  map_state_ = MapState::kUpToDate;
}

std::optional<std::string> ClipboardMap::GetCustomClipData(
    JNIEnv* env,
    const std::string& custom_type_name) {
  if (has_test_custom_data_) {
    return test_custom_data_;
  }
  return Java_Clipboard_getCustomClipData(env, clipboard_manager_,
                                          custom_type_name);
}

void ClipboardMap::ReadWebCustomData(JNIEnv* env) {
  lock_.AssertAcquired();
  std::string custom_type_name =
      ClipboardFormatType::DataTransferCustomType().GetName();
  std::optional<std::string> encoded_data =
      GetCustomClipData(env, custom_type_name);
  if (!encoded_data) {
    map_.erase(ClipboardFormatType::DataTransferCustomType());
    return;
  }
  std::string decoded_data;
  if (!base::Base64Decode(*encoded_data, &decoded_data) ||
      decoded_data.empty()) {
    map_.erase(ClipboardFormatType::DataTransferCustomType());
    return;
  }
  map_[ClipboardFormatType::DataTransferCustomType()] = std::move(decoded_data);
}

}  // namespace

// Clipboard factory method.
// static
Clipboard* Clipboard::Create() {
  return new ClipboardAndroid;
}

// Static method for testing.
static void JNI_Clipboard_CleanupForTesting(JNIEnv* env) {
  Clipboard::DestroyClipboardForCurrentThread();
}

// Set custom clip data for testing.
void SetCustomClipDataForTesting(std::optional<std::string> data) {
  GetClipboardMap().SetCustomClipDataForTesting(std::move(data));  // IN-TEST
}

// ClipboardAndroid implementation.

void ClipboardAndroid::OnPrimaryClipChanged(JNIEnv* env) {
  GetClipboardMap().OnPrimaryClipboardChanged();
}

void ClipboardAndroid::OnPrimaryClipTimestampInvalidated(
    JNIEnv* env,
    const int64_t j_timestamp_ms) {
  base::Time timestamp =
      base::Time::FromMillisecondsSinceUnixEpoch(j_timestamp_ms);
  if (GetLastModifiedTime() < timestamp) {
    // If the timestamp is newer than the last modified time, update the
    // sequence number, last modified time and notify the ClipboardMonitor if
    // monitoring external clipboard changes.
    GetClipboardMap().OnPrimaryClipTimestampInvalidated(timestamp);
    if (base::FeatureList::IsEnabled(features::kPlatformClipboardMonitor)) {
      ClipboardMonitor::GetInstance()->NotifyClipboardDataChanged();
    }
  }
}

int64_t ClipboardAndroid::GetLastModifiedTimeToJavaTime(JNIEnv* env) {
  return GetLastModifiedTime().InMillisecondsSinceUnixEpoch();
}

void ClipboardAndroid::SetModifiedCallback(ModifiedCallback cb) {
  GetClipboardMap().SetModifiedCallback(std::move(cb));
}

void ClipboardAndroid::SetLastModifiedTimeWithoutRunningCallback(
    base::Time time) {
  GetClipboardMap().SetLastModifiedTimeWithoutRunningCallback(time);
}

ClipboardAndroid::ClipboardAndroid() {
  DCHECK(CalledOnValidThread());
  GetClipboardMap().SetJavaSideNativePtr(this);
}

ClipboardAndroid::~ClipboardAndroid() {
  DCHECK(CalledOnValidThread());
  GetClipboardMap().SetJavaSideNativePtr(nullptr);
}

void ClipboardAndroid::OnPreShutdown() {}

void ClipboardAndroid::GetSource(ClipboardBuffer buffer,
                                 GetSourceCallback callback) const {
  DCHECK(CalledOnValidThread());
  DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);

  GetClipboardMap().GetSource(std::move(callback));
}

const ClipboardSequenceNumberToken& ClipboardAndroid::GetSequenceNumber(
    ClipboardBuffer /* buffer */) const {
  DCHECK(CalledOnValidThread());
  return GetClipboardMap().GetSequenceNumber();
}

void ClipboardAndroid::GetAllAvailableFormats(
    ClipboardBuffer buffer,
    const std::optional<DataTransferEndpoint>& data_dst,
    base::OnceCallback<void(base::flat_set<ClipboardFormatType>)> callback)
    const {
  DCHECK(CalledOnValidThread());
  DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);
  std::move(callback).Run(GetClipboardMap().GetAllAvailableFormats(buffer));
}

void ClipboardAndroid::GetAvailableFormats(
    ClipboardBuffer buffer,
    std::vector<ClipboardFormatType> formats,
    const std::optional<DataTransferEndpoint>& data_dst,
    base::OnceCallback<void(base::flat_set<ClipboardFormatType>)> callback)
    const {
  DCHECK(CalledOnValidThread());
  DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);
  // Probe only the requested formats.
  base::flat_set<ClipboardFormatType> result;
  auto& clipboard_map = GetClipboardMap();
  std::ranges::copy_if(formats, std::inserter(result, result.end()),
                       [&clipboard_map](const auto& format) {
                         return clipboard_map.HasFormat(format);
                       });
  std::move(callback).Run(std::move(result));
}

void ClipboardAndroid::Clear(ClipboardBuffer buffer) {
  DCHECK(CalledOnValidThread());
  DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);
  GetClipboardMap().Clear();
  ClipboardMonitor::GetInstance()->NotifyClipboardDataChanged();
}

void ClipboardAndroid::GetStandardFormats(
    ClipboardBuffer buffer,
    const std::optional<DataTransferEndpoint>& data_dst,
    GetStandardFormatsCallback callback) const {
  std::vector<std::u16string> types;
  auto available_formats = GetClipboardMap().GetAllAvailableFormats(buffer);
  if (available_formats.contains(ClipboardFormatType::PlainTextType())) {
    types.push_back(kMimeTypePlainText16);
  }
  if (available_formats.contains(ClipboardFormatType::HtmlType())) {
    types.push_back(kMimeTypeHtml16);
  }
  if (available_formats.contains(ClipboardFormatType::SvgType())) {
    types.push_back(kMimeTypeSvg16);
  }
  // We can read images from either the Android clipboard or the local map.
  if (available_formats.contains(ClipboardFormatType::BitmapType()) ||
      available_formats.contains(ClipboardFormatType::PngType())) {
    types.push_back(kMimeTypeImageUri16);
    types.push_back(kMimeTypePng16);
  }
  if (available_formats.contains(ClipboardFormatType::FilenamesType())) {
    types.push_back(kMimeTypeUriList16);
  }
  // these formats aren't supported by the ClipboardMap currently, but might
  // be one day?
  if (available_formats.contains(ClipboardFormatType::RtfType())) {
    types.push_back(kMimeTypeRtf16);
  }
  std::move(callback).Run(std::move(types));
}

// |data_dst| is not used. It's only passed to be consistent with other
// platforms.
void ClipboardAndroid::ReadAvailableTypes(
    ClipboardBuffer buffer,
    const std::optional<DataTransferEndpoint>& data_dst,
    ReadAvailableTypesCallback callback) const {
  DCHECK(CalledOnValidThread());
  DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);

  auto combine_types_callback = base::BindOnce(
      [](ReadAvailableTypesCallback callback,
         std::vector<std::u16string> types) {
        // Add custom types from the system clipboard to enable pasting of custom
        // types.
        std::vector<std::u16string> custom_types =
            GetClipboardMap().GetCustomTypes();
        for (const auto& type : custom_types) {
          if (std::find(types.begin(), types.end(), type) == types.end()) {
            types.push_back(type);
          }
        }
        std::move(callback).Run(std::move(types));
      },
      std::move(callback));

  GetStandardFormats(buffer, data_dst, std::move(combine_types_callback));
}

// |data_dst| is not used. It's only passed to be consistent with other
// platforms.
void ClipboardAndroid::ReadText(
    ClipboardBuffer buffer,
    const std::optional<DataTransferEndpoint>& data_dst,
    ReadTextCallback callback) const {
  DCHECK(CalledOnValidThread());
  DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);
  RecordRead(ClipboardFormatMetric::kText);
  std::move(callback).Run(base::UTF8ToUTF16(
      GetClipboardMap().Get(ClipboardFormatType::PlainTextType())));
}

// |data_dst| is not used. It's only passed to be consistent with other
// platforms.
void ClipboardAndroid::ReadAsciiText(
    ClipboardBuffer buffer,
    const std::optional<DataTransferEndpoint>& data_dst,
    ReadAsciiTextCallback callback) const {
  DCHECK(CalledOnValidThread());
  DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);
  RecordRead(ClipboardFormatMetric::kText);
  std::move(callback).Run(
      GetClipboardMap().Get(ClipboardFormatType::PlainTextType()));
}

// |src_url| isn't really used. It is only implemented in Windows.
// |data_dst| is not used. It's only passed to be consistent with other
// platforms.
void ClipboardAndroid::ReadHTML(
    ClipboardBuffer buffer,
    const std::optional<DataTransferEndpoint>& data_dst,
    ReadHtmlCallback callback) const {
  DCHECK(CalledOnValidThread());
  DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);
  RecordRead(ClipboardFormatMetric::kHtml);

  std::string input = GetClipboardMap().Get(ClipboardFormatType::HtmlType());
  std::u16string markup = base::UTF8ToUTF16(input);

  uint32_t fragment_start = 0;
  uint32_t fragment_end = static_cast<uint32_t>(markup.length());
  std::move(callback).Run(std::move(markup), GURL(), fragment_start,
                          fragment_end);
}

// |data_dst| is not used. It's only passed to be consistent with other
// platforms.
void ClipboardAndroid::ReadSvg(
    ClipboardBuffer buffer,
    const std::optional<DataTransferEndpoint>& data_dst,
    ReadSvgCallback callback) const {
  DCHECK(CalledOnValidThread());
  DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);
  std::string utf8 = GetClipboardMap().Get(ClipboardFormatType::SvgType());
  std::move(callback).Run(base::UTF8ToUTF16(utf8));
}

// |data_dst| is not used. It's only passed to be consistent with other
// platforms.
void ClipboardAndroid::ReadRTF(
    ClipboardBuffer buffer,
    const std::optional<DataTransferEndpoint>& data_dst,
    ReadRTFCallback callback) const {
  DCHECK(CalledOnValidThread());
  NOTIMPLEMENTED();
  std::move(callback).Run("");
}

// |data_dst| is not used. It's only passed to be consistent with other
// platforms.
void ClipboardAndroid::ReadPng(
    ClipboardBuffer buffer,
    const std::optional<DataTransferEndpoint>& data_dst,
    ReadPngCallback callback) const {
  DCHECK(CalledOnValidThread());
  DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);
  RecordRead(ClipboardFormatMetric::kPng);
  GetClipboardMap().GetPng(std::move(callback));
}

// |data_dst| is not used. It's only passed to be consistent with other
// platforms.
void ClipboardAndroid::ReadDataTransferCustomData(
    ClipboardBuffer buffer,
    const std::u16string& type,
    const std::optional<DataTransferEndpoint>& data_dst,
    ReadDataTransferCustomDataCallback callback) const {
  DCHECK(CalledOnValidThread());
  DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);
  RecordRead(ClipboardFormatMetric::kCustomData);

  std::string data =
      GetClipboardMap().Get(ClipboardFormatType::DataTransferCustomType());
  if (!data.empty()) {
    if (std::optional<std::u16string> result =
            ui::ReadCustomDataForType(base::as_byte_span(data), type);
        result) {
      std::move(callback).Run(std::move(*result));
      return;
    }
  }
  std::move(callback).Run(u"");
}

// |data_dst| is not used. It's only passed to be consistent with other
// platforms.
void ClipboardAndroid::ReadFilenames(
    ClipboardBuffer buffer,
    const std::optional<DataTransferEndpoint>& data_dst,
    ReadFilenamesCallback callback) const {
  DCHECK(CalledOnValidThread());
  DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);
  RecordRead(ClipboardFormatMetric::kFilenames);
  std::move(callback).Run(GetClipboardMap().GetFilenames());
}
// 'data_dst' is not used. It's only passed to be consistent with
// other platforms.
void ClipboardAndroid::ReadURL(
    const std::optional<DataTransferEndpoint>& data_dst,
    ReadUrlCallback callback) const {
  DCHECK(CalledOnValidThread());
  RecordRead(ClipboardFormatMetric::kUrl);
  ClipboardUrlInfo url_info;
  url_info.url = GURL(GetClipboardMap().Get(ClipboardFormatType::UrlType()));
  std::move(callback).Run(std::move(url_info));
}

// |data_dst| is not used. It's only passed to be consistent with other
// platforms.
void ClipboardAndroid::ReadData(
    const ClipboardFormatType& format,
    const std::optional<DataTransferEndpoint>& data_dst,
    ReadDataCallback callback) const {
  DCHECK(CalledOnValidThread());
  RecordRead(ClipboardFormatMetric::kData);
  std::move(callback).Run(GetClipboardMap().Get(format));
}

base::Time ClipboardAndroid::GetLastModifiedTime() const {
  DCHECK(CalledOnValidThread());
  return GetClipboardMap().GetLastModifiedTime();
}

void ClipboardAndroid::ClearLastModifiedTime() {
  DCHECK(CalledOnValidThread());
  GetClipboardMap().ClearLastModifiedTime();
}

// Main entry point used to write several values in the clipboard.
void ClipboardAndroid::WritePortableAndPlatformRepresentations(
    ClipboardBuffer buffer,
    const ObjectMap& objects,
    const std::vector<RawData>& raw_objects,
    std::vector<Clipboard::PlatformRepresentation> platform_representations,
    std::unique_ptr<DataTransferEndpoint> data_src,
    uint32_t privacy_types) {
  DCHECK(CalledOnValidThread());
  DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);
  GetClipboardMap().Clear();

  DispatchPlatformRepresentations(std::move(platform_representations));
  for (const auto& object : objects) {
    DispatchPortableRepresentation(object.second);
  }
  for (const auto& raw_object : raw_objects) {
    DispatchPortableRepresentation(raw_object);
  }

  if (privacy_types & Clipboard::PrivacyTypes::kNoDisplay) {
    WriteConfidentialDataForPassword();
  }

  GURL data_source;
  if (data_src && data_src->IsUrlType()) {
    data_source = *data_src->GetURL();
  }

  GetClipboardMap().CommitToAndroidClipboard(std::move(data_source));
  ClipboardMonitor::GetInstance()->NotifyClipboardDataChanged();
}

void ClipboardAndroid::WriteText(std::string_view text) {
  GetClipboardMap().Set(ClipboardFormatType::PlainTextType(), text);
}

void ClipboardAndroid::WriteHTML(
    std::string_view markup,
    std::optional<std::string_view> /* source_url */) {
  GetClipboardMap().Set(ClipboardFormatType::HtmlType(), markup);
}

void ClipboardAndroid::WriteSvg(std::string_view markup) {
  GetClipboardMap().Set(ClipboardFormatType::SvgType(), markup);
}

void ClipboardAndroid::WriteRTF(std::string_view rtf) {
  NOTIMPLEMENTED();
}

void ClipboardAndroid::WriteFilenames(std::vector<ui::FileInfo> filenames) {
  GetClipboardMap().SetFilenames(std::move(filenames));
}

// According to other platforms implementations, this really writes the
// URL spec.
void ClipboardAndroid::WriteURL(const ClipboardUrlInfo& url_info) {
  GetClipboardMap().Set(ClipboardFormatType::UrlType(), url_info.url.spec());
}

// Write an extra flavor that signifies WebKit was the last to modify the
// pasteboard. This flavor has no data.
void ClipboardAndroid::WriteWebSmartPaste() {
  GetClipboardMap().Set(ClipboardFormatType::WebKitSmartPasteType(),
                        std::string());
}

// Encoding SkBitmap to PNG data. Then, |GetClipboardMap()| can commit the PNG
// data to Android system clipboard without encode/decode.
void ClipboardAndroid::WriteBitmap(const SkBitmap& sk_bitmap) {
  // Encode the bitmap to a PNG from the UI thread. Ideally this CPU-intensive
  // encoding operation would be performed on a background thread, but
  // ui::base::Clipboard writes are (unfortunately) synchronous.
  // We could consider making writes async, then moving this image encoding to a
  // background sequence.
  scoped_refptr<base::RefCountedMemory> image_memory =
      gfx::Image::CreateFrom1xBitmap(sk_bitmap).As1xPNGBytes();
  GetClipboardMap().Set(ClipboardFormatType::PngType(),
                        std::string(base::as_string_view(*image_memory)));
}

void ClipboardAndroid::WriteData(const ClipboardFormatType& format,
                                 base::span<const uint8_t> data) {
  GetClipboardMap().Set(
      format,
      std::string(reinterpret_cast<const char*>(data.data()), data.size()));
}

void ClipboardAndroid::WriteConfidentialDataForPassword() {
  // Set the password data that is marked as IS_SENSITIVE.
  GetClipboardMap().MarkPasswordData();
}

}  // namespace ui

DEFINE_JNI(Clipboard)
