summaryrefslogtreecommitdiff
path: root/chromium/components/autofill/android/provider
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/components/autofill/android/provider')
-rw-r--r--chromium/components/autofill/android/provider/BUILD.gn14
-rw-r--r--chromium/components/autofill/android/provider/DEPS3
-rw-r--r--chromium/components/autofill/android/provider/autofill_provider_android.cc52
-rw-r--r--chromium/components/autofill/android/provider/autofill_provider_android.h6
-rw-r--r--chromium/components/autofill/android/provider/form_data_android.cc16
-rw-r--r--chromium/components/autofill/android/provider/form_data_android.h3
-rw-r--r--chromium/components/autofill/android/provider/form_field_data_android.cc60
-rw-r--r--chromium/components/autofill/android/provider/form_field_data_android.h16
-rw-r--r--chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillHintsService.java74
-rw-r--r--chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java45
-rw-r--r--chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillProvider.java89
-rw-r--r--chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillProviderUMA.java110
-rw-r--r--chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/FormFieldData.java52
-rw-r--r--chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill_public/IAutofillHintsService.aidl22
-rw-r--r--chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill_public/IViewTypeCallback.aidl22
-rw-r--r--chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill_public/OWNERS2
-rw-r--r--chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill_public/README.md53
-rw-r--r--chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill_public/ViewType.aidl7
-rw-r--r--chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill_public/ViewType.java88
-rw-r--r--chromium/components/autofill/android/provider/junit/src/org/chromium/components/autofill/AutofillProviderTest.java8
-rw-r--r--chromium/components/autofill/android/provider/test_support/BUILD.gn41
-rw-r--r--chromium/components/autofill/android/provider/test_support/autofill_provider_test_helper.cc173
-rw-r--r--chromium/components/autofill/android/provider/test_support/java/src/org/chromium/components/autofill/AutofillHintsServiceTestHelper.java57
-rw-r--r--chromium/components/autofill/android/provider/test_support/java/src/org/chromium/components/autofill/AutofillProviderTestHelper.java64
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);
+ }
+}