diff options
Diffstat (limited to 'chromium/components/autofill/android/provider')
24 files changed, 1036 insertions, 41 deletions
diff --git a/chromium/components/autofill/android/provider/BUILD.gn b/chromium/components/autofill/android/provider/BUILD.gn index bff1710270d..1050f6ad624 100644 --- a/chromium/components/autofill/android/provider/BUILD.gn +++ b/chromium/components/autofill/android/provider/BUILD.gn @@ -5,7 +5,17 @@ import("//build/config/android/rules.gni") import("//build/config/locales.gni") +android_aidl("autofill_aidl") { + import_include = [ "java/src" ] + sources = [ + "java/src/org/chromium/components/autofill_public/IAutofillHintsService.aidl", + "java/src/org/chromium/components/autofill_public/IViewTypeCallback.aidl", + "java/src/org/chromium/components/autofill_public/ViewType.aidl", + ] +} + android_library("java") { + srcjar_deps = [ ":autofill_aidl" ] deps = [ "//base:base_java", "//base:jni_java", @@ -13,17 +23,19 @@ android_library("java") { "//components/autofill/core/common/mojom:mojo_types_java", "//components/version_info/android:version_constants_java", "//content/public/android:content_java", - "//third_party/android_deps:androidx_annotation_annotation_java", + "//third_party/androidx:androidx_annotation_annotation_java", "//ui/android:ui_no_recycler_view_java", ] annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ] sources = [ "java/src/org/chromium/components/autofill/AutofillActionModeCallback.java", + "java/src/org/chromium/components/autofill/AutofillHintsService.java", "java/src/org/chromium/components/autofill/AutofillManagerWrapper.java", "java/src/org/chromium/components/autofill/AutofillProvider.java", "java/src/org/chromium/components/autofill/AutofillProviderUMA.java", "java/src/org/chromium/components/autofill/FormData.java", "java/src/org/chromium/components/autofill/FormFieldData.java", + "java/src/org/chromium/components/autofill_public/ViewType.java", ] } diff --git a/chromium/components/autofill/android/provider/DEPS b/chromium/components/autofill/android/provider/DEPS new file mode 100644 index 00000000000..1bc851ebbbd --- /dev/null +++ b/chromium/components/autofill/android/provider/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+components/autofill/content", +] diff --git a/chromium/components/autofill/android/provider/autofill_provider_android.cc b/chromium/components/autofill/android/provider/autofill_provider_android.cc index 157142cde47..652a4fe4ce0 100644 --- a/chromium/components/autofill/android/provider/autofill_provider_android.cc +++ b/chromium/components/autofill/android/provider/autofill_provider_android.cc @@ -9,11 +9,13 @@ #include "base/android/jni_android.h" #include "base/android/jni_array.h" #include "base/android/jni_string.h" +#include "base/feature_list.h" #include "components/autofill/android/provider/form_data_android.h" #include "components/autofill/android/provider/jni_headers/AutofillProvider_jni.h" #include "components/autofill/core/browser/autofill_driver.h" #include "components/autofill/core/browser/autofill_handler_proxy.h" #include "components/autofill/core/common/autofill_constants.h" +#include "components/autofill/core/common/autofill_features.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/web_contents.h" #include "ui/android/window_android.h" @@ -34,6 +36,12 @@ namespace autofill { using mojom::SubmissionSource; +static jboolean JNI_AutofillProvider_IsQueryServerFieldTypesEnabled( + JNIEnv* env) { + return base::FeatureList::IsEnabled( + features::kAndroidAutofillQueryServerFieldTypes); +} + AutofillProviderAndroid::AutofillProviderAndroid( const JavaRef<jobject>& jcaller, content::WebContents* web_contents) @@ -151,7 +159,7 @@ void AutofillProviderAndroid::MaybeStartNewSession( Java_AutofillProvider_startAutofillSession( env, obj, form_obj, index, transformed_bounding.x(), transformed_bounding.y(), transformed_bounding.width(), - transformed_bounding.height()); + transformed_bounding.height(), handler->has_server_prediction()); } void AutofillProviderAndroid::OnAutofillAvailable(JNIEnv* env, @@ -341,8 +349,7 @@ void AutofillProviderAndroid::OnDidFillAutofillFormData( } void AutofillProviderAndroid::OnFormsSeen(AutofillHandlerProxy* handler, - const std::vector<FormData>& forms, - const base::TimeTicks timestamp) {} + const std::vector<FormData>& forms) {} void AutofillProviderAndroid::OnHidePopup(AutofillHandlerProxy* handler) { DCHECK_CURRENTLY_ON(BrowserThread::UI); @@ -356,6 +363,45 @@ void AutofillProviderAndroid::OnHidePopup(AutofillHandlerProxy* handler) { } } +void AutofillProviderAndroid::OnServerPredictionsAvailable( + AutofillHandlerProxy* handler) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (handler != handler_.get() || !form_.get()) + return; + + if (auto* form_structure = handler_->FindCachedFormByRendererId( + form_->form().unique_renderer_id)) { + form_->UpdateFieldTypes(*form_structure); + + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = java_ref_.get(env); + if (obj.is_null()) + return; + + Java_AutofillProvider_onQueryDone(env, obj, /*success=*/true); + } +} + +void AutofillProviderAndroid::OnServerQueryRequestError( + AutofillHandlerProxy* handler, + FormSignature form_signature) { + if (!IsCurrentlyLinkedHandler(handler) || !form_.get()) + return; + + if (auto* form_structure = handler_->FindCachedFormByRendererId( + form_->form().unique_renderer_id)) { + if (form_structure->form_signature() != form_signature) + return; + + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = java_ref_.get(env); + if (obj.is_null()) + return; + + Java_AutofillProvider_onQueryDone(env, obj, /*success=*/false); + } +} + void AutofillProviderAndroid::Reset(AutofillHandlerProxy* handler) { DCHECK_CURRENTLY_ON(BrowserThread::UI); if (handler == handler_.get()) { diff --git a/chromium/components/autofill/android/provider/autofill_provider_android.h b/chromium/components/autofill/android/provider/autofill_provider_android.h index 618517ca585..bd21af383a0 100644 --- a/chromium/components/autofill/android/provider/autofill_provider_android.h +++ b/chromium/components/autofill/android/provider/autofill_provider_android.h @@ -66,9 +66,11 @@ class AutofillProviderAndroid : public AutofillProvider { const FormData& form, base::TimeTicks timestamp) override; void OnFormsSeen(AutofillHandlerProxy* handler, - const std::vector<FormData>& forms, - const base::TimeTicks timestamp) override; + const std::vector<FormData>& forms) override; void OnHidePopup(AutofillHandlerProxy* handler) override; + void OnServerPredictionsAvailable(AutofillHandlerProxy* handler) override; + void OnServerQueryRequestError(AutofillHandlerProxy* handler, + FormSignature form_signature) override; void Reset(AutofillHandlerProxy* handler) override; diff --git a/chromium/components/autofill/android/provider/form_data_android.cc b/chromium/components/autofill/android/provider/form_data_android.cc index 7f24f8967c5..1d74c49fd32 100644 --- a/chromium/components/autofill/android/provider/form_data_android.cc +++ b/chromium/components/autofill/android/provider/form_data_android.cc @@ -40,7 +40,7 @@ ScopedJavaLocalRef<jobject> FormDataAndroid::GetJavaPeer( new FormFieldDataAndroid(&form_.fields[i]))); } if (form_structure) - ApplyHeuristicFieldType(*form_structure); + UpdateFieldTypes(*form_structure); ScopedJavaLocalRef<jstring> jname = ConvertUTF16ToJavaString(env, form_.name); ScopedJavaLocalRef<jstring> jhost = @@ -96,14 +96,20 @@ bool FormDataAndroid::SimilarFormAs(const FormData& form) { return form_.SimilarFormAs(form); } -void FormDataAndroid::ApplyHeuristicFieldType( - const FormStructure& form_structure) { +void FormDataAndroid::UpdateFieldTypes(const FormStructure& form_structure) { DCHECK(form_structure.field_count() == fields_.size()); auto form_field_data_android = fields_.begin(); for (const auto& autofill_field : form_structure) { DCHECK(form_field_data_android->get()->SimilarFieldAs(*autofill_field)); - form_field_data_android->get()->set_heuristic_type( - AutofillType(autofill_field->heuristic_type())); + std::vector<AutofillType> server_predictions; + for (const auto& prediction : autofill_field->server_predictions()) { + server_predictions.emplace_back( + static_cast<ServerFieldType>(prediction.type())); + } + form_field_data_android->get()->UpdateAutofillTypes( + AutofillType(autofill_field->heuristic_type()), + AutofillType(autofill_field->server_type()), + autofill_field->ComputedType(), server_predictions); if (++form_field_data_android == fields_.end()) break; } diff --git a/chromium/components/autofill/android/provider/form_data_android.h b/chromium/components/autofill/android/provider/form_data_android.h index 96002f62c94..e7c39495dc3 100644 --- a/chromium/components/autofill/android/provider/form_data_android.h +++ b/chromium/components/autofill/android/provider/form_data_android.h @@ -52,7 +52,8 @@ class FormDataAndroid { // |value|. void OnFormFieldDidChange(size_t index, const base::string16& value); - void ApplyHeuristicFieldType(const FormStructure& form); + // Updates the field types from the |form|. + void UpdateFieldTypes(const FormStructure& form); const FormData& form() { return form_; } diff --git a/chromium/components/autofill/android/provider/form_field_data_android.cc b/chromium/components/autofill/android/provider/form_field_data_android.cc index 943ff3cf116..b40701a8e56 100644 --- a/chromium/components/autofill/android/provider/form_field_data_android.cc +++ b/chromium/components/autofill/android/provider/form_field_data_android.cc @@ -21,9 +21,28 @@ using base::android::ToJavaArrayOfStrings; namespace autofill { +namespace { +base::android::ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfPredictionString( + JNIEnv* env, + const std::vector<AutofillType>& server_predictions) { + if (!server_predictions.empty()) { + std::vector<std::string> server_prediction_array; + server_prediction_array.reserve(server_predictions.size()); + for (const auto& p : server_predictions) { + server_prediction_array.emplace_back(p.ToString()); + } + return ToJavaArrayOfStrings(env, server_prediction_array); + } + return nullptr; +} + +} // namespace + FormFieldDataAndroid::FormFieldDataAndroid(FormFieldData* field) : heuristic_type_(AutofillType(UNKNOWN_TYPE)), field_ptr_(field) {} +FormFieldDataAndroid::~FormFieldDataAndroid() = default; + ScopedJavaLocalRef<jobject> FormFieldDataAndroid::GetJavaPeer() { JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef<jobject> obj = java_ref_.get(env); @@ -47,9 +66,17 @@ ScopedJavaLocalRef<jobject> FormFieldDataAndroid::GetJavaPeer() { ScopedJavaLocalRef<jobjectArray> joption_contents = ToJavaArrayOfStrings(env, field_ptr_->option_contents); ScopedJavaLocalRef<jstring> jheuristic_type; - if (!heuristic_type_.IsUnknown()) + if (!heuristic_type_.IsUnknown()) { jheuristic_type = ConvertUTF8ToJavaString(env, heuristic_type_.ToString()); + } + ScopedJavaLocalRef<jstring> jserver_type = + ConvertUTF8ToJavaString(env, server_type_.ToString()); + ScopedJavaLocalRef<jstring> jcomputed_type = + ConvertUTF8ToJavaString(env, computed_type_.ToString()); + ScopedJavaLocalRef<jobjectArray> jserver_predictions = + ToJavaArrayOfPredictionString(env, server_predictions_); + ScopedJavaLocalRef<jobjectArray> jdatalist_values = ToJavaArrayOfStrings(env, field_ptr_->datalist_values); ScopedJavaLocalRef<jobjectArray> jdatalist_labels = @@ -60,7 +87,8 @@ ScopedJavaLocalRef<jobject> FormFieldDataAndroid::GetJavaPeer() { field_ptr_->should_autocomplete, jplaceholder, jtype, jid, joption_values, joption_contents, IsCheckable(field_ptr_->check_status), IsChecked(field_ptr_->check_status), field_ptr_->max_length, - jheuristic_type, field_ptr_->bounds.x(), field_ptr_->bounds.y(), + jheuristic_type, jserver_type, jcomputed_type, jserver_predictions, + field_ptr_->bounds.x(), field_ptr_->bounds.y(), field_ptr_->bounds.right(), field_ptr_->bounds.bottom(), jdatalist_values, jdatalist_labels, field_ptr_->IsVisible()); java_ref_ = JavaObjectWeakGlobalRef(env, obj); @@ -103,4 +131,32 @@ bool FormFieldDataAndroid::SimilarFieldAs(const FormFieldData& field) const { return field_ptr_->SimilarFieldAs(field); } +void FormFieldDataAndroid::UpdateAutofillTypes( + const AutofillType& heuristic_type, + const AutofillType& server_type, + const AutofillType& computed_type, + const std::vector<AutofillType>& server_predictions) { + heuristic_type_ = heuristic_type; + server_type_ = server_type; + computed_type_ = computed_type; + server_predictions_ = server_predictions; + + // Java peer isn't available when this object is instantiated, update to + // Java peer if the prediction arrives later. + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = java_ref_.get(env); + if (obj.is_null()) + return; + + ScopedJavaLocalRef<jstring> jserver_type = + ConvertUTF8ToJavaString(env, server_type_.ToString()); + ScopedJavaLocalRef<jstring> jcomputed_type = + ConvertUTF8ToJavaString(env, computed_type_.ToString()); + ScopedJavaLocalRef<jobjectArray> jserver_predictions = + ToJavaArrayOfPredictionString(env, server_predictions_); + + Java_FormFieldData_updateFieldTypes(env, obj, jserver_type, jcomputed_type, + jserver_predictions); +} + } // namespace autofill diff --git a/chromium/components/autofill/android/provider/form_field_data_android.h b/chromium/components/autofill/android/provider/form_field_data_android.h index a12ad979bfe..ff0879b48b9 100644 --- a/chromium/components/autofill/android/provider/form_field_data_android.h +++ b/chromium/components/autofill/android/provider/form_field_data_android.h @@ -16,20 +16,24 @@ namespace autofill { // autofill::FormFieldData available in Java. class FormFieldDataAndroid { public: - FormFieldDataAndroid(FormFieldData* field); - virtual ~FormFieldDataAndroid() {} + explicit FormFieldDataAndroid(FormFieldData* field); + virtual ~FormFieldDataAndroid(); base::android::ScopedJavaLocalRef<jobject> GetJavaPeer(); void GetValue(); void OnFormFieldDidChange(const base::string16& value); bool SimilarFieldAs(const FormFieldData& field) const; - - void set_heuristic_type(const AutofillType& heuristic_type) { - heuristic_type_ = heuristic_type; - } + void UpdateAutofillTypes(const AutofillType& heuristic_type, + const AutofillType& server_type, + const AutofillType& computed_type, + const std::vector<AutofillType>& server_predictions); private: AutofillType heuristic_type_; + AutofillType server_type_; + AutofillType computed_type_; + std::vector<AutofillType> server_predictions_; + // Not owned. FormFieldData* field_ptr_; JavaObjectWeakGlobalRef java_ref_; diff --git a/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillHintsService.java b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillHintsService.java new file mode 100644 index 00000000000..14fd7b0e73e --- /dev/null +++ b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillHintsService.java @@ -0,0 +1,74 @@ +// 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. + +package org.chromium.components.autofill; + +import android.os.IBinder; + +import org.chromium.base.Log; +import org.chromium.components.autofill_public.IAutofillHintsService; +import org.chromium.components.autofill_public.IViewTypeCallback; +import org.chromium.components.autofill_public.ViewType; + +import java.util.List; + +/** + * This class is used to talk to autofill service about the view type. + */ +public class AutofillHintsService { + private static final String TAG = "AutofillHintsService"; + + public AutofillHintsService() { + mBinder = new IAutofillHintsService.Stub() { + @Override + public void registerViewTypeCallback(IViewTypeCallback callback) { + mCallback = callback; + if (mUnsentViewTypes != null) { + invokeOnViewTypeAvailable(); + } else if (mQueryFailed != null) { + invokeOnQueryFailed(); + } + } + }; + } + + public IBinder getBinder() { + return mBinder; + } + + public void onViewTypeAvailable(List<ViewType> viewTypes) { + if (mUnsentViewTypes != null) return; + mUnsentViewTypes = viewTypes; + if (mCallback == null) return; + invokeOnViewTypeAvailable(); + } + + public void onQueryFailed() { + if (mQueryFailed != null) return; + mQueryFailed = Boolean.TRUE; + if (mCallback == null) return; + invokeOnQueryFailed(); + } + + private void invokeOnViewTypeAvailable() { + try { + mCallback.onViewTypeAvailable(mUnsentViewTypes); + } catch (Exception e) { + Log.e(TAG, "onViewTypeAvailable ", e); + } + } + + private void invokeOnQueryFailed() { + try { + mCallback.onQueryFailed(); + } catch (Exception e) { + Log.e(TAG, "onQueryFailed ", e); + } + } + + private IAutofillHintsService.Stub mBinder; + private IViewTypeCallback mCallback; + private List<ViewType> mUnsentViewTypes; + private Boolean mQueryFailed; +} diff --git a/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java index e92fdb19c61..237cf244e98 100644 --- a/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java +++ b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java @@ -5,6 +5,7 @@ package org.chromium.components.autofill; import android.annotation.TargetApi; +import android.content.ComponentName; import android.content.Context; import android.graphics.Rect; import android.os.Build; @@ -29,7 +30,8 @@ public class AutofillManagerWrapper { // NOTE: As a result of the above, the tag below still references the name of this class from // when it was originally developed specifically for Android WebView. public static final String TAG = "AwAutofillManager"; - + private static final String AWG_COMPONENT_NAME = + "com.google.android.gms/com.google.android.gms.autofill.service.AutofillService"; /** * The observer of suggestion window. */ @@ -58,17 +60,42 @@ public class AutofillManagerWrapper { private boolean mDestroyed; private boolean mDisabled; private ArrayList<WeakReference<InputUIObserver>> mInputUIObservers; + // Indicates if AwG is the current Android autofill service. + private final boolean mIsAwGCurrentAutofillService; public AutofillManagerWrapper(Context context) { updateLogStat(); if (isLoggable()) log("constructor"); mAutofillManager = context.getSystemService(AutofillManager.class); mDisabled = mAutofillManager == null || !mAutofillManager.isEnabled(); + if (mDisabled) { + mIsAwGCurrentAutofillService = false; if (isLoggable()) log("disabled"); return; } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + ComponentName componentName = null; + try { + componentName = mAutofillManager.getAutofillServiceComponentName(); + } catch (Exception e) { + // Can't catch com.android.internal.util.SyncResultReceiver.TimeoutException, + // because + // - The exception isn't Android API. + // - Different version of Android handle it differently. + // Uses Exception to catch various cases. (refer to crbug.com/1186406) + Log.e(TAG, "getAutofillServiceComponentName", e); + } + if (componentName != null) { + mIsAwGCurrentAutofillService = + AWG_COMPONENT_NAME.equals(componentName.flattenToString()); + } else { + mIsAwGCurrentAutofillService = false; + } + } else { + mIsAwGCurrentAutofillService = false; + } mMonitor = new AutofillInputUIMonitor(this); mAutofillManager.registerCallback(mMonitor); } @@ -142,6 +169,14 @@ public class AutofillManagerWrapper { return mDisabled; } + /** + * Only work for Android P and beyond. Always return false for Android O. + * @return if the Autofill with Google is the current autofill service. + */ + public boolean isAwGCurrentAutofillService() { + return mIsAwGCurrentAutofillService; + } + private boolean checkAndWarnIfDestroyed() { if (mDestroyed) { Log.w(TAG, "Application attempted to call on a destroyed AutofillManagerWrapper", @@ -165,9 +200,13 @@ public class AutofillManagerWrapper { } } - public void notifyNewSessionStarted() { + public void notifyNewSessionStarted(boolean hasServerPrediction) { updateLogStat(); - if (isLoggable()) log("Session starts"); + if (isLoggable()) log("Session starts, has server prediction = " + hasServerPrediction); + } + + public void onQueryDone(boolean success) { + if (isLoggable()) log("Query " + (success ? "succeed" : "failed")); } /** diff --git a/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillProvider.java b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillProvider.java index 226f0dc55a7..726a17f0784 100644 --- a/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillProvider.java +++ b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillProvider.java @@ -28,6 +28,7 @@ import org.chromium.base.annotations.JNINamespace; import org.chromium.base.annotations.NativeMethods; import org.chromium.base.annotations.VerifiesOnO; import org.chromium.base.metrics.ScopedSysTraceEvent; +import org.chromium.components.autofill_public.ViewType; import org.chromium.components.version_info.VersionConstants; import org.chromium.content_public.browser.RenderCoordinates; import org.chromium.content_public.browser.WebContents; @@ -37,6 +38,8 @@ import org.chromium.ui.base.ViewAndroidDelegate; import org.chromium.ui.base.WindowAndroid; import org.chromium.ui.display.DisplayAndroid; +import java.util.ArrayList; + /** * This class works with Android autofill service to fill web form, it doesn't use chrome's * autofill service or suggestion UI. All methods are supposed to be called in UI thread. @@ -58,6 +61,11 @@ import org.chromium.ui.display.DisplayAndroid; @JNINamespace("autofill") public class AutofillProvider { private static final String TAG = "AutofillProvider"; + + // This member is initialize at first use. Not access it directly, always through + // isQueryServerFieldTypesEnabled(). + private static Boolean sIsQueryServerFieldTypesEnabled; + private static class FocusField { public final short fieldIndex; public final Rect absBound; @@ -81,11 +89,19 @@ public class AutofillProvider { public final int sessionId; private FormData mFormData; private FocusField mFocusField; - - public AutofillRequest(FormData formData, FocusField focus) { + private AutofillHintsService mAutofillHintsService; + + /** + * @param formData the form of the AutofillRequest. + * @param focus the current focused field. + * @param hasServerPrediction whether the server type of formData is valid. + */ + public AutofillRequest(FormData formData, FocusField focus, boolean hasServerPrediction) { sessionId = getNextClientId(); mFormData = formData; mFocusField = focus; + // Don't need to create binder object if server prediction is already available. + if (!hasServerPrediction) mAutofillHintsService = new AutofillHintsService(); } public void fillViewStructure(ViewStructure structure) { @@ -99,6 +115,7 @@ public class AutofillProvider { ViewStructure child = structure.newChild(index++); int virtualId = toVirtualId(sessionId, fieldIndex++); child.setAutofillId(structure.getAutofillId(), virtualId); + field.setAutofillId(child.getAutofillId()); if (field.mAutocompleteAttr != null && !field.mAutocompleteAttr.isEmpty()) { child.setAutofillHints(field.mAutocompleteAttr.split(" +")); } @@ -118,6 +135,16 @@ public class AutofillProvider { .addAttribute("ua-autofill-hints", field.mHeuristicType) .addAttribute("id", field.mId); + if (isQueryServerFieldTypesEnabled()) { + builder.addAttribute("crowdsourcing-autofill-hints", field.getServerType()); + builder.addAttribute("computed-autofill-hints", field.getComputedType()); + // Compose multiple predictions to a string separated by ','. + String[] predictions = field.getServerPredictions(); + if (predictions != null && predictions.length > 0) { + builder.addAttribute("crowdsourcing-predictions-autofill-hints", + String.join(",", predictions)); + } + } switch (field.getControlType()) { case FormFieldData.ControlType.LIST: child.setAutofillType(View.AUTOFILL_TYPE_LIST); @@ -249,6 +276,24 @@ public class AutofillProvider { private static int toVirtualId(int clientId, short index) { return (clientId << 16) | index; } + + public AutofillHintsService getAutofillHintsService() { + return mAutofillHintsService; + } + + public void onQueryDone(boolean success) { + if (mAutofillHintsService == null) return; + if (success) { + ArrayList<ViewType> viewTypes = new ArrayList<ViewType>(); + for (FormFieldData field : mFormData.mFields) { + viewTypes.add(new ViewType(field.getAutofillId(), field.getServerType(), + field.getComputedType(), field.getServerPredictions())); + } + mAutofillHintsService.onViewTypeAvailable(viewTypes); + } else { + mAutofillHintsService.onQueryFailed(); + } + } } private final String mProviderName; @@ -279,7 +324,7 @@ public class AutofillProvider { assert Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; mAutofillManager = manager; mContainerView = containerView; - mAutofillUMA = new AutofillProviderUMA(context); + mAutofillUMA = new AutofillProviderUMA(context, manager.isAwGCurrentAutofillService()); mInputUIObserver = new AutofillManagerWrapper.InputUIObserver() { @Override public void onInputUIShown() { @@ -321,6 +366,13 @@ public class AutofillProvider { bundle.putCharSequence("VIRTUAL_STRUCTURE_PROVIDER_NAME", mProviderName); bundle.putCharSequence( "VIRTUAL_STRUCTURE_PROVIDER_VERSION", VersionConstants.PRODUCT_VERSION); + + if (isQueryServerFieldTypesEnabled()) { + AutofillHintsService autofillHintsService = mRequest.getAutofillHintsService(); + if (autofillHintsService != null) { + bundle.putBinder("AUTOFILL_HINTS_SERVICE", autofillHintsService.getBinder()); + } + } } mRequest.fillViewStructure(structure); if (AutofillManagerWrapper.isLoggable()) { @@ -373,10 +425,11 @@ public class AutofillProvider { * @param y the boundary of focus field. * @param width the boundary of focus field. * @param height the boundary of focus field. + * @param hasServerPrediction whether the server prediction arrived. */ @CalledByNative - public void startAutofillSession( - FormData formData, int focus, float x, float y, float width, float height) { + public void startAutofillSession(FormData formData, int focus, float x, float y, float width, + float height, boolean hasServerPrediction) { // Check focusField inside short value? // Autofill Manager might have session that wasn't started by AutofillProvider, // we just always cancel existing session here. @@ -387,13 +440,17 @@ public class AutofillProvider { Rect absBound = transformToWindowBounds(new RectF(x, y, x + width, y + height)); if (mRequest != null) notifyViewExitBeforeDestroyRequest(); transformFormFieldToContainViewCoordinates(formData); - mRequest = new AutofillRequest(formData, new FocusField((short) focus, absBound)); + mRequest = new AutofillRequest( + formData, new FocusField((short) focus, absBound), hasServerPrediction); int virtualId = mRequest.getVirtualId((short) focus); notifyVirtualViewEntered(mContainerView, virtualId, absBound); mAutofillUMA.onSessionStarted(mAutofillManager.isDisabled()); + if (hasServerPrediction) { + mAutofillUMA.onServerTypeAvailable(formData, /*afterSessionStarted=*/false); + } mAutofillTriggeredTimeMillis = System.currentTimeMillis(); - mAutofillManager.notifyNewSessionStarted(); + mAutofillManager.notifyNewSessionStarted(hasServerPrediction); } /** @@ -704,6 +761,22 @@ public class AutofillProvider { forceNotifyFormValues(); } + @CalledByNative + private void onQueryDone(boolean success) { + mRequest.onQueryDone(success); + mAutofillUMA.onServerTypeAvailable( + success ? mRequest.mFormData : null, /*afterSessionStarted*/ true); + mAutofillManager.onQueryDone(success); + } + + public static boolean isQueryServerFieldTypesEnabled() { + if (sIsQueryServerFieldTypesEnabled == null) { + sIsQueryServerFieldTypesEnabled = + AutofillProviderJni.get().isQueryServerFieldTypesEnabled(); + } + return sIsQueryServerFieldTypesEnabled; + } + private void forceNotifyFormValues() { if (mRequest == null) return; for (int i = 0; i < mRequest.getFieldCount(); ++i) { @@ -795,5 +868,7 @@ public class AutofillProvider { void setAnchorViewRect(long nativeAutofillProviderAndroid, AutofillProvider caller, View anchorView, float x, float y, float width, float height); + + boolean isQueryServerFieldTypesEnabled(); } } diff --git a/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillProviderUMA.java b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillProviderUMA.java index 394cd849c95..3f4ea071c87 100644 --- a/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillProviderUMA.java +++ b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillProviderUMA.java @@ -5,6 +5,7 @@ package org.chromium.components.autofill; import android.content.Context; +import android.os.Build; import org.chromium.autofill.mojom.SubmissionSource; import org.chromium.base.ContextUtils; @@ -26,6 +27,10 @@ public class AutofillProviderUMA { public static final String UMA_AUTOFILL_CREATED_BY_ACTIVITY_CONTEXT = "Autofill.WebView.CreatedByActivityContext"; + // Records whether the current autofill service is AwG. + public static final String UMA_AUTOFILL_AWG_IS_CURRENT_SERVICE = + "Autofill.WebView.AwGIsCurrentService"; + // Records what happened in an autofill session. public static final String UMA_AUTOFILL_AUTOFILL_SESSION = "Autofill.WebView.AutofillSession"; // The possible value of UMA_AUTOFILL_AUTOFILL_SESSION. @@ -45,6 +50,25 @@ public class AutofillProviderUMA { public static final int USER_NOT_SELECT_SUGGESTION_USER_NOT_CHANGE_FORM_NO_FORM_SUBMITTED = 13; public static final int AUTOFILL_SESSION_HISTOGRAM_COUNT = 14; + // The possible values for the server prediction availability. + public static final String UMA_AUTOFILL_SERVER_PREDICTION_AVAILABILITY = + "Autofill.WebView.ServerPredicton.PredictionAvailability"; + public static final int SERVER_PREDICTION_NOT_AVAILABLE = 0; + public static final int SERVER_PREDICTION_AVAILABLE_ON_SESSION_STARTS = 1; + public static final int SERVER_PREDICTION_AVAILABLE_AFTER_SESSION_STARTS = 2; + public static final int SERVER_PREDICTION_AVAILABLE_COUNT = 3; + + // The possible values for the AwG suggestion availability. + public static final String UMA_AUTOFILL_AWG_SUGGSTION_AVAILABILITY = + "Autofill.WebView.ServerPrediction.AwGSuggestionAvailability"; + public static final int AWG_NO_SUGGESTION = 0; + public static final int AWG_HAS_SUGGESTION_NO_AUTOFILL = 1; + public static final int AWG_HAS_SUGGESTION_AUTOFILLED = 2; + public static final int AWG_SUGGSTION_AVAILABLE_COUNT = 3; + + public static final String UMA_AUTOFILL_VALID_SERVER_PREDICTION = + "Autofill.WebView.ServerPredicton.HasValidServerPrediction"; + // Records whether user changed autofilled field if user ever changed the form. The action isn't // recorded if user didn't change form at all. public static final String UMA_AUTOFILL_USER_CHANGED_AUTOFILLED_FIELD = @@ -120,6 +144,31 @@ public class AutofillProviderUMA { if (mSuggestionTimeMillis != null) { recordTimesHistogram(UMA_AUTOFILL_SUGGESTION_TIME, mSuggestionTimeMillis); } + if (!mServerPredictionAvailable && AutofillProvider.isQueryServerFieldTypesEnabled()) { + RecordHistogram.recordEnumeratedHistogram( + UMA_AUTOFILL_SERVER_PREDICTION_AVAILABILITY, + SERVER_PREDICTION_NOT_AVAILABLE, SERVER_PREDICTION_AVAILABLE_COUNT); + } + } + + public void onServerTypeAvailable(FormData formData, boolean afterSessionStarted) { + if (!AutofillProvider.isQueryServerFieldTypesEnabled()) return; + mServerPredictionAvailable = true; + RecordHistogram.recordEnumeratedHistogram(UMA_AUTOFILL_SERVER_PREDICTION_AVAILABILITY, + afterSessionStarted ? SERVER_PREDICTION_AVAILABLE_AFTER_SESSION_STARTS + : SERVER_PREDICTION_AVAILABLE_ON_SESSION_STARTS, + SERVER_PREDICTION_AVAILABLE_COUNT); + if (formData != null) { + boolean hasValidServerData = false; + for (FormFieldData fieldData : formData.mFields) { + if (!fieldData.getServerType().equals("NO_SERVER_DATA")) { + hasValidServerData = true; + break; + } + } + RecordHistogram.recordBooleanHistogram( + UMA_AUTOFILL_VALID_SERVER_PREDICTION, hasValidServerData); + } } private int toUMAAutofillSessionValue() { @@ -174,19 +223,62 @@ public class AutofillProviderUMA { private int mState; private Boolean mUserChangedAutofilledField; + + // Indicates whether the server prediction arrives. + private boolean mServerPredictionAvailable; + } + + /** + * The class to record Autofill.WebView.ServerPrediction.AwGSuggestion, is only instantiated + * when the Android platform AutofillServcie is AwG, This will give us more actual result in + * A/B experiment while only AwG supports the server prediction. + */ + private static class ServerPredictionRecorder { + private boolean mHasSuggestions; + private boolean mAutofilled; + private boolean mRecorded; + + public void onSuggestionDisplayed() { + mHasSuggestions = true; + } + + public void onAutofill() { + mAutofilled = true; + } + + public void recordHistograms() { + if (mRecorded) return; + mRecorded = true; + int sample = AWG_NO_SUGGESTION; + if (mHasSuggestions) { + sample = mAutofilled ? AWG_HAS_SUGGESTION_AUTOFILLED + : AWG_HAS_SUGGESTION_NO_AUTOFILL; + } + RecordHistogram.recordEnumeratedHistogram( + UMA_AUTOFILL_AWG_SUGGSTION_AVAILABILITY, sample, AWG_SUGGSTION_AVAILABLE_COUNT); + } } private SessionRecorder mRecorder; private Boolean mAutofillDisabled; - public AutofillProviderUMA(Context context) { + private final boolean mIsAwGCurrentAutofillService; + private ServerPredictionRecorder mServerPredictionRecorder; + + public AutofillProviderUMA(Context context, boolean isAwGCurrentAutofillService) { RecordHistogram.recordBooleanHistogram(UMA_AUTOFILL_CREATED_BY_ACTIVITY_CONTEXT, ContextUtils.activityFromContext(context) != null); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + RecordHistogram.recordBooleanHistogram( + UMA_AUTOFILL_AWG_IS_CURRENT_SERVICE, isAwGCurrentAutofillService); + } + mIsAwGCurrentAutofillService = isAwGCurrentAutofillService; } public void onFormSubmitted(int submissionSource) { if (mRecorder != null) mRecorder.record(SessionRecorder.EVENT_FORM_SUBMITTED); recordSession(); + if (mServerPredictionRecorder != null) mServerPredictionRecorder.recordHistograms(); // We record this no matter autofill service is disabled or not. RecordHistogram.recordEnumeratedHistogram(UMA_AUTOFILL_SUBMISSION_SOURCE, toUMASubmissionSource(submissionSource), SUBMISSION_SOURCE_HISTOGRAM_COUNT); @@ -201,6 +293,9 @@ public class AutofillProviderUMA { if (mRecorder != null) recordSession(); mRecorder = new SessionRecorder(); + if (mIsAwGCurrentAutofillService) { + mServerPredictionRecorder = new ServerPredictionRecorder(); + } } public void onVirtualStructureProvided() { @@ -212,10 +307,12 @@ public class AutofillProviderUMA { mRecorder.record(SessionRecorder.EVENT_SUGGESTION_DISPLAYED); mRecorder.setSuggestionTimeMillis(suggestionTimeMillis); } + if (mServerPredictionRecorder != null) mServerPredictionRecorder.onSuggestionDisplayed(); } public void onAutofill() { if (mRecorder != null) mRecorder.record(SessionRecorder.EVENT_FORM_AUTOFILLED); + if (mServerPredictionRecorder != null) mServerPredictionRecorder.onAutofill(); } public void onUserChangeFieldValue(boolean isPreviouslyAutofilled) { @@ -227,6 +324,17 @@ public class AutofillProviderUMA { } } + /** + * Invoked when the server query was done or has arrived when the autofill sension starts. + * + * @param formData the form of the current session, is null if the query failed. + * @param afterSessionStarted true if the server type predication arrive after the session + * starts. + */ + public void onServerTypeAvailable(FormData formData, boolean afterSessionStarted) { + mRecorder.onServerTypeAvailable(formData, afterSessionStarted); + } + private void recordSession() { if (mAutofillDisabled != null && !mAutofillDisabled.booleanValue() && mRecorder != null) { mRecorder.recordHistogram(); diff --git a/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/FormFieldData.java b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/FormFieldData.java index 9175bd42752..77f65ef7324 100644 --- a/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/FormFieldData.java +++ b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/FormFieldData.java @@ -5,6 +5,7 @@ package org.chromium.components.autofill; import android.graphics.RectF; +import android.view.autofill.AutofillId; import androidx.annotation.IntDef; import androidx.annotation.VisibleForTesting; @@ -63,10 +64,18 @@ public class FormFieldData { // Indicates whether this fields was autofilled, but changed by user. private boolean mPreviouslyAutofilled; + // Provides the field type along with mHeuristicType, but could be changed + // after the object instantiated. + private String mServerType; + private String mComputedType; + private String[] mServerPredictions; + private AutofillId mAutofillId; + private FormFieldData(String name, String label, String value, String autocompleteAttr, boolean shouldAutocomplete, String placeholder, String type, String id, String[] optionValues, String[] optionContents, boolean isCheckField, boolean isChecked, - int maxLength, String heuristicType, float left, float top, float right, float bottom, + int maxLength, String heuristicType, String serverType, String computedType, + String[] serverPredictions, float left, float top, float right, float bottom, String[] datalistValues, String[] datalistLabels, boolean visible) { mName = name; mLabel = label; @@ -92,6 +101,9 @@ public class FormFieldData { } mMaxLength = maxLength; mHeuristicType = heuristicType; + mServerType = serverType; + mServerPredictions = serverPredictions; + mComputedType = computedType; mBounds = new RectF(left, top, right, bottom); mVisible = visible; } @@ -137,6 +149,26 @@ public class FormFieldData { } @CalledByNative + private void updateFieldTypes( + String serverType, String computedType, String[] serverPredictions) { + mServerType = serverType; + mComputedType = computedType; + mServerPredictions = serverPredictions; + } + + public String getServerType() { + return mServerType; + } + + public String getComputedType() { + return mComputedType; + } + + public String[] getServerPredictions() { + return mServerPredictions; + } + + @CalledByNative public boolean isChecked() { return mIsChecked; } @@ -150,17 +182,25 @@ public class FormFieldData { mAutofilled = autofilled; } + public void setAutofillId(AutofillId id) { + mAutofillId = id; + } + + public AutofillId getAutofillId() { + return mAutofillId; + } + @CalledByNative @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) public static FormFieldData createFormFieldData(String name, String label, String value, String autocompleteAttr, boolean shouldAutocomplete, String placeholder, String type, String id, String[] optionValues, String[] optionContents, boolean isCheckField, - boolean isChecked, int maxLength, String heuristicType, float left, float top, - float right, float bottom, String[] datalistValues, String[] datalistLabels, - boolean visible) { + boolean isChecked, int maxLength, String heuristicType, String serverType, + String computedType, String[] serverPredictions, float left, float top, float right, + float bottom, String[] datalistValues, String[] datalistLabels, boolean visible) { return new FormFieldData(name, label, value, autocompleteAttr, shouldAutocomplete, placeholder, type, id, optionValues, optionContents, isCheckField, isChecked, - maxLength, heuristicType, left, top, right, bottom, datalistValues, datalistLabels, - visible); + maxLength, heuristicType, serverType, computedType, serverPredictions, left, top, + right, bottom, datalistValues, datalistLabels, visible); } } diff --git a/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill_public/IAutofillHintsService.aidl b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill_public/IAutofillHintsService.aidl new file mode 100644 index 00000000000..dd5dd191b39 --- /dev/null +++ b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill_public/IAutofillHintsService.aidl @@ -0,0 +1,22 @@ +// 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. + +package org.chromium.components.autofill_public; + +import android.os.Bundle; + +import org.chromium.components.autofill_public.IViewTypeCallback; + +/** + * Interface to provide the autofill hints that are unable to be supported + * by Android framework. + * + * The autofill service could get the binder from ViewStructure. + * Bundle bundle = viewStructure.getExtras(); + * IBinder binder = bundle.getBinder("AUTOFILL_HINTS_SERVICE"); + */ +interface IAutofillHintsService { + // Register the IViewTypeCallback to get the server prediction type. + void registerViewTypeCallback(IViewTypeCallback callback); +} diff --git a/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill_public/IViewTypeCallback.aidl b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill_public/IViewTypeCallback.aidl new file mode 100644 index 00000000000..70fa902c55d --- /dev/null +++ b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill_public/IViewTypeCallback.aidl @@ -0,0 +1,22 @@ +// 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. + +package org.chromium.components.autofill_public; + +import android.os.Bundle; + +import org.chromium.components.autofill_public.ViewType; + +/** + * The interface for AutofillHintsService to provide the type of view. + */ +interface IViewTypeCallback { + // Invoked when the query succeeds, though the server might not have the + // prediction of the views. + void onViewTypeAvailable(in List<ViewType> viewTypes); + + // Invoked when the query fails, mostly because of the connection or server + // error. + void onQueryFailed(); +} diff --git a/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill_public/OWNERS b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill_public/OWNERS new file mode 100644 index 00000000000..8f094e0099e --- /dev/null +++ b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill_public/OWNERS @@ -0,0 +1,2 @@ +per-file *.aidl=set noparent +per-file *.aidl=file://ipc/SECURITY_OWNERS diff --git a/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill_public/README.md b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill_public/README.md new file mode 100644 index 00000000000..7e77e096932 --- /dev/null +++ b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill_public/README.md @@ -0,0 +1,53 @@ +# How to integrate the AutofillHintsService + +1. Add all files of this directory to your project. +2. Get the binder in your autofill service as below + + ```java + public void processNode(AssistStructure.ViewNode node) { + Bundle bundle = node.getExtras(); + if (bundle != null) { + IBinder binder = bundle.getBinder("AUTOFILL_HINTS_SERVICE"); + if (binder != null) { + callViewTypeService(binder); + } else { + Log.e("MyAutofillService", "binder is null."); + } + } else { + Log.e("MyAutofillService", "bundle is null."); + } + } + ``` + +3. Register the ViewTypeCallback + + ```java + private void callViewTypeService(IBinder binder) { + IViewTypeService viewTypeService = IViewTypeService.Stub.asInterface(binder); + if (viewTypeService != null) { + try { + if (mViewTypeCallback == null) mViewTypeCallback = new ViewTypeCallback(); + viewTypeService.registerViewTypeCallback(mViewTypeCallback.getBinder()); + Log.d("MyAutofillService", " registerViewTypeCallback "); + } catch (Exception e) { + Log.e("MyAutofillService", " registerViewTypeCallback exception", e); + } + } else { + Log.e("MyAutofillService", "viewTypeService is null."); + } + } + ``` + +4. A list of ViewType will be returned from ViewTypeCallback when they are available. + + ```java + public void onViewTypeAvailable(List<ViewType> viewTypeList) { + for(ViewType viewType : viewTypeList) { + if (viewType.getServerPredictions() ! = null) { + // Uses server predictions if they are available. + } else { + // otherwise, uses viewType.mServerType. + } + } + } + ``` diff --git a/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill_public/ViewType.aidl b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill_public/ViewType.aidl new file mode 100644 index 00000000000..6a7398d38a4 --- /dev/null +++ b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill_public/ViewType.aidl @@ -0,0 +1,7 @@ +// 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. + +package org.chromium.components.autofill_public; + +parcelable ViewType; diff --git a/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill_public/ViewType.java b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill_public/ViewType.java new file mode 100644 index 00000000000..fe0d9e18e28 --- /dev/null +++ b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill_public/ViewType.java @@ -0,0 +1,88 @@ +// 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. + +package org.chromium.components.autofill_public; + +import android.annotation.TargetApi; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.autofill.AutofillId; + +import org.chromium.base.annotations.VerifiesOnO; + +/** + * This class is used to send the server and computed view type to the autofill service. + * The valid types are listed in the two FieldTypeToStringPiece() functions in + * components/autofill/core/browser/field_types.cc. Note that the list of possibly returned strings + * can and will change in the future. + */ +@TargetApi(Build.VERSION_CODES.O) +@VerifiesOnO +public class ViewType implements Parcelable { + /** + * The AutofillId of the view that types are for. + */ + public final AutofillId mAutofillId; + + /** + * The type from Chrome autofill server. + */ + public final String mServerType; + + /** + * The type computed overall type. The valid types are the same as for mServerType. + */ + public final String mComputedType; + + private String[] mServerPredictions; + + public static final Parcelable.Creator<ViewType> CREATOR = new Parcelable.Creator<ViewType>() { + @Override + public ViewType createFromParcel(Parcel in) { + return new ViewType(in); + } + + @Override + public ViewType[] newArray(int size) { + return new ViewType[size]; + } + }; + + public ViewType( + AutofillId id, String serverType, String computedType, String[] serverPredictions) { + mAutofillId = id; + mServerType = serverType; + mComputedType = computedType; + mServerPredictions = serverPredictions; + } + + private ViewType(Parcel in) { + mAutofillId = AutofillId.CREATOR.createFromParcel(in); + mServerType = in.readString(); + mComputedType = in.readString(); + in.readStringArray(mServerPredictions); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + mAutofillId.writeToParcel(parcel, flags); + parcel.writeString(mServerType); + parcel.writeString(mComputedType); + parcel.writeStringArray(mServerPredictions); + } + + /** + * @return the server predictions, they are in the order of the confidence. The mServerType + * shall be used if the server predictions aren't available. + */ + public String[] getServerPredictions() { + return mServerPredictions; + } +} diff --git a/chromium/components/autofill/android/provider/junit/src/org/chromium/components/autofill/AutofillProviderTest.java b/chromium/components/autofill/android/provider/junit/src/org/chromium/components/autofill/AutofillProviderTest.java index 5d63ae0630b..87c73025e49 100644 --- a/chromium/components/autofill/android/provider/junit/src/org/chromium/components/autofill/AutofillProviderTest.java +++ b/chromium/components/autofill/android/provider/junit/src/org/chromium/components/autofill/AutofillProviderTest.java @@ -83,11 +83,11 @@ public class AutofillProviderTest { public void testTransformFormFieldToContainViewCoordinates() { ArrayList<FormFieldData> fields = new ArrayList<FormFieldData>(1); fields.add(FormFieldData.createFormFieldData(null, null, null, null, false, null, null, - null, null, null, false, false, 0, null, 10 /* left */, 20 /* top */, - 300 /* right */, 60 /*bottom*/, null, null, true)); + null, null, null, false, false, 0, null, null, null, null, 10 /* left */, + 20 /* top */, 300 /* right */, 60 /*bottom*/, null, null, true)); fields.add(FormFieldData.createFormFieldData(null, null, null, null, false, null, null, - null, null, null, false, false, 0, null, 20 /* left */, 100 /* top */, - 400 /* right */, 200 /*bottom*/, null, null, true)); + null, null, null, false, false, 0, null, null, null, null, 20 /* left */, + 100 /* top */, 400 /* right */, 200 /*bottom*/, null, null, true)); FormData formData = new FormData(null, null, fields); mAutofillProvider.transformFormFieldToContainViewCoordinates(formData); RectF result = formData.mFields.get(0).getBoundsInContainerViewCoordinates(); diff --git a/chromium/components/autofill/android/provider/test_support/BUILD.gn b/chromium/components/autofill/android/provider/test_support/BUILD.gn new file mode 100644 index 00000000000..6ed8af92b68 --- /dev/null +++ b/chromium/components/autofill/android/provider/test_support/BUILD.gn @@ -0,0 +1,41 @@ +# 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. + +import("//build/config/android/config.gni") +import("//build/config/android/rules.gni") + +testonly = true + +android_library("component_autofill_provider_java_test_support") { + annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ] + sources = [ + "java/src/org/chromium/components/autofill/AutofillHintsServiceTestHelper.java", + "java/src/org/chromium/components/autofill/AutofillProviderTestHelper.java", + ] + deps = [ + "//base:base_java", + "//base:base_java_test_support", + "//base:jni_java", + "//components/autofill/android/provider:autofill_aidl", + "//components/autofill/android/provider:java", + "//content/public/android:content_java", + "//third_party/androidx:androidx_annotation_annotation_java", + ] +} +generate_jni("jni_headers") { + sources = [ + "java/src/org/chromium/components/autofill/AutofillProviderTestHelper.java", + ] +} + +source_set("component_autofill_provider_native_test_support") { + sources = [ "autofill_provider_test_helper.cc" ] + deps = [ + ":jni_headers", + "//base", + "//components/autofill/content/browser", + "//components/autofill/core/browser:test_support", + "//content/public/browser", + ] +} diff --git a/chromium/components/autofill/android/provider/test_support/autofill_provider_test_helper.cc b/chromium/components/autofill/android/provider/test_support/autofill_provider_test_helper.cc new file mode 100644 index 00000000000..1d244099f3e --- /dev/null +++ b/chromium/components/autofill/android/provider/test_support/autofill_provider_test_helper.cc @@ -0,0 +1,173 @@ +// 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/autofill/android/provider/test_support/jni_headers/AutofillProviderTestHelper_jni.h" + +#include "base/android/jni_array.h" +#include "base/base64.h" +#include "base/strings/string16.h" +#include "components/autofill/content/browser/content_autofill_driver.h" +#include "components/autofill/content/browser/content_autofill_driver_factory.h" +#include "components/autofill/core/browser/autofill_provider.h" +#include "components/autofill/core/browser/autofill_test_utils.h" +#include "components/autofill/core/browser/field_types.h" +#include "content/public/browser/web_contents.h" + +namespace autofill { + +namespace { +AutofillHandler* GetAutofillHandler(content::WebContents* web_contents, + content::RenderFrameHost* rfh) { + // Avoid using ContentAutofillDriver::GetForRenderFrameHost(), it will create + // a new ContentAutofillDriver. + if (ContentAutofillDriverFactory* factory = + ContentAutofillDriverFactory::FromWebContents(web_contents)) { + if (ContentAutofillDriver* driver = + static_cast<ContentAutofillDriver*>(factory->DriverForKey(rfh))) { + return driver->autofill_handler(); + } + } + return nullptr; +} + +AutofillHandler* ToMainFrameAutofillHandler( + const base::android::JavaParamRef<jobject>& jweb_contents) { + content::WebContents* web_contents = + content::WebContents::FromJavaWebContents(jweb_contents); + CHECK(web_contents); + AutofillHandler* autofill_handler = + GetAutofillHandler(web_contents, web_contents->GetMainFrame()); + CHECK(autofill_handler); + return autofill_handler; +} + +} // namespace + +static void JNI_AutofillProviderTestHelper_DisableDownloadServerForTesting( + JNIEnv* env_md_ctx_st) { + AutofillProvider::set_is_download_manager_disabled_for_testing(); +} + +static jboolean +JNI_AutofillProviderTestHelper_SimulateMainFrameAutofillServerResponseForTesting( + JNIEnv* env, + const base::android::JavaParamRef<jobject>& jweb_contents, + const base::android::JavaParamRef<jobjectArray>& jfield_ids, + const base::android::JavaParamRef<jintArray>& jfield_types) { + std::vector<base::string16> field_ids; + base::android::AppendJavaStringArrayToStringVector(env, jfield_ids, + &field_ids); + std::vector<int> field_types; + base::android::JavaIntArrayToIntVector(env, jfield_types, &field_types); + + AutofillHandler* autofill_handler = ToMainFrameAutofillHandler(jweb_contents); + const std::map<FormRendererId, std::unique_ptr<FormStructure>>& + form_structures = autofill_handler->form_structures(); + CHECK(!form_structures.empty()); + + // Make API response with suggestions. + AutofillQueryResponse response; + AutofillQueryResponse::FormSuggestion* form_suggestion; + + form_suggestion = response.add_form_suggestions(); + size_t found_fields_count = 0; + std::vector<FormSignature> signatures; + for (auto& j : form_structures) { + FormData formData = j.second->ToFormData(); + for (size_t i = 0; i < field_ids.size(); ++i) { + for (auto form_field_data : formData.fields) { + if (form_field_data.id_attribute == field_ids[i]) { + autofill::test::AddFieldSuggestionToForm( + form_field_data, + static_cast<autofill::ServerFieldType>(field_types[i]), + form_suggestion); + found_fields_count++; + break; + } + } + } + if (found_fields_count > 0) { + signatures = autofill::test::GetEncodedSignatures(*(j.second)); + break; + } + } + CHECK(found_fields_count == field_ids.size()); + + std::string response_string; + CHECK(response.SerializeToString(&response_string)); + std::string encoded_response_string; + base::Base64Encode(response_string, &encoded_response_string); + autofill_handler->OnLoadedServerPredictionsForTest(encoded_response_string, + signatures); + return true; +} + +static jboolean +JNI_AutofillProviderTestHelper_SimulateMainFramePredictionsAutofillServerResponseForTesting( + JNIEnv* env, + const base::android::JavaParamRef<jobject>& jweb_contents, + const base::android::JavaParamRef<jobjectArray>& jfield_ids, + const base::android::JavaParamRef<jobjectArray>& jfield_types) { + std::vector<base::string16> field_ids; + base::android::AppendJavaStringArrayToStringVector(env, jfield_ids, + &field_ids); + std::vector<std::vector<int>> field_types; + base::android::JavaArrayOfIntArrayToIntVector(env, jfield_types, + &field_types); + + AutofillHandler* autofill_handler = ToMainFrameAutofillHandler(jweb_contents); + const std::map<FormRendererId, std::unique_ptr<FormStructure>>& + form_structures = autofill_handler->form_structures(); + CHECK(!form_structures.empty()); + + // Make API response with suggestions. + AutofillQueryResponse response; + AutofillQueryResponse::FormSuggestion* form_suggestion; + + form_suggestion = response.add_form_suggestions(); + size_t found_fields_count = 0; + std::vector<FormSignature> signatures; + for (auto& j : form_structures) { + FormData formData = j.second->ToFormData(); + for (size_t i = 0; i < field_ids.size(); ++i) { + for (auto form_field_data : formData.fields) { + if (form_field_data.id_attribute == field_ids[i]) { + autofill::test::AddFieldPredictionsToForm( + form_field_data, field_types[i], form_suggestion); + found_fields_count++; + break; + } + } + } + if (found_fields_count > 0) { + signatures = autofill::test::GetEncodedSignatures(*(j.second)); + CHECK(found_fields_count == field_ids.size()); + } + } + + std::string response_string; + CHECK(response.SerializeToString(&response_string)); + std::string encoded_response_string; + base::Base64Encode(response_string, &encoded_response_string); + autofill_handler->OnLoadedServerPredictionsForTest(encoded_response_string, + signatures); + return true; +} + +static void +JNI_AutofillProviderTestHelper_SimulateMainFrameAutofillQueryFailedForTesting( + JNIEnv* env, + const base::android::JavaParamRef<jobject>& jweb_contents) { + AutofillHandler* autofill_handler = ToMainFrameAutofillHandler(jweb_contents); + const std::map<FormRendererId, std::unique_ptr<FormStructure>>& + form_structures = autofill_handler->form_structures(); + // Always use first form. + CHECK(form_structures.size()); + autofill_handler->OnServerRequestErrorForTest( + *(autofill::test::GetEncodedSignatures(*(form_structures.begin()->second)) + .begin()), + AutofillDownloadManager::RequestType::REQUEST_QUERY, 400); +} + +} // namespace autofill diff --git a/chromium/components/autofill/android/provider/test_support/java/src/org/chromium/components/autofill/AutofillHintsServiceTestHelper.java b/chromium/components/autofill/android/provider/test_support/java/src/org/chromium/components/autofill/AutofillHintsServiceTestHelper.java new file mode 100644 index 00000000000..742e98d7471 --- /dev/null +++ b/chromium/components/autofill/android/provider/test_support/java/src/org/chromium/components/autofill/AutofillHintsServiceTestHelper.java @@ -0,0 +1,57 @@ +// 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. + +package org.chromium.components.autofill; + +import android.os.IBinder; + +import org.chromium.base.test.util.CallbackHelper; +import org.chromium.components.autofill_public.IAutofillHintsService; +import org.chromium.components.autofill_public.IViewTypeCallback; +import org.chromium.components.autofill_public.ViewType; + +import java.util.List; + +/** + * This class implements and registers IViewTypeCallback for testing. + */ +public class AutofillHintsServiceTestHelper { + public void registerViewTypeService(IBinder binder) throws Exception { + IAutofillHintsService.Stub.asInterface(binder).registerViewTypeCallback(getBinder()); + } + + private IViewTypeCallback.Stub mBinder = new IViewTypeCallback.Stub() { + @Override + public void onViewTypeAvailable(List<ViewType> viewTypeList) { + mViewTypeList = viewTypeList; + mCallbackHelper.notifyCalled(); + } + + @Override + public void onQueryFailed() { + mQueryFailed = true; + mCallbackHelper.notifyCalled(); + } + }; + + private List<ViewType> mViewTypeList; + private boolean mQueryFailed; + private CallbackHelper mCallbackHelper = new CallbackHelper(); + + public IViewTypeCallback getBinder() { + return mBinder; + } + + public List<ViewType> getViewTypes() { + return mViewTypeList; + } + + public boolean isQueryFailed() { + return mQueryFailed; + } + + public void waitForCallbackInvoked() throws Exception { + mCallbackHelper.waitForCallback(0); + } +} diff --git a/chromium/components/autofill/android/provider/test_support/java/src/org/chromium/components/autofill/AutofillProviderTestHelper.java b/chromium/components/autofill/android/provider/test_support/java/src/org/chromium/components/autofill/AutofillProviderTestHelper.java new file mode 100644 index 00000000000..5db6332bce7 --- /dev/null +++ b/chromium/components/autofill/android/provider/test_support/java/src/org/chromium/components/autofill/AutofillProviderTestHelper.java @@ -0,0 +1,64 @@ +// Copyright 2012 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. + +package org.chromium.components.autofill; + +import android.annotation.TargetApi; +import android.os.Build; + +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeMethods; +import org.chromium.base.annotations.VerifiesOnO; +import org.chromium.content_public.browser.WebContents; + +/** + * The help class for Autofill Provider test to access the native code. + */ +@VerifiesOnO +@TargetApi(Build.VERSION_CODES.O) +@JNINamespace("autofill") +public class AutofillProviderTestHelper { + /** + * Disable the download server for testing to avoid the server response affect the integration + * tests. Must be called before WebContents is created. + */ + public static void disableDownloadServerForTesting() { + AutofillProviderTestHelperJni.get().disableDownloadServerForTesting(); + } + + /** + * Simulate the primary server type only. + */ + public static boolean simulateMainFrameAutofillServerResponseForTesting( + WebContents webContents, String[] fieldIds, int[] fieldTypes) { + return AutofillProviderTestHelperJni.get() + .simulateMainFrameAutofillServerResponseForTesting( + webContents, fieldIds, fieldTypes); + } + + /** + * Simulate the server predictions, the first prediction will be set as primary server type. + */ + public static boolean simulateMainFramePredictionsAutofillServerResponseForTesting( + WebContents webContents, String[] fieldIds, int[][] fieldTypes) { + return AutofillProviderTestHelperJni.get() + .simulateMainFramePredictionsAutofillServerResponseForTesting( + webContents, fieldIds, fieldTypes); + } + + public static void simulateMainFrameAutofillQueryFailedForTesting(WebContents webContents) { + AutofillProviderTestHelperJni.get().simulateMainFrameAutofillQueryFailedForTesting( + webContents); + } + + @NativeMethods + interface Natives { + void disableDownloadServerForTesting(); + boolean simulateMainFrameAutofillServerResponseForTesting( + WebContents webContents, String[] fieldIds, int[] fieldTypes); + boolean simulateMainFramePredictionsAutofillServerResponseForTesting( + WebContents webContents, String[] fieldIds, int[][] fieldTypes); + void simulateMainFrameAutofillQueryFailedForTesting(WebContents webContents); + } +} |