chromium/components/omnibox/browser/autocomplete_match_android.cc

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

#include "components/omnibox/browser/autocomplete_match.h"

#include <vector>

#include "base/android/callback_android.h"
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/functional/bind.h"
#include "components/omnibox/browser/actions/omnibox_action.h"
#include "components/omnibox/browser/actions/omnibox_action_factory_android.h"
#include "components/omnibox/browser/clipboard_provider.h"
#include "components/omnibox/browser/omnibox_feature_configs.h"
#include "components/omnibox/browser/search_suggestion_parser.h"
#include "url/android/gurl_android.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "components/omnibox/browser/jni_headers/AutocompleteMatch_jni.h"

using base::android::ConvertUTF16ToJavaString;
using base::android::ConvertUTF8ToJavaString;
using base::android::RunRunnableAndroid;
using base::android::ScopedJavaGlobalRef;
using base::android::ScopedJavaLocalRef;
using base::android::ToJavaArrayOfStrings;
using base::android::ToJavaByteArray;
using base::android::ToJavaIntArray;

// static
jclass AutocompleteMatch::GetClazz(JNIEnv* env) {
  return org_chromium_components_omnibox_AutocompleteMatch_clazz(env);
}

ScopedJavaLocalRef<jobject> AutocompleteMatch::GetOrCreateJavaObject(
    JNIEnv* env) const {
  // Short circuit if we already built the match.
  if (java_match_)
    return ScopedJavaLocalRef<jobject>(*java_match_);

  std::vector<int> contents_class_offsets;
  std::vector<int> contents_class_styles;
  for (auto contents_class_item : contents_class) {
    contents_class_offsets.push_back(contents_class_item.offset);
    contents_class_styles.push_back(contents_class_item.style);
  }

  std::vector<int> description_class_offsets;
  std::vector<int> description_class_styles;
  for (auto description_class_item : description_class) {
    description_class_offsets.push_back(description_class_item.offset);
    description_class_styles.push_back(description_class_item.style);
  }

  base::android::ScopedJavaLocalRef<jobject> janswer;
  if (answer)
    janswer = answer->CreateJavaObject(answer_type);

  ScopedJavaLocalRef<jbyteArray> j_answer_template;
  if (answer_template) {
    std::string str_answer_template;
    if (answer_template->SerializeToString(&str_answer_template)) {
      j_answer_template =
          base::android::ToJavaByteArray(env, str_answer_template);
    }
  }

  ScopedJavaLocalRef<jstring> j_image_dominant_color;
  ScopedJavaLocalRef<jstring> j_post_content_type;
  ScopedJavaLocalRef<jbyteArray> j_post_content;
  std::string clipboard_image_data;

  if (!image_dominant_color.empty()) {
    j_image_dominant_color = ConvertUTF8ToJavaString(env, image_dominant_color);
  }

  if (post_content && !post_content->first.empty() &&
      !post_content->second.empty()) {
    j_post_content_type = ConvertUTF8ToJavaString(env, post_content->first);
    j_post_content = ToJavaByteArray(env, post_content->second);
  }

  if (search_terms_args.get()) {
    clipboard_image_data = search_terms_args->image_thumbnail_content;
  }

  std::vector<int> temp_subtypes(subtypes.begin(), subtypes.end());

  std::vector<jni_zero::ScopedJavaLocalRef<jobject>> actions_list;
  if (actions.empty() && takeover_action) {
    actions_list = ToJavaOmniboxActionsList(env, {takeover_action});
  } else {
    actions_list = ToJavaOmniboxActionsList(env, actions);
  }

  java_match_ = std::make_unique<ScopedJavaGlobalRef<jobject>>(
      Java_AutocompleteMatch_build(
          env, reinterpret_cast<intptr_t>(this), type,
          ToJavaIntArray(env, temp_subtypes), IsSearchType(type), relevance,
          transition, ConvertUTF16ToJavaString(env, contents),
          ToJavaIntArray(env, contents_class_offsets),
          ToJavaIntArray(env, contents_class_styles),
          ConvertUTF16ToJavaString(env, description),
          ToJavaIntArray(env, description_class_offsets),
          ToJavaIntArray(env, description_class_styles), janswer,
          j_answer_template, answer_type,
          ConvertUTF16ToJavaString(env, fill_into_edit),
          url::GURLAndroid::FromNativeGURL(env, destination_url),
          url::GURLAndroid::FromNativeGURL(env, image_url),
          j_image_dominant_color, SupportsDeletion(), j_post_content_type,
          j_post_content, suggestion_group_id.value_or(omnibox::GROUP_INVALID),
          ToJavaByteArray(env, clipboard_image_data),
          has_tab_match.value_or(false), actions_list,
          allowed_to_be_default_match,
          ConvertUTF16ToJavaString(env, inline_autocompletion),
          ConvertUTF16ToJavaString(env, additional_text)));

  return ScopedJavaLocalRef<jobject>(*java_match_);
}

void AutocompleteMatch::UpdateJavaObjectNativeRef() {
  if (!java_match_)
    return;

  JNIEnv* env = base::android::AttachCurrentThread();
  Java_AutocompleteMatch_updateNativeObjectRef(
      env, *java_match_, reinterpret_cast<intptr_t>(this));
}

void AutocompleteMatch::DestroyJavaObject() {
  if (!java_match_)
    return;

  JNIEnv* env = base::android::AttachCurrentThread();
  Java_AutocompleteMatch_destroy(env, *java_match_);
  java_match_.reset();
}

void AutocompleteMatch::UpdateWithClipboardContent(
    JNIEnv* env,
    const base::android::JavaParamRef<jobject>& j_callback) {
  DCHECK(provider) << "No provider available";
  DCHECK(provider->type() == AutocompleteProvider::TYPE_CLIPBOARD)
      << "Invalid provider type: " << provider->type();

  ClipboardProvider* clipboard_provider =
      static_cast<ClipboardProvider*>(provider);
  clipboard_provider->UpdateClipboardMatchWithContent(
      this,
      base::BindOnce(&AutocompleteMatch::OnClipboardSuggestionContentUpdated,
                     weak_ptr_factory_.GetWeakPtr(),
                     base::android::ScopedJavaGlobalRef<jobject>(j_callback)));
}

void AutocompleteMatch::OnClipboardSuggestionContentUpdated(
    const base::android::JavaRef<jobject>& j_callback) {
  JNIEnv* env = base::android::AttachCurrentThread();
  UpdateClipboardContent(env);
  RunRunnableAndroid(j_callback);
}

void AutocompleteMatch::UpdateMatchingJavaTab(
    const JavaObjectWeakGlobalRef& tab) {
  matching_java_tab_ = tab;

  // Default state is: we don't have a matching tab. If that default state has
  // changed, reflect it in the UI.
  // TODO(crbug.com/40204147): when Tab.java is relocated to Components, pass
  // the Tab object directly to Java. This is not possible right now due to
  // //components being explicitly denied to depend on //chrome targets.
  if (!java_match_ || !has_tab_match.value_or(false))
    return;

  JNIEnv* env = base::android::AttachCurrentThread();
  Java_AutocompleteMatch_updateMatchingTab(env, *java_match_, true);
}

JavaObjectWeakGlobalRef AutocompleteMatch::GetMatchingJavaTab() const {
  return matching_java_tab_;
}

void AutocompleteMatch::UpdateClipboardContent(JNIEnv* env) {
  if (!java_match_)
    return;

  std::string clipboard_image_data;
  if (search_terms_args.get()) {
    clipboard_image_data = search_terms_args->image_thumbnail_content;
  }

  ScopedJavaLocalRef<jstring> j_post_content_type;
  ScopedJavaLocalRef<jbyteArray> j_post_content;
  if (post_content && !post_content->first.empty() &&
      !post_content->second.empty()) {
    j_post_content_type = ConvertUTF8ToJavaString(env, post_content->first);
    j_post_content = ToJavaByteArray(env, post_content->second);
  }

  Java_AutocompleteMatch_updateClipboardContent(
      env, *java_match_, ConvertUTF16ToJavaString(env, contents),
      url::GURLAndroid::FromNativeGURL(env, destination_url),
      j_post_content_type, j_post_content,
      ToJavaByteArray(env, clipboard_image_data));
}

void AutocompleteMatch::UpdateJavaDestinationUrl() {
  if (java_match_) {
    JNIEnv* env = base::android::AttachCurrentThread();
    Java_AutocompleteMatch_setDestinationUrl(
        env, *java_match_,
        url::GURLAndroid::FromNativeGURL(env, destination_url));
  }
}

void AutocompleteMatch::UpdateJavaAnswer() {
  if (java_match_) {
    JNIEnv* env = base::android::AttachCurrentThread();
    if (omnibox_feature_configs::SuggestionAnswerMigration::Get().enabled) {
      ScopedJavaLocalRef<jbyteArray> j_answer_template;
      if (answer_template) {
        std::string str_answer_template;
        if (answer_template->SerializeToString(&str_answer_template)) {
          j_answer_template =
              base::android::ToJavaByteArray(env, str_answer_template);
        }
      }
      Java_AutocompleteMatch_setAnswerTemplate(
          env, *java_match_, answer_template ? j_answer_template : nullptr);
    } else {
      Java_AutocompleteMatch_setAnswer(
          env, *java_match_,
          answer ? answer->CreateJavaObject(answer_type) : nullptr);
    }
    Java_AutocompleteMatch_setAnswerType(env, *java_match_, answer_type);
  }
}

void AutocompleteMatch::UpdateJavaDescription() {
  if (java_match_) {
    std::vector<int> description_class_offsets;
    std::vector<int> description_class_styles;
    for (auto description_class_item : description_class) {
      description_class_offsets.push_back(description_class_item.offset);
      description_class_styles.push_back(description_class_item.style);
    }
    JNIEnv* env = base::android::AttachCurrentThread();
    Java_AutocompleteMatch_setDescription(
        env, *java_match_, ConvertUTF16ToJavaString(env, description),
        ToJavaIntArray(env, description_class_offsets),
        ToJavaIntArray(env, description_class_styles));
  }
}