// 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_result.h"
#include <stdint.h>
#include <vector>
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/containers/contains.h"
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.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/AutocompleteResult_jni.h"
using base::android::JavaParamRef;
using base::android::ScopedJavaLocalRef;
using base::android::ToJavaBooleanArray;
using base::android::ToJavaByteArray;
using base::android::ToJavaIntArray;
namespace {
// Special value passed to VerifyCoherency() suggesting that the action
// requesting verification has no specific index associated with it.
constexpr const int kNoMatchIndex = -1;
// Used for histograms, append only.
enum class MatchVerificationResult {
VALID_MATCH = 0,
WRONG_MATCH = 1,
BAD_RESULT_SIZE = 2,
OBSOLETE_NATIVE_MATCH_DEAD = 3,
INVALID_MATCH_POSITION = 4,
// Keep as the last entry:
COUNT
};
enum class MatchVerificationPoint {
INVALID = 0,
SELECT_MATCH = 1,
UPDATE_MATCH = 2,
DELETE_MATCH = 3,
GROUP_BY_SEARCH_VS_URL_BEFORE = 4,
GROUP_BY_SEARCH_VS_URL_AFTER = 5,
ON_TOUCH_MATCH = 6,
GET_MATCHING_TAB = 7,
};
const char* MatchVerificationPointToString(int verification_point) {
switch (static_cast<MatchVerificationPoint>(verification_point)) {
case MatchVerificationPoint::SELECT_MATCH:
return "Select";
case MatchVerificationPoint::UPDATE_MATCH:
return "Update";
case MatchVerificationPoint::DELETE_MATCH:
return "Delete";
case MatchVerificationPoint::GROUP_BY_SEARCH_VS_URL_BEFORE:
return "Group/Before";
case MatchVerificationPoint::GROUP_BY_SEARCH_VS_URL_AFTER:
return "Group/After";
case MatchVerificationPoint::ON_TOUCH_MATCH:
return "OnTouch";
case MatchVerificationPoint::GET_MATCHING_TAB:
return "GetMatchingTab";
case MatchVerificationPoint::INVALID:
return "Invalid";
}
NOTREACHED_IN_MIGRATION();
}
bool sInvalidMatchMetricsUploaded = false;
void ReportInvalidMatchData(std::string debug_info, int verification_point) {
if (sInvalidMatchMetricsUploaded)
return;
sInvalidMatchMetricsUploaded = true;
SCOPED_CRASH_KEY_STRING32("ACMatch", "wrong-match-info", debug_info);
SCOPED_CRASH_KEY_STRING32("ACMatch", "verification-point",
MatchVerificationPointToString(verification_point));
base::debug::DumpWithoutCrashing();
}
} // namespace
ScopedJavaLocalRef<jobject> AutocompleteResult::GetOrCreateJavaObject(
JNIEnv* env) const {
// Short circuit if we already built the java object.
if (java_result_)
return ScopedJavaLocalRef<jobject>(java_result_);
const size_t groups_count = suggestion_groups_map().size();
std::vector<int> group_ids(groups_count);
omnibox::GroupsInfo groups_info;
std::string serialized_groups_info;
for (const auto& suggestion_group : suggestion_groups_map()) {
(*groups_info.mutable_group_configs())[suggestion_group.first] =
suggestion_group.second;
}
if (!groups_info.SerializeToString(&serialized_groups_info)) {
serialized_groups_info.clear();
}
ScopedJavaLocalRef<jintArray> j_group_ids = ToJavaIntArray(env, group_ids);
java_result_ = Java_AutocompleteResult_fromNative(
env, reinterpret_cast<intptr_t>(this), BuildJavaMatches(env),
ToJavaByteArray(env, serialized_groups_info));
return ScopedJavaLocalRef<jobject>(java_result_);
}
void AutocompleteResult::DestroyJavaObject() const {
if (!java_result_)
return;
JNIEnv* env = base::android::AttachCurrentThread();
Java_AutocompleteResult_notifyNativeDestroyed(env, java_result_);
java_result_.Reset();
}
ScopedJavaLocalRef<jobjectArray> AutocompleteResult::BuildJavaMatches(
JNIEnv* env) const {
jclass clazz = AutocompleteMatch::GetClazz(env);
ScopedJavaLocalRef<jobjectArray> j_matches(
env, env->NewObjectArray(matches_.size(), clazz, nullptr));
base::android::CheckException(env);
for (size_t index = 0; index < matches_.size(); ++index) {
env->SetObjectArrayElement(
j_matches.obj(), index,
matches_[index].GetOrCreateJavaObject(env).obj());
}
return j_matches;
}
bool AutocompleteResult::VerifyCoherency(
JNIEnv* env,
const JavaParamRef<jlongArray>& j_matches_array,
jint match_index,
jint verification_point) {
DCHECK(j_matches_array);
std::vector<jlong> j_matches;
base::android::JavaLongArrayToLongVector(env, j_matches_array, &j_matches);
if (j_matches.size() != size()) {
UMA_HISTOGRAM_ENUMERATION("Android.Omnibox.InvalidMatch",
MatchVerificationResult::BAD_RESULT_SIZE,
MatchVerificationResult::COUNT);
ReportInvalidMatchData(base::NumberToString(j_matches.size()) +
"!=" + base::NumberToString(size()),
verification_point);
return false;
}
if (match_index != kNoMatchIndex && match_index >= static_cast<int>(size())) {
UMA_HISTOGRAM_ENUMERATION("Android.Omnibox.InvalidMatch",
MatchVerificationResult::INVALID_MATCH_POSITION,
MatchVerificationResult::COUNT);
ReportInvalidMatchData(
base::NumberToString(match_index) + ">=" + base::NumberToString(size()),
verification_point);
return false;
}
for (auto index = 0u; index < size(); index++) {
if (reinterpret_cast<intptr_t>(match_at(index)) != j_matches[index]) {
UMA_HISTOGRAM_ENUMERATION("Android.Omnibox.InvalidMatch",
MatchVerificationResult::WRONG_MATCH,
MatchVerificationResult::COUNT);
// Note: the NDEBUG is defined for release / debug-disabled builds.
#ifndef NDEBUG
// Print the list of matches at every position on each side.
// Used for debugging purposes.
for (auto i = 0u; i < size(); i++) {
auto* this_match = match_at(i);
auto* other_match = reinterpret_cast<AutocompleteMatch*>(j_matches[i]);
DLOG(WARNING) << "Suggestion at index " << i << ": "
<< "(Native): " << this_match->fill_into_edit
<< "(Java): "
<< (other_match ? other_match->fill_into_edit
: u"<null>");
}
#endif
ReportInvalidMatchData(
base::NumberToString(index) + "/" + base::NumberToString(size()),
verification_point);
return false;
}
}
UMA_HISTOGRAM_ENUMERATION("Android.Omnibox.InvalidMatch",
MatchVerificationResult::VALID_MATCH,
MatchVerificationResult::COUNT);
return true;
}