| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // 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/metrics/histogram_macros.h" |
| #include "components/omnibox/browser/jni_headers/AutocompleteResult_jni.h" |
| #include "components/omnibox/browser/search_suggestion_parser.h" |
| #include "components/query_tiles/android/tile_conversion_bridge.h" |
| #include "url/android/gurl_android.h" |
| |
| using base::android::JavaParamRef; |
| using base::android::ScopedJavaLocalRef; |
| using base::android::ToJavaArrayOfStrings; |
| using base::android::ToJavaBooleanArray; |
| using base::android::ToJavaIntArray; |
| |
| namespace { |
| // Used for histograms, append only. |
| enum class MatchVerificationResult { |
| VALID_MATCH = 0, |
| WRONG_MATCH = 1, |
| BAD_RESULT_SIZE = 2, |
| // Keep as the last entry: |
| COUNT |
| }; |
| } // 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 = headers_map_.size(); |
| |
| std::vector<int> group_ids(groups_count); |
| std::vector<std::u16string> group_names(groups_count); |
| bool group_collapsed_states[groups_count]; |
| |
| size_t index = 0; |
| for (const auto& group_header : headers_map_) { |
| group_ids[index] = group_header.first; |
| group_names[index] = group_header.second; |
| group_collapsed_states[index] = |
| base::Contains(hidden_group_ids_, group_header.first); |
| ++index; |
| } |
| |
| ScopedJavaLocalRef<jintArray> j_group_ids = ToJavaIntArray(env, group_ids); |
| ScopedJavaLocalRef<jbooleanArray> j_group_collapsed_states = |
| ToJavaBooleanArray(env, group_collapsed_states, groups_count); |
| ScopedJavaLocalRef<jobjectArray> j_group_names = |
| ToJavaArrayOfStrings(env, group_names); |
| |
| java_result_ = Java_AutocompleteResult_fromNative( |
| env, reinterpret_cast<intptr_t>(this), BuildJavaMatches(env), j_group_ids, |
| j_group_names, j_group_collapsed_states); |
| |
| 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; |
| } |
| |
| void AutocompleteResult::GroupSuggestionsBySearchVsURL(JNIEnv* env, |
| int first_index, |
| int last_index) { |
| if (first_index == last_index) |
| return; |
| const int num_elements = matches_.size(); |
| if (first_index < 0 || last_index <= first_index || |
| last_index > num_elements) { |
| DCHECK(false) << "Range [" << first_index << "; " << last_index |
| << ") is not valid for grouping; accepted range: [0; " |
| << num_elements << ")."; |
| return; |
| } |
| |
| auto range_start = const_cast<ACMatches&>(matches_).begin(); |
| GroupSuggestionsBySearchVsURL(range_start + first_index, |
| range_start + last_index); |
| Java_AutocompleteResult_updateMatches(env, java_result_, |
| BuildJavaMatches(env)); |
| } |
| |
| bool AutocompleteResult::VerifyCoherency( |
| JNIEnv* env, |
| const JavaParamRef<jlongArray>& j_matches_array) { |
| 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); |
| NOTREACHED() << "AutocompletResult objects are of different size: " |
| << j_matches.size() << " (Java) vs " << size() << " (Native)"; |
| 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 |
| NOTREACHED() |
| << "AutocompleteMatch mismatch with native-sourced suggestions at " |
| << index; |
| return false; |
| } |
| } |
| |
| UMA_HISTOGRAM_ENUMERATION("Android.Omnibox.InvalidMatch", |
| MatchVerificationResult::VALID_MATCH, |
| MatchVerificationResult::COUNT); |
| return true; |
| } |