From c30a6232df03e1efbd9f3b226777b07e087a1122 Mon Sep 17 00:00:00 2001 From: Allan Sandfeld Jensen Date: Mon, 12 Oct 2020 14:27:29 +0200 Subject: BASELINE: Update Chromium to 85.0.4183.140 Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057 Reviewed-by: Allan Sandfeld Jensen --- chromium/components/autofill/android/BUILD.gn | 47 +- chromium/components/autofill/android/OWNERS | 6 - .../autofill/android/autofill_provider_android.cc | 350 --------- .../autofill/android/autofill_provider_android.h | 117 --- .../autofill/android/form_data_android.cc | 112 --- .../autofill/android/form_data_android.h | 73 -- .../autofill/android/form_field_data_android.cc | 101 --- .../autofill/android/form_field_data_android.h | 42 -- .../autofill/AutofillActionModeCallback.java | 60 -- .../autofill/AutofillManagerWrapper.java | 206 ------ .../components/autofill/AutofillProvider.java | 157 ---- .../components/autofill/AutofillProviderImpl.java | 540 -------------- .../components/autofill/AutofillProviderUMA.java | 255 ------- .../org/chromium/components/autofill/FormData.java | 56 -- .../components/autofill/FormFieldData.java | 150 ---- .../components/autofill/android/junit/BUILD.gn | 21 - chromium/components/autofill/android/junit/OWNERS | 2 - .../autofill/AutofillProviderImplTest.java | 116 --- .../components/autofill/android/provider/BUILD.gn | 53 ++ .../components/autofill/android/provider/OWNERS | 2 + .../android/provider/autofill_provider_android.cc | 405 +++++++++++ .../android/provider/autofill_provider_android.h | 127 ++++ .../autofill/android/provider/form_data_android.cc | 112 +++ .../autofill/android/provider/form_data_android.h | 73 ++ .../android/provider/form_field_data_android.cc | 106 +++ .../android/provider/form_field_data_android.h | 42 ++ .../autofill/AutofillActionModeCallback.java | 60 ++ .../autofill/AutofillManagerWrapper.java | 206 ++++++ .../components/autofill/AutofillProvider.java | 786 +++++++++++++++++++++ .../components/autofill/AutofillProviderUMA.java | 255 +++++++ .../org/chromium/components/autofill/FormData.java | 56 ++ .../components/autofill/FormFieldData.java | 162 +++++ .../autofill/android/provider/junit/BUILD.gn | 23 + .../components/autofill/AutofillProviderTest.java | 115 +++ 34 files changed, 2584 insertions(+), 2410 deletions(-) delete mode 100644 chromium/components/autofill/android/autofill_provider_android.cc delete mode 100644 chromium/components/autofill/android/autofill_provider_android.h delete mode 100644 chromium/components/autofill/android/form_data_android.cc delete mode 100644 chromium/components/autofill/android/form_data_android.h delete mode 100644 chromium/components/autofill/android/form_field_data_android.cc delete mode 100644 chromium/components/autofill/android/form_field_data_android.h delete mode 100644 chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillActionModeCallback.java delete mode 100644 chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java delete mode 100644 chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProvider.java delete mode 100644 chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProviderImpl.java delete mode 100644 chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProviderUMA.java delete mode 100644 chromium/components/autofill/android/java/src/org/chromium/components/autofill/FormData.java delete mode 100644 chromium/components/autofill/android/java/src/org/chromium/components/autofill/FormFieldData.java delete mode 100644 chromium/components/autofill/android/junit/BUILD.gn delete mode 100644 chromium/components/autofill/android/junit/OWNERS delete mode 100644 chromium/components/autofill/android/junit/src/org/chromium/components/autofill/AutofillProviderImplTest.java create mode 100644 chromium/components/autofill/android/provider/BUILD.gn create mode 100644 chromium/components/autofill/android/provider/OWNERS create mode 100644 chromium/components/autofill/android/provider/autofill_provider_android.cc create mode 100644 chromium/components/autofill/android/provider/autofill_provider_android.h create mode 100644 chromium/components/autofill/android/provider/form_data_android.cc create mode 100644 chromium/components/autofill/android/provider/form_data_android.h create mode 100644 chromium/components/autofill/android/provider/form_field_data_android.cc create mode 100644 chromium/components/autofill/android/provider/form_field_data_android.h create mode 100644 chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillActionModeCallback.java create mode 100644 chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java create mode 100644 chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillProvider.java create mode 100644 chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillProviderUMA.java create mode 100644 chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/FormData.java create mode 100644 chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/FormFieldData.java create mode 100644 chromium/components/autofill/android/provider/junit/BUILD.gn create mode 100644 chromium/components/autofill/android/provider/junit/src/org/chromium/components/autofill/AutofillProviderTest.java (limited to 'chromium/components/autofill/android') diff --git a/chromium/components/autofill/android/BUILD.gn b/chromium/components/autofill/android/BUILD.gn index f246baeb159..e29c613e982 100644 --- a/chromium/components/autofill/android/BUILD.gn +++ b/chromium/components/autofill/android/BUILD.gn @@ -41,6 +41,7 @@ android_library("autofill_java") { "//base:base_java", "//content/public/android:content_java", "//third_party/android_deps:android_support_v7_appcompat_java", + "//third_party/android_deps:androidx_appcompat_appcompat_resources_java", "//ui/android:ui_java", ] sources = [ @@ -54,49 +55,3 @@ android_library("autofill_java") { ] srcjar_deps = [ ":autofill_core_browser_java_enums" ] } - -android_library("provider_java") { - deps = [ - "//base:base_java", - "//base:jni_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", - "//ui/android:ui_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/AutofillManagerWrapper.java", - "java/src/org/chromium/components/autofill/AutofillProvider.java", - "java/src/org/chromium/components/autofill/AutofillProviderImpl.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", - ] -} - -generate_jni("jni_headers") { - sources = [ - "java/src/org/chromium/components/autofill/AutofillProvider.java", - "java/src/org/chromium/components/autofill/FormData.java", - "java/src/org/chromium/components/autofill/FormFieldData.java", - ] -} - -static_library("provider") { - sources = [ - "autofill_provider_android.cc", - "autofill_provider_android.h", - "form_data_android.cc", - "form_data_android.h", - "form_field_data_android.cc", - "form_field_data_android.h", - ] - deps = [ - ":jni_headers", - "//components/autofill/core/browser:browser", - "//content/public/browser", - ] -} diff --git a/chromium/components/autofill/android/OWNERS b/chromium/components/autofill/android/OWNERS index 7f956d082c5..475b1659fb0 100644 --- a/chromium/components/autofill/android/OWNERS +++ b/chromium/components/autofill/android/OWNERS @@ -1,8 +1,2 @@ file://ui/android/OWNERS -# Files related to integration with system autofill -per-file *autofill_provider*=michaelbai@chromium.org -per-file *AutofillProvider*=michaelbai@chromium.org -per-file *AutofillManagerWrapper*=michaelbai@chromium.org -per-file *form*=michaelbai@chromium.org -per-file *Form*=michaelbai@chromium.org diff --git a/chromium/components/autofill/android/autofill_provider_android.cc b/chromium/components/autofill/android/autofill_provider_android.cc deleted file mode 100644 index 76129371d95..00000000000 --- a/chromium/components/autofill/android/autofill_provider_android.cc +++ /dev/null @@ -1,350 +0,0 @@ -// Copyright 2017 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/autofill_provider_android.h" - -#include - -#include "base/android/jni_android.h" -#include "base/android/jni_array.h" -#include "base/android/jni_string.h" -#include "components/autofill/android/form_data_android.h" -#include "components/autofill/android/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 "content/public/browser/browser_thread.h" -#include "content/public/browser/web_contents.h" -#include "ui/gfx/geometry/rect_f.h" - -using base::android::AttachCurrentThread; -using base::android::ConvertUTF16ToJavaString; -using base::android::ConvertUTF8ToJavaString; -using base::android::JavaRef; -using base::android::ScopedJavaLocalRef; -using content::BrowserThread; -using content::WebContents; -using gfx::RectF; - -namespace autofill { - -using mojom::SubmissionSource; - -AutofillProviderAndroid::AutofillProviderAndroid( - const JavaRef& jcaller, - content::WebContents* web_contents) - : id_(kNoQueryId), web_contents_(web_contents), check_submission_(false) { - OnJavaAutofillProviderChanged(AttachCurrentThread(), jcaller); -} - -void AutofillProviderAndroid::OnJavaAutofillProviderChanged( - JNIEnv* env, - const JavaRef& jcaller) { - // If the current Java object isn't null (e.g., because it hasn't been - // garbage-collected yet), clear its reference to this object. - ScopedJavaLocalRef obj = java_ref_.get(env); - if (!obj.is_null()) { - Java_AutofillProvider_setNativeAutofillProvider(env, obj, 0); - } - - java_ref_ = JavaObjectWeakGlobalRef(env, jcaller); - - // If the new Java object isn't null, set its native object to |this|. - obj = java_ref_.get(env); - if (!obj.is_null()) { - Java_AutofillProvider_setNativeAutofillProvider( - env, obj, reinterpret_cast(this)); - } -} - -AutofillProviderAndroid::~AutofillProviderAndroid() { - JNIEnv* env = AttachCurrentThread(); - ScopedJavaLocalRef obj = java_ref_.get(env); - if (obj.is_null()) - return; - - // Remove the reference to this object on the Java side. - Java_AutofillProvider_setNativeAutofillProvider(env, obj, 0); -} - -void AutofillProviderAndroid::OnQueryFormFieldAutofill( - AutofillHandlerProxy* handler, - int32_t id, - const FormData& form, - const FormFieldData& field, - const gfx::RectF& bounding_box, - bool /*unused_autoselect_first_suggestion*/) { - // The id isn't passed to Java side because Android API guarantees the - // response is always for current session, so we just use the current id - // in response, see OnAutofillAvailable. - DCHECK_CURRENTLY_ON(BrowserThread::UI); - id_ = id; - - // Focus or field value change will also trigger the query, so it should be - // ignored if the form is same. - if (ShouldStartNewSession(handler, form)) - StartNewSession(handler, form, field, bounding_box); -} - -bool AutofillProviderAndroid::ShouldStartNewSession( - AutofillHandlerProxy* handler, - const FormData& form) { - // Only start a new session when form or handler is changed, the change of - // handler indicates query from other frame and a new session is needed. - return !IsCurrentlyLinkedForm(form) || !IsCurrentlyLinkedHandler(handler); -} - -void AutofillProviderAndroid::StartNewSession(AutofillHandlerProxy* handler, - const FormData& form, - const FormFieldData& field, - const gfx::RectF& bounding_box) { - JNIEnv* env = AttachCurrentThread(); - ScopedJavaLocalRef obj = java_ref_.get(env); - if (obj.is_null()) - return; - - form_ = std::make_unique( - form, base::BindRepeating( - &AutofillDriver::TransformBoundingBoxToViewportCoordinates, - base::Unretained(handler->driver()))); - - size_t index; - if (!form_->GetFieldIndex(field, &index)) { - form_.reset(); - return; - } - - FormStructure* form_structure = nullptr; - AutofillField* autofill_field = nullptr; - if (!handler->GetCachedFormAndField(form, field, &form_structure, - &autofill_field)) { - form_structure = nullptr; - } - gfx::RectF transformed_bounding = ToClientAreaBound(bounding_box); - - ScopedJavaLocalRef form_obj = form_->GetJavaPeer(form_structure); - handler_ = handler->GetWeakPtr(); - Java_AutofillProvider_startAutofillSession( - env, obj, form_obj, index, transformed_bounding.x(), - transformed_bounding.y(), transformed_bounding.width(), - transformed_bounding.height()); -} - -void AutofillProviderAndroid::OnAutofillAvailable(JNIEnv* env, - jobject jcaller, - jobject formData) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - if (handler_) { - const FormData& form = form_->GetAutofillValues(); - SendFormDataToRenderer(handler_.get(), id_, form); - } -} - -void AutofillProviderAndroid::OnTextFieldDidChange( - AutofillHandlerProxy* handler, - const FormData& form, - const FormFieldData& field, - const gfx::RectF& bounding_box, - const base::TimeTicks timestamp) { - FireFormFieldDidChanged(handler, form, field, bounding_box); -} - -void AutofillProviderAndroid::OnTextFieldDidScroll( - AutofillHandlerProxy* handler, - const FormData& form, - const FormFieldData& field, - const gfx::RectF& bounding_box) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - size_t index; - if (!IsCurrentlyLinkedHandler(handler) || !IsCurrentlyLinkedForm(form) || - !form_->GetSimilarFieldIndex(field, &index)) - return; - - form_->OnFormFieldDidChange(index, field.value); - JNIEnv* env = AttachCurrentThread(); - ScopedJavaLocalRef obj = java_ref_.get(env); - if (obj.is_null()) - return; - - gfx::RectF transformed_bounding = ToClientAreaBound(bounding_box); - Java_AutofillProvider_onTextFieldDidScroll( - env, obj, index, transformed_bounding.x(), transformed_bounding.y(), - transformed_bounding.width(), transformed_bounding.height()); -} - -void AutofillProviderAndroid::OnSelectControlDidChange( - AutofillHandlerProxy* handler, - const FormData& form, - const FormFieldData& field, - const gfx::RectF& bounding_box) { - if (ShouldStartNewSession(handler, form)) - StartNewSession(handler, form, field, bounding_box); - FireFormFieldDidChanged(handler, form, field, bounding_box); -} - -void AutofillProviderAndroid::FireSuccessfulSubmission( - SubmissionSource source) { - JNIEnv* env = AttachCurrentThread(); - ScopedJavaLocalRef obj = java_ref_.get(env); - if (obj.is_null()) - return; - - Java_AutofillProvider_onFormSubmitted(env, obj, (int)source); - Reset(); -} - -void AutofillProviderAndroid::OnFormSubmitted(AutofillHandlerProxy* handler, - const FormData& form, - bool known_success, - SubmissionSource source) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - if (!IsCurrentlyLinkedHandler(handler) || !IsCurrentlyLinkedForm(form)) - return; - - if (known_success || source == SubmissionSource::FORM_SUBMISSION) { - FireSuccessfulSubmission(source); - return; - } - - check_submission_ = true; - pending_submission_source_ = source; -} - -void AutofillProviderAndroid::OnFocusNoLongerOnForm( - AutofillHandlerProxy* handler) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - if (!IsCurrentlyLinkedHandler(handler)) - return; - - OnFocusChanged(false, 0, RectF()); -} - -void AutofillProviderAndroid::OnFocusOnFormField( - AutofillHandlerProxy* handler, - const FormData& form, - const FormFieldData& field, - const gfx::RectF& bounding_box) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - - size_t index; - if (!IsCurrentlyLinkedHandler(handler) || !IsCurrentlyLinkedForm(form) || - !form_->GetSimilarFieldIndex(field, &index)) - return; - - // Because this will trigger a suggestion query, set request id to browser - // initiated request. - id_ = kNoQueryId; - - OnFocusChanged(true, index, ToClientAreaBound(bounding_box)); -} - -void AutofillProviderAndroid::OnFocusChanged(bool focus_on_form, - size_t index, - const gfx::RectF& bounding_box) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - JNIEnv* env = AttachCurrentThread(); - ScopedJavaLocalRef obj = java_ref_.get(env); - if (obj.is_null()) - return; - - Java_AutofillProvider_onFocusChanged( - env, obj, focus_on_form, index, bounding_box.x(), bounding_box.y(), - bounding_box.width(), bounding_box.height()); -} - -void AutofillProviderAndroid::FireFormFieldDidChanged( - AutofillHandlerProxy* handler, - const FormData& form, - const FormFieldData& field, - const gfx::RectF& bounding_box) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - size_t index; - if (!IsCurrentlyLinkedHandler(handler) || !IsCurrentlyLinkedForm(form) || - !form_->GetSimilarFieldIndex(field, &index)) - return; - - form_->OnFormFieldDidChange(index, field.value); - JNIEnv* env = AttachCurrentThread(); - ScopedJavaLocalRef obj = java_ref_.get(env); - if (obj.is_null()) - return; - - gfx::RectF transformed_bounding = ToClientAreaBound(bounding_box); - Java_AutofillProvider_onFormFieldDidChange( - env, obj, index, transformed_bounding.x(), transformed_bounding.y(), - transformed_bounding.width(), transformed_bounding.height()); -} - -void AutofillProviderAndroid::OnDidFillAutofillFormData( - AutofillHandlerProxy* handler, - const FormData& form, - base::TimeTicks timestamp) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - if (handler != handler_.get() || !IsCurrentlyLinkedForm(form)) - return; - - JNIEnv* env = AttachCurrentThread(); - ScopedJavaLocalRef obj = java_ref_.get(env); - if (obj.is_null()) - return; - - Java_AutofillProvider_onDidFillAutofillFormData(env, obj); -} - -void AutofillProviderAndroid::OnFormsSeen(AutofillHandlerProxy* handler, - const std::vector& forms, - const base::TimeTicks) { - handler_for_testing_ = handler->GetWeakPtr(); - if (!check_submission_) - return; - - if (handler != handler_.get()) - return; - - if (form_.get() == nullptr) - return; - - for (auto const& form : forms) { - if (form_->SimilarFormAs(form)) - return; - } - // The form_ disappeared after it was submitted, we consider the submission - // succeeded. - FireSuccessfulSubmission(pending_submission_source_); -} - -void AutofillProviderAndroid::Reset(AutofillHandlerProxy* handler) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - if (handler == handler_.get()) { - JNIEnv* env = AttachCurrentThread(); - ScopedJavaLocalRef obj = java_ref_.get(env); - if (obj.is_null()) - return; - - Java_AutofillProvider_reset(env, obj); - } -} - -bool AutofillProviderAndroid::IsCurrentlyLinkedHandler( - AutofillHandlerProxy* handler) { - return handler == handler_.get(); -} - -bool AutofillProviderAndroid::IsCurrentlyLinkedForm(const FormData& form) { - return form_ && form_->SimilarFormAs(form); -} - -gfx::RectF AutofillProviderAndroid::ToClientAreaBound( - const gfx::RectF& bounding_box) { - gfx::Rect client_area = web_contents_->GetContainerBounds(); - return bounding_box + client_area.OffsetFromOrigin(); -} - -void AutofillProviderAndroid::Reset() { - form_.reset(nullptr); - id_ = kNoQueryId; - check_submission_ = false; -} - -} // namespace autofill diff --git a/chromium/components/autofill/android/autofill_provider_android.h b/chromium/components/autofill/android/autofill_provider_android.h deleted file mode 100644 index 270a05eea00..00000000000 --- a/chromium/components/autofill/android/autofill_provider_android.h +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2017 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. - -#ifndef COMPONENTS_AUTOFILL_ANDROID_AUTOFILL_PROVIDER_ANDROID_H_ -#define COMPONENTS_AUTOFILL_ANDROID_AUTOFILL_PROVIDER_ANDROID_H_ - -#include "base/android/jni_weak_ref.h" -#include "base/memory/weak_ptr.h" -#include "components/autofill/core/browser/autofill_provider.h" - -namespace content { -class WebContents; -} - -namespace autofill { - -class FormDataAndroid; - -// Android implementation of AutofillProvider, it has one instance per -// WebContents, this class is native peer of AutofillProvider.java. -class AutofillProviderAndroid : public AutofillProvider { - public: - AutofillProviderAndroid(const base::android::JavaRef& jcaller, - content::WebContents* web_contents); - // Invoked when the Java-side AutofillProvider counterpart of this object - // has been changed (either to null or to a new object). - void OnJavaAutofillProviderChanged( - JNIEnv* env, - const base::android::JavaRef& jcaller); - - ~AutofillProviderAndroid() override; - - // AutofillProvider: - void OnQueryFormFieldAutofill( - AutofillHandlerProxy* handler, - int32_t id, - const FormData& form, - const FormFieldData& field, - const gfx::RectF& bounding_box, - bool /*unused_autoselect_first_suggestion*/) override; - void OnTextFieldDidChange(AutofillHandlerProxy* handler, - const FormData& form, - const FormFieldData& field, - const gfx::RectF& bounding_box, - const base::TimeTicks timestamp) override; - void OnTextFieldDidScroll(AutofillHandlerProxy* handler, - const FormData& form, - const FormFieldData& field, - const gfx::RectF& bounding_box) override; - void OnSelectControlDidChange(AutofillHandlerProxy* handler, - const FormData& form, - const FormFieldData& field, - const gfx::RectF& bounding_box) override; - void OnFormSubmitted(AutofillHandlerProxy* handler, - const FormData& form, - bool known_success, - mojom::SubmissionSource source) override; - void OnFocusNoLongerOnForm(AutofillHandlerProxy* handler) override; - void OnFocusOnFormField(AutofillHandlerProxy* handler, - const FormData& form, - const FormFieldData& field, - const gfx::RectF& bounding_box) override; - void OnDidFillAutofillFormData(AutofillHandlerProxy* handler, - const FormData& form, - base::TimeTicks timestamp) override; - void OnFormsSeen(AutofillHandlerProxy* handler, - const std::vector& forms, - const base::TimeTicks timestamp) override; - - void Reset(AutofillHandlerProxy* handler) override; - - // Methods called by Java. - void OnAutofillAvailable(JNIEnv* env, jobject jcaller, jobject form_data); - - private: - void FireSuccessfulSubmission(mojom::SubmissionSource source); - void OnFocusChanged(bool focus_on_form, - size_t index, - const gfx::RectF& bounding_box); - void FireFormFieldDidChanged(AutofillHandlerProxy* handler, - const FormData& form, - const FormFieldData& field, - const gfx::RectF& bounding_box); - - bool IsCurrentlyLinkedHandler(AutofillHandlerProxy* handler); - - bool IsCurrentlyLinkedForm(const FormData& form); - - gfx::RectF ToClientAreaBound(const gfx::RectF& bounding_box); - - bool ShouldStartNewSession(AutofillHandlerProxy* handler, - const FormData& form); - - void StartNewSession(AutofillHandlerProxy* handler, - const FormData& form, - const FormFieldData& field, - const gfx::RectF& bounding_box); - - void Reset(); - - int32_t id_; - std::unique_ptr form_; - base::WeakPtr handler_; - JavaObjectWeakGlobalRef java_ref_; - content::WebContents* web_contents_; - bool check_submission_; - // Valid only if check_submission_ is true. - mojom::SubmissionSource pending_submission_source_; - - base::WeakPtr handler_for_testing_; - - DISALLOW_COPY_AND_ASSIGN(AutofillProviderAndroid); -}; -} // namespace autofill - -#endif // COMPONENTS_AUTOFILL_ANDROID_AUTOFILL_PROVIDER_ANDROID_H_ diff --git a/chromium/components/autofill/android/form_data_android.cc b/chromium/components/autofill/android/form_data_android.cc deleted file mode 100644 index c91a0c62472..00000000000 --- a/chromium/components/autofill/android/form_data_android.cc +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2017 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/form_data_android.h" - -#include "base/android/jni_string.h" -#include "components/autofill/android/form_field_data_android.h" -#include "components/autofill/android/jni_headers/FormData_jni.h" -#include "components/autofill/core/browser/form_structure.h" - -using base::android::AttachCurrentThread; -using base::android::ConvertJavaStringToUTF16; -using base::android::ConvertUTF16ToJavaString; -using base::android::ConvertUTF8ToJavaString; -using base::android::JavaParamRef; -using base::android::ScopedJavaGlobalRef; -using base::android::ScopedJavaLocalRef; - -namespace autofill { - -FormDataAndroid::FormDataAndroid(const FormData& form, - const TransformCallback& callback) - : form_(form), index_(0) { - for (FormFieldData& field : form_.fields) - field.bounds = callback.Run(field.bounds); -} - -FormDataAndroid::~FormDataAndroid() = default; - -ScopedJavaLocalRef FormDataAndroid::GetJavaPeer( - const FormStructure* form_structure) { - // |form_structure| is ephemeral and shouldn't be used outside this call - // stack. - JNIEnv* env = AttachCurrentThread(); - ScopedJavaLocalRef obj = java_ref_.get(env); - if (obj.is_null()) { - for (size_t i = 0; i < form_.fields.size(); ++i) { - fields_.push_back(std::unique_ptr( - new FormFieldDataAndroid(&form_.fields[i]))); - } - if (form_structure) - ApplyHeuristicFieldType(*form_structure); - ScopedJavaLocalRef jname = - ConvertUTF16ToJavaString(env, form_.name); - ScopedJavaLocalRef jhost = - ConvertUTF8ToJavaString(env, form_.url.GetOrigin().spec()); - obj = Java_FormData_createFormData(env, reinterpret_cast(this), - jname, jhost, form_.fields.size()); - java_ref_ = JavaObjectWeakGlobalRef(env, obj); - } - return obj; -} - -const FormData& FormDataAndroid::GetAutofillValues() { - for (std::unique_ptr& field : fields_) - field->GetValue(); - return form_; -} - -ScopedJavaLocalRef FormDataAndroid::GetNextFormFieldData(JNIEnv* env) { - DCHECK(index_ <= fields_.size()); - if (index_ == fields_.size()) - return ScopedJavaLocalRef(); - return fields_[index_++]->GetJavaPeer(); -} - -void FormDataAndroid::OnFormFieldDidChange(size_t index, - const base::string16& value) { - form_.fields[index].value = value; - fields_[index]->OnFormFieldDidChange(value); -} - -bool FormDataAndroid::GetFieldIndex(const FormFieldData& field, size_t* index) { - for (size_t i = 0; i < form_.fields.size(); ++i) { - if (form_.fields[i].SameFieldAs(field)) { - *index = i; - return true; - } - } - return false; -} - -bool FormDataAndroid::GetSimilarFieldIndex(const FormFieldData& field, - size_t* index) { - for (size_t i = 0; i < form_.fields.size(); ++i) { - if (form_.fields[i].SimilarFieldAs(field)) { - *index = i; - return true; - } - } - return false; -} - -bool FormDataAndroid::SimilarFormAs(const FormData& form) { - return form_.SimilarFormAs(form); -} - -void FormDataAndroid::ApplyHeuristicFieldType( - 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())); - if (++form_field_data_android == fields_.end()) - break; - } -} - -} // namespace autofill diff --git a/chromium/components/autofill/android/form_data_android.h b/chromium/components/autofill/android/form_data_android.h deleted file mode 100644 index a2b33c8d917..00000000000 --- a/chromium/components/autofill/android/form_data_android.h +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2017 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. - -#ifndef COMPONENTS_AUTOFILL_ANDROID_FORM_DATA_ANDROID_H_ -#define COMPONENTS_AUTOFILL_ANDROID_FORM_DATA_ANDROID_H_ - -#include "base/android/jni_weak_ref.h" -#include "base/android/scoped_java_ref.h" -#include "components/autofill/core/common/form_data.h" - -namespace autofill { - -class FormFieldDataAndroid; -class FormStructure; - -// This class is native peer of FormData.java, to make autofill::FormData -// available in Java. -class FormDataAndroid { - public: - // The callback func to transform FormFieldData's bounds to viewport's - // coordinates, it is only used in FormDataAndroid constructor and transforms - // bounds in place to avoids an extra copy of FormData. - using TransformCallback = - base::RepeatingCallback; - - FormDataAndroid(const FormData& form, const TransformCallback& callback); - virtual ~FormDataAndroid(); - - base::android::ScopedJavaLocalRef GetJavaPeer( - const FormStructure* form_structure); - - // Get autofill values from Java side and return FormData. - const FormData& GetAutofillValues(); - - base::android::ScopedJavaLocalRef GetNextFormFieldData(JNIEnv* env); - - // Get index of given field, return True and index of focus field if found. - bool GetFieldIndex(const FormFieldData& field, size_t* index); - - // Get index of given field, return True and index of focus field if - // similar field is found. This method compares less attributes than - // GetFieldIndex() does, and should be used when field could be changed - // dynamically, but the changed has no impact on autofill purpose, e.g. css - // style change, see FormFieldData::SimilarFieldAs() for details. - bool GetSimilarFieldIndex(const FormFieldData& field, size_t* index); - - // Return true if this form is similar to the given form. - bool SimilarFormAs(const FormData& form); - - // Invoked when form field which specified by |index| is charged to new - // |value|. - void OnFormFieldDidChange(size_t index, const base::string16& value); - - void ApplyHeuristicFieldType(const FormStructure& form); - - const FormData& form_for_testing() { return form_; } - - private: - // Same as the form passed in from constructor, but FormFieldData's bounds is - // transformed to viewport coordinates. - FormData form_; - std::vector> fields_; - JavaObjectWeakGlobalRef java_ref_; - // keep track of index when popping up fields to Java. - size_t index_; - - DISALLOW_COPY_AND_ASSIGN(FormDataAndroid); -}; - -} // namespace autofill - -#endif // COMPONENTS_AUTOFILL_ANDROID_FORM_DATA_ANDROID_H_ diff --git a/chromium/components/autofill/android/form_field_data_android.cc b/chromium/components/autofill/android/form_field_data_android.cc deleted file mode 100644 index da134267220..00000000000 --- a/chromium/components/autofill/android/form_field_data_android.cc +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2017 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/form_field_data_android.h" - -#include "base/android/jni_array.h" -#include "base/android/jni_string.h" -#include "components/autofill/android/jni_headers/FormFieldData_jni.h" -#include "components/autofill/core/common/autofill_util.h" - -using base::android::AttachCurrentThread; -using base::android::ConvertJavaStringToUTF16; -using base::android::ConvertUTF16ToJavaString; -using base::android::ConvertUTF8ToJavaString; -using base::android::JavaParamRef; -using base::android::JavaRef; -using base::android::ScopedJavaGlobalRef; -using base::android::ScopedJavaLocalRef; -using base::android::ToJavaArrayOfStrings; - -namespace autofill { - -FormFieldDataAndroid::FormFieldDataAndroid(FormFieldData* field) - : heuristic_type_(AutofillType(UNKNOWN_TYPE)), field_ptr_(field) {} - -ScopedJavaLocalRef FormFieldDataAndroid::GetJavaPeer() { - JNIEnv* env = AttachCurrentThread(); - ScopedJavaLocalRef obj = java_ref_.get(env); - if (obj.is_null()) { - ScopedJavaLocalRef jname = - ConvertUTF16ToJavaString(env, field_ptr_->name); - ScopedJavaLocalRef jlabel = - ConvertUTF16ToJavaString(env, field_ptr_->label); - ScopedJavaLocalRef jvalue = - ConvertUTF16ToJavaString(env, field_ptr_->value); - ScopedJavaLocalRef jautocomplete_attr = - ConvertUTF8ToJavaString(env, field_ptr_->autocomplete_attribute); - ScopedJavaLocalRef jplaceholder = - ConvertUTF16ToJavaString(env, field_ptr_->placeholder); - ScopedJavaLocalRef jid = - ConvertUTF16ToJavaString(env, field_ptr_->id_attribute); - ScopedJavaLocalRef jtype = - ConvertUTF8ToJavaString(env, field_ptr_->form_control_type); - ScopedJavaLocalRef joption_values = - ToJavaArrayOfStrings(env, field_ptr_->option_values); - ScopedJavaLocalRef joption_contents = - ToJavaArrayOfStrings(env, field_ptr_->option_contents); - ScopedJavaLocalRef jheuristic_type; - if (!heuristic_type_.IsUnknown()) - jheuristic_type = - ConvertUTF8ToJavaString(env, heuristic_type_.ToString()); - - obj = Java_FormFieldData_createFormFieldData( - env, jname, jlabel, jvalue, jautocomplete_attr, - 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(), - field_ptr_->bounds.right(), field_ptr_->bounds.bottom()); - java_ref_ = JavaObjectWeakGlobalRef(env, obj); - } - return obj; -} - -void FormFieldDataAndroid::GetValue() { - JNIEnv* env = AttachCurrentThread(); - - ScopedJavaLocalRef obj = java_ref_.get(env); - if (obj.is_null()) - return; - - if (IsCheckable(field_ptr_->check_status)) { - bool checked = Java_FormFieldData_isChecked(env, obj); - SetCheckStatus(field_ptr_, true, checked); - } else { - ScopedJavaLocalRef jvalue = Java_FormFieldData_getValue(env, obj); - if (jvalue.is_null()) - return; - field_ptr_->value = ConvertJavaStringToUTF16(env, jvalue); - } - field_ptr_->is_autofilled = true; -} - -void FormFieldDataAndroid::OnFormFieldDidChange(const base::string16& value) { - field_ptr_->value = value; - field_ptr_->is_autofilled = false; - JNIEnv* env = AttachCurrentThread(); - ScopedJavaLocalRef obj = java_ref_.get(env); - if (obj.is_null()) - return; - - Java_FormFieldData_updateValue(env, obj, - ConvertUTF16ToJavaString(env, value)); -} - -bool FormFieldDataAndroid::SimilarFieldAs(const FormFieldData& field) const { - return field_ptr_->SimilarFieldAs(field); -} - -} // namespace autofill diff --git a/chromium/components/autofill/android/form_field_data_android.h b/chromium/components/autofill/android/form_field_data_android.h deleted file mode 100644 index e882bb49704..00000000000 --- a/chromium/components/autofill/android/form_field_data_android.h +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2017 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. - -#ifndef COMPONENTS_AUTOFILL_ANDROID_FORM_FIELD_DATA_ANDROID_H_ -#define COMPONENTS_AUTOFILL_ANDROID_FORM_FIELD_DATA_ANDROID_H_ - -#include "base/android/jni_weak_ref.h" -#include "base/android/scoped_java_ref.h" -#include "components/autofill/core/browser/autofill_type.h" -#include "components/autofill/core/common/form_field_data.h" - -namespace autofill { - -// This class is native peer of FormFieldData.java, makes -// autofill::FormFieldData available in Java. -class FormFieldDataAndroid { - public: - FormFieldDataAndroid(FormFieldData* field); - virtual ~FormFieldDataAndroid() {} - - base::android::ScopedJavaLocalRef 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; - } - - private: - AutofillType heuristic_type_; - // Not owned. - FormFieldData* field_ptr_; - JavaObjectWeakGlobalRef java_ref_; - - DISALLOW_COPY_AND_ASSIGN(FormFieldDataAndroid); -}; - -} // namespace autofill - -#endif // COMPONENTS_AUTOFILL_ANDROID_FORM_FIELD_DATA_ANDROID_H_ diff --git a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillActionModeCallback.java b/chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillActionModeCallback.java deleted file mode 100644 index 6921fcab52c..00000000000 --- a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillActionModeCallback.java +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2017 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.content.Context; -import android.view.ActionMode; -import android.view.Menu; -import android.view.MenuItem; - -/** - * The class to implement autofill context menu. To match the Android native view behavior, the - * autofill context menu only appears when there is no text selected. - */ -public class AutofillActionModeCallback implements ActionMode.Callback { - private final Context mContext; - private final AutofillProvider mAutofillProvider; - private final int mAutofillMenuItemTitle; - private final int mAutofillMenuItem; - - public AutofillActionModeCallback(Context context, AutofillProvider autofillProvider) { - mContext = context; - mAutofillProvider = autofillProvider; - // TODO(michaelbai): Uses the resource directly after sdk roll to Android O MR1. - // crbug.com/740628 - mAutofillMenuItemTitle = - mContext.getResources().getIdentifier("autofill", "string", "android"); - mAutofillMenuItem = mContext.getResources().getIdentifier("autofill", "id", "android"); - } - - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - return mAutofillMenuItemTitle != 0 && mAutofillMenuItem != 0; - } - - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - if (mAutofillMenuItemTitle != 0 && mAutofillProvider.shouldQueryAutofillSuggestion()) { - MenuItem item = menu.add( - Menu.NONE, mAutofillMenuItem, Menu.CATEGORY_SECONDARY, mAutofillMenuItemTitle); - item.setShowAsActionFlags( - MenuItem.SHOW_AS_ACTION_NEVER | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - } - return true; - } - - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - if (item.getItemId() == mAutofillMenuItem) { - mAutofillProvider.queryAutofillSuggestion(); - mode.finish(); - return true; - } - return false; - } - - @Override - public void onDestroyActionMode(ActionMode mode) {} -} diff --git a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java b/chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java deleted file mode 100644 index 0287ac38f97..00000000000 --- a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright 2017 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.content.Context; -import android.graphics.Rect; -import android.os.Build; -import android.view.View; -import android.view.autofill.AutofillManager; -import android.view.autofill.AutofillValue; - -import androidx.annotation.VisibleForTesting; - -import org.chromium.base.Log; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Iterator; - -/** - * The class to call Android's AutofillManager. - */ -@TargetApi(Build.VERSION_CODES.O) -public class AutofillManagerWrapper { - // Don't change TAG, it is used for runtime log. - // 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"; - - /** - * The observer of suggestion window. - */ - public static interface InputUIObserver { void onInputUIShown(); } - - private static class AutofillInputUIMonitor extends AutofillManager.AutofillCallback { - private WeakReference mManager; - - public AutofillInputUIMonitor(AutofillManagerWrapper manager) { - mManager = new WeakReference(manager); - } - - @Override - public void onAutofillEvent(View view, int virtualId, int event) { - AutofillManagerWrapper manager = mManager.get(); - if (manager == null) return; - manager.mIsAutofillInputUIShowing = (event == EVENT_INPUT_SHOWN); - if (event == EVENT_INPUT_SHOWN) manager.notifyInputUIChange(); - } - } - - private static boolean sIsLoggable; - private AutofillManager mAutofillManager; - private boolean mIsAutofillInputUIShowing; - private AutofillInputUIMonitor mMonitor; - private boolean mDestroyed; - private boolean mDisabled; - private ArrayList> mInputUIObservers; - - public AutofillManagerWrapper(Context context) { - updateLogStat(); - if (isLoggable()) log("constructor"); - mAutofillManager = context.getSystemService(AutofillManager.class); - mDisabled = mAutofillManager == null || !mAutofillManager.isEnabled(); - if (mDisabled) { - if (isLoggable()) log("disabled"); - return; - } - - mMonitor = new AutofillInputUIMonitor(this); - mAutofillManager.registerCallback(mMonitor); - } - - public void notifyVirtualValueChanged(View parent, int childId, AutofillValue value) { - if (mDisabled || checkAndWarnIfDestroyed()) return; - if (isLoggable()) log("notifyVirtualValueChanged"); - mAutofillManager.notifyValueChanged(parent, childId, value); - } - - public void commit(int submissionSource) { - if (mDisabled || checkAndWarnIfDestroyed()) return; - if (isLoggable()) log("commit source:" + submissionSource); - mAutofillManager.commit(); - } - - public void cancel() { - if (mDisabled || checkAndWarnIfDestroyed()) return; - if (isLoggable()) log("cancel"); - mAutofillManager.cancel(); - } - - public void notifyVirtualViewEntered(View parent, int childId, Rect absBounds) { - // Log warning only when the autofill is triggered. - if (mDisabled) { - Log.w(TAG, "Autofill is disabled: AutofillManager isn't available in given Context."); - return; - } - if (checkAndWarnIfDestroyed()) return; - if (isLoggable()) log("notifyVirtualViewEntered"); - mAutofillManager.notifyViewEntered(parent, childId, absBounds); - } - - public void notifyVirtualViewExited(View parent, int childId) { - if (mDisabled || checkAndWarnIfDestroyed()) return; - if (isLoggable()) log("notifyVirtualViewExited"); - mAutofillManager.notifyViewExited(parent, childId); - } - - public void requestAutofill(View parent, int virtualId, Rect absBounds) { - if (mDisabled || checkAndWarnIfDestroyed()) return; - if (isLoggable()) log("requestAutofill"); - mAutofillManager.requestAutofill(parent, virtualId, absBounds); - } - - public boolean isAutofillInputUIShowing() { - if (mDisabled || checkAndWarnIfDestroyed()) return false; - if (isLoggable()) log("isAutofillInputUIShowing: " + mIsAutofillInputUIShowing); - return mIsAutofillInputUIShowing; - } - - public void destroy() { - if (mDisabled || checkAndWarnIfDestroyed()) return; - if (isLoggable()) log("destroy"); - try { - // The binder in the autofill service side might already be dropped, - // unregisterCallback() will cause various exceptions in this - // scenario (see crbug.com/1078337), catching RuntimeException here prevents crash. - mAutofillManager.unregisterCallback(mMonitor); - } catch (RuntimeException e) { - // We are not logging anything here since some of the exceptions are raised as 'generic' - // RuntimeException which makes it difficult to catch and ignore separately; and the - // RuntimeException seemed only happen in Android O, therefore, isn't actionable. - } finally { - mAutofillManager = null; - mDestroyed = true; - } - } - - public boolean isDisabled() { - return mDisabled; - } - - private boolean checkAndWarnIfDestroyed() { - if (mDestroyed) { - Log.w(TAG, "Application attempted to call on a destroyed AutofillManagerWrapper", - new Throwable()); - } - return mDestroyed; - } - - public void addInputUIObserver(InputUIObserver observer) { - if (observer == null) return; - if (mInputUIObservers == null) { - mInputUIObservers = new ArrayList>(); - } - mInputUIObservers.add(new WeakReference(observer)); - } - - public void removeInputUIObserver(InputUIObserver observer) { - if (observer == null) return; - for (Iterator> i = mInputUIObservers.listIterator(); - i.hasNext();) { - WeakReference o = i.next(); - if (o.get() == null || o.get() == observer) i.remove(); - } - } - - @VisibleForTesting - public void notifyInputUIChange() { - for (Iterator> i = mInputUIObservers.listIterator(); - i.hasNext();) { - WeakReference o = i.next(); - InputUIObserver observer = o.get(); - if (observer == null) { - i.remove(); - continue; - } - observer.onInputUIShown(); - } - } - - public void notifyNewSessionStarted() { - updateLogStat(); - if (isLoggable()) log("Session starts"); - } - - /** - * Always check isLoggable() before call this method. - */ - public static void log(String log) { - // Log.i() instead of Log.d() is used here because log.d() is stripped out in release build. - Log.i(TAG, log); - } - - public static boolean isLoggable() { - return sIsLoggable; - } - - private static void updateLogStat() { - // Use 'setprop log.tag.AwAutofillManager DEBUG' to enable the log at runtime. - // NOTE: See the comment on TAG above for why this is still AwAutofillManager. - sIsLoggable = Log.isLoggable(TAG, Log.DEBUG); - } -} diff --git a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProvider.java b/chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProvider.java deleted file mode 100644 index a7d05d359a4..00000000000 --- a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProvider.java +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2017 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.util.SparseArray; -import android.view.ViewGroup; -import android.view.ViewStructure; -import android.view.autofill.AutofillValue; - -import org.chromium.base.annotations.CalledByNative; -import org.chromium.base.annotations.JNINamespace; -import org.chromium.base.annotations.NativeMethods; -import org.chromium.content_public.browser.WebContents; - -/** - * This class defines interface of AutofillProvider, it doesn't use chrome's - * autofill service or suggestion UI, instead, uses third party autofill service - * by knowing of format structure and user's input. - * - * AutofillProvider handles one autofill session at time, each call of - * queryFormFieldAutofill cancels previous session and starts a new one, the - * calling of other methods shall associate with current session. - * - */ -@JNINamespace("autofill") -public abstract class AutofillProvider { - public AutofillProvider() {} - - /** - * Invoked when container view is changed. - * - * @param containerView new container view. - */ - public abstract void onContainerViewChanged(ViewGroup containerView); - - public abstract void setWebContents(WebContents webContents); - - /** - * Invoked when autofill value is available, AutofillProvider shall fill the - * form with the provided values. - * - * @param values the array of autofill values, the key is virtual id of form - * field. - */ - public abstract void autofill(final SparseArray values); - - /** - * Invoked when autofill service needs the form structure. - * - * @param structure see View.onProvideAutofillVirtualStructure() - * @param flags see View.onProvideAutofillVirtualStructure() - */ - public abstract void onProvideAutoFillVirtualStructure(ViewStructure structure, int flags); - - /** - * @return whether query autofill suggestion. - */ - public abstract boolean shouldQueryAutofillSuggestion(); - - public abstract void queryAutofillSuggestion(); - - /** - * Invoked when filling form is need. AutofillProvider shall ask autofill - * service for the values with which to fill the form. - * - * @param formData the form needs to fill. - * @param focus the index of focus field in formData - * @param x the boundary of focus field. - * @param y the boundary of focus field. - * @param width the boundary of focus field. - * @param height the boundary of focus field. - */ - @CalledByNative - protected abstract void startAutofillSession( - FormData formData, int focus, float x, float y, float width, float height); - - /** - * Invoked when form field's value is changed. - * - * @param index index of field in current form. - * @param x the boundary of focus field. - * @param y the boundary of focus field. - * @param width the boundary of focus field. - * @param height the boundary of focus field. - * - */ - @CalledByNative - protected abstract void onFormFieldDidChange( - int index, float x, float y, float width, float height); - - /** - * Invoked when text field is scrolled. - * - * @param index index of field in current form. - * @param x the boundary of focus field. - * @param y the boundary of focus field. - * @param width the boundary of focus field. - * @param height the boundary of focus field. - * - */ - @CalledByNative - protected abstract void onTextFieldDidScroll( - int index, float x, float y, float width, float height); - - /** - * Invoked when current form will be submitted. - * @param submissionSource the submission source, could be any member defined in - * SubmissionSource.java - */ - @CalledByNative - protected abstract void onFormSubmitted(int submissionSource); - - /** - * Invoked when focus field changed. - * - * @param focusOnForm whether focus is still on form. - * @param focusItem the index of field has focus - * @param x the boundary of focus field. - * @param y the boundary of focus field. - * @param width the boundary of focus field. - * @param height the boundary of focus field. - */ - @CalledByNative - protected abstract void onFocusChanged( - boolean focusOnForm, int focusItem, float x, float y, float width, float height); - - /** - * Send form to renderer for filling. - * - * @param nativeAutofillProvider the native autofill provider. - * @param formData the form to fill. - */ - protected void autofill(long nativeAutofillProvider, FormData formData) { - AutofillProviderJni.get().onAutofillAvailable( - nativeAutofillProvider, AutofillProvider.this, formData); - } - - /** - * Invoked when current query need to be reset. - */ - @CalledByNative - protected abstract void reset(); - - @CalledByNative - protected abstract void setNativeAutofillProvider(long nativeAutofillProvider); - - @CalledByNative - protected abstract void onDidFillAutofillFormData(); - - @NativeMethods - interface Natives { - void onAutofillAvailable( - long nativeAutofillProviderAndroid, AutofillProvider caller, FormData formData); - } -} diff --git a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProviderImpl.java b/chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProviderImpl.java deleted file mode 100644 index e53188067c3..00000000000 --- a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProviderImpl.java +++ /dev/null @@ -1,540 +0,0 @@ -// Copyright 2017 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.content.Context; -import android.graphics.Matrix; -import android.graphics.Rect; -import android.graphics.RectF; -import android.os.Build; -import android.os.Bundle; -import android.util.SparseArray; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewStructure; -import android.view.autofill.AutofillValue; - -import androidx.annotation.VisibleForTesting; - -import org.chromium.base.Log; -import org.chromium.base.ThreadUtils; -import org.chromium.base.annotations.DoNotInline; -import org.chromium.base.metrics.ScopedSysTraceEvent; -import org.chromium.components.version_info.VersionConstants; -import org.chromium.content_public.browser.WebContents; -import org.chromium.ui.base.WindowAndroid; -import org.chromium.ui.display.DisplayAndroid; - -/** - * This class uses Android autofill service to fill web form. All methods are - * supposed to be called in UI thread. - * - * This class doesn't have 1:1 mapping to native AutofillProviderAndroid; the - * normal ownership model is that this object is owned by the embedder-specific - * Java WebContents wrapper (e.g., AwContents.java in //android_webview), and - * AutofillProviderAndroid is owned by the embedder-specific C++ WebContents - * wrapper (e.g., native AwContents in //android_webview). - * - * DoNotInline since it causes class verification errors, see crbug.com/991851. - */ -@DoNotInline -@TargetApi(Build.VERSION_CODES.O) -public class AutofillProviderImpl extends AutofillProvider { - private static final String TAG = "AutofillProviderImpl"; - private static class FocusField { - public final short fieldIndex; - public final Rect absBound; - - public FocusField(short fieldIndex, Rect absBound) { - this.fieldIndex = fieldIndex; - this.absBound = absBound; - } - } - /** - * The class to wrap the request to framework. - * - * Though framework guarantees always giving us the autofill value of current - * session, we still want to verify this by using unique virtual id which is - * composed of sessionId and form field index, we don't use the request id - * which comes from renderer as session id because it is not unique. - */ - private static class AutofillRequest { - private static final int INIT_ID = 1; // ID can't be 0 in Android. - private static int sSessionId = INIT_ID; - public final int sessionId; - private FormData mFormData; - private FocusField mFocusField; - - public AutofillRequest(FormData formData, FocusField focus) { - sessionId = getNextClientId(); - mFormData = formData; - mFocusField = focus; - } - - public void fillViewStructure(ViewStructure structure) { - structure.setWebDomain(mFormData.mHost); - structure.setHtmlInfo(structure.newHtmlInfoBuilder("form") - .addAttribute("name", mFormData.mName) - .build()); - int index = structure.addChildCount(mFormData.mFields.size()); - short fieldIndex = 0; - for (FormFieldData field : mFormData.mFields) { - ViewStructure child = structure.newChild(index++); - int virtualId = toVirtualId(sessionId, fieldIndex++); - child.setAutofillId(structure.getAutofillId(), virtualId); - if (field.mAutocompleteAttr != null && !field.mAutocompleteAttr.isEmpty()) { - child.setAutofillHints(field.mAutocompleteAttr.split(" +")); - } - child.setHint(field.mPlaceholder); - - RectF bounds = field.getBoundsInContainerViewCoordinates(); - // Field has no scroll. - child.setDimens((int) bounds.left, (int) bounds.top, 0 /* scrollX*/, - 0 /* scrollY */, (int) bounds.width(), (int) bounds.height()); - - ViewStructure.HtmlInfo.Builder builder = - child.newHtmlInfoBuilder("input") - .addAttribute("name", field.mName) - .addAttribute("type", field.mType) - .addAttribute("label", field.mLabel) - .addAttribute("ua-autofill-hints", field.mHeuristicType) - .addAttribute("id", field.mId); - - switch (field.getControlType()) { - case FormFieldData.ControlType.LIST: - child.setAutofillType(View.AUTOFILL_TYPE_LIST); - child.setAutofillOptions(field.mOptionContents); - int i = findIndex(field.mOptionValues, field.getValue()); - if (i != -1) { - child.setAutofillValue(AutofillValue.forList(i)); - } - break; - case FormFieldData.ControlType.TOGGLE: - child.setAutofillType(View.AUTOFILL_TYPE_TOGGLE); - child.setAutofillValue(AutofillValue.forToggle(field.isChecked())); - break; - case FormFieldData.ControlType.TEXT: - child.setAutofillType(View.AUTOFILL_TYPE_TEXT); - child.setAutofillValue(AutofillValue.forText(field.getValue())); - if (field.mMaxLength != 0) { - builder.addAttribute("maxlength", String.valueOf(field.mMaxLength)); - } - break; - default: - break; - } - child.setHtmlInfo(builder.build()); - } - } - - public boolean autofill(final SparseArray values) { - for (int i = 0; i < values.size(); ++i) { - int id = values.keyAt(i); - if (toSessionId(id) != sessionId) return false; - AutofillValue value = values.get(id); - if (value == null) continue; - short index = toIndex(id); - if (index < 0 || index >= mFormData.mFields.size()) return false; - FormFieldData field = mFormData.mFields.get(index); - if (field == null) return false; - try { - switch (field.getControlType()) { - case FormFieldData.ControlType.LIST: - int j = value.getListValue(); - if (j < 0 && j >= field.mOptionValues.length) continue; - field.setAutofillValue(field.mOptionValues[j]); - break; - case FormFieldData.ControlType.TOGGLE: - field.setChecked(value.getToggleValue()); - break; - case FormFieldData.ControlType.TEXT: - field.setAutofillValue((String) value.getTextValue()); - break; - default: - break; - } - } catch (IllegalStateException e) { - // Refer to crbug.com/1080580 . - Log.e(TAG, "The given AutofillValue wasn't expected, abort autofill.", e); - return false; - } - } - return true; - } - - public void setFocusField(FocusField focusField) { - mFocusField = focusField; - } - - public FocusField getFocusField() { - return mFocusField; - } - - public int getFieldCount() { - return mFormData.mFields.size(); - } - - public AutofillValue getFieldNewValue(int index) { - FormFieldData field = mFormData.mFields.get(index); - if (field == null) return null; - switch (field.getControlType()) { - case FormFieldData.ControlType.LIST: - int i = findIndex(field.mOptionValues, field.getValue()); - if (i == -1) return null; - return AutofillValue.forList(i); - case FormFieldData.ControlType.TOGGLE: - return AutofillValue.forToggle(field.isChecked()); - case FormFieldData.ControlType.TEXT: - return AutofillValue.forText(field.getValue()); - default: - return null; - } - } - - public int getVirtualId(short index) { - return toVirtualId(sessionId, index); - } - - public FormFieldData getField(short index) { - return mFormData.mFields.get(index); - } - - private static int findIndex(String[] values, String value) { - if (values != null && value != null) { - for (int i = 0; i < values.length; i++) { - if (value.equals(values[i])) return i; - } - } - return -1; - } - - private static int getNextClientId() { - ThreadUtils.assertOnUiThread(); - if (sSessionId == 0xffff) sSessionId = INIT_ID; - return sSessionId++; - } - - private static int toSessionId(int virtualId) { - return (virtualId & 0xffff0000) >> 16; - } - - private static short toIndex(int virtualId) { - return (short) (virtualId & 0xffff); - } - - private static int toVirtualId(int clientId, short index) { - return (clientId << 16) | index; - } - } - - private final String mProviderName; - private AutofillManagerWrapper mAutofillManager; - private ViewGroup mContainerView; - private WebContents mWebContents; - - private AutofillRequest mRequest; - private long mNativeAutofillProvider; - private AutofillProviderUMA mAutofillUMA; - private AutofillManagerWrapper.InputUIObserver mInputUIObserver; - private long mAutofillTriggeredTimeMillis; - - public AutofillProviderImpl(Context context, ViewGroup containerView, String providerName) { - this(containerView, new AutofillManagerWrapper(context), context, providerName); - } - - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - public AutofillProviderImpl(ViewGroup containerView, AutofillManagerWrapper manager, - Context context, String providerName) { - mProviderName = providerName; - try (ScopedSysTraceEvent e = - ScopedSysTraceEvent.scoped("AutofillProviderImpl.constructor")) { - assert Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; - mAutofillManager = manager; - mContainerView = containerView; - mAutofillUMA = new AutofillProviderUMA(context); - mInputUIObserver = new AutofillManagerWrapper.InputUIObserver() { - @Override - public void onInputUIShown() { - // Not need to report suggestion window displayed if there is no live autofill - // session. - if (mRequest == null) return; - mAutofillUMA.onSuggestionDisplayed( - System.currentTimeMillis() - mAutofillTriggeredTimeMillis); - } - }; - mAutofillManager.addInputUIObserver(mInputUIObserver); - } - } - - @Override - public void onContainerViewChanged(ViewGroup containerView) { - mContainerView = containerView; - } - - @Override - public void onProvideAutoFillVirtualStructure(ViewStructure structure, int flags) { - // This method could be called for the session started by the native - // control outside of the scope of autofill, e.g. the URL bar, in this case, we simply - // return. - if (mRequest == null) return; - - Bundle bundle = structure.getExtras(); - if (bundle != null) { - bundle.putCharSequence("VIRTUAL_STRUCTURE_PROVIDER_NAME", mProviderName); - bundle.putCharSequence( - "VIRTUAL_STRUCTURE_PROVIDER_VERSION", VersionConstants.PRODUCT_VERSION); - } - mRequest.fillViewStructure(structure); - if (AutofillManagerWrapper.isLoggable()) { - AutofillManagerWrapper.log( - "onProvideAutoFillVirtualStructure fields:" + structure.getChildCount()); - } - mAutofillUMA.onVirtualStructureProvided(); - } - - @Override - public void autofill(final SparseArray values) { - if (mNativeAutofillProvider != 0 && mRequest != null && mRequest.autofill((values))) { - autofill(mNativeAutofillProvider, mRequest.mFormData); - if (AutofillManagerWrapper.isLoggable()) { - AutofillManagerWrapper.log("autofill values:" + values.size()); - } - mAutofillUMA.onAutofill(); - } - } - - @Override - public boolean shouldQueryAutofillSuggestion() { - return mRequest != null && mRequest.getFocusField() != null - && !mAutofillManager.isAutofillInputUIShowing(); - } - - @Override - public void queryAutofillSuggestion() { - if (shouldQueryAutofillSuggestion()) { - FocusField focusField = mRequest.getFocusField(); - mAutofillManager.requestAutofill(mContainerView, - mRequest.getVirtualId(focusField.fieldIndex), focusField.absBound); - } - } - - @Override - public void startAutofillSession( - FormData formData, int focus, float x, float y, float width, float height) { - // Check focusField inside short value? - // Autofill Manager might have session that wasn't started by AutofillProviderImpl, - // we just always cancel existing session here. - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { - mAutofillManager.cancel(); - } - mAutofillManager.notifyNewSessionStarted(); - Rect absBound = transformToWindowBounds(new RectF(x, y, x + width, y + height)); - if (mRequest != null) notifyViewExitBeforeDestoryRequest(); - transformFormFieldToContainViewCoordinates(formData); - mRequest = new AutofillRequest(formData, new FocusField((short) focus, absBound)); - int virtualId = mRequest.getVirtualId((short) focus); - mAutofillManager.notifyVirtualViewEntered(mContainerView, virtualId, absBound); - mAutofillUMA.onSessionStarted(mAutofillManager.isDisabled()); - mAutofillTriggeredTimeMillis = System.currentTimeMillis(); - } - - @Override - public void onFormFieldDidChange(int index, float x, float y, float width, float height) { - // Check index inside short value? - if (mRequest == null) return; - - short sIndex = (short) index; - FocusField focusField = mRequest.getFocusField(); - if (focusField == null || sIndex != focusField.fieldIndex) { - onFocusChangedImpl(true, index, x, y, width, height, true /*causedByValueChange*/); - } else { - // Currently there is no api to notify both value and position - // change, before the API is available, we still need to call - // notifyVirtualViewEntered() to tell current coordinates because - // the position could be changed. - int virtualId = mRequest.getVirtualId(sIndex); - Rect absBound = transformToWindowBounds(new RectF(x, y, x + width, y + height)); - if (!focusField.absBound.equals(absBound)) { - mAutofillManager.notifyVirtualViewExited(mContainerView, virtualId); - mAutofillManager.notifyVirtualViewEntered(mContainerView, virtualId, absBound); - // Update focus field position. - mRequest.setFocusField(new FocusField(focusField.fieldIndex, absBound)); - } - } - notifyVirtualValueChanged(index); - mAutofillUMA.onUserChangeFieldValue(mRequest.getField(sIndex).hasPreviouslyAutofilled()); - } - - @Override - public void onTextFieldDidScroll(int index, float x, float y, float width, float height) { - // crbug.com/730764 - from P and above, Android framework listens to the onScrollChanged() - // and repositions the autofill UI automatically. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) return; - if (mRequest == null) return; - - short sIndex = (short) index; - FocusField focusField = mRequest.getFocusField(); - if (focusField == null || sIndex != focusField.fieldIndex) return; - - int virtualId = mRequest.getVirtualId(sIndex); - Rect absBound = transformToWindowBounds(new RectF(x, y, x + width, y + height)); - // Notify the new position to the Android framework. Note that we do not call - // notifyVirtualViewExited() here intentionally to avoid flickering. - mAutofillManager.notifyVirtualViewEntered(mContainerView, virtualId, absBound); - - // Update focus field position. - mRequest.setFocusField(new FocusField(focusField.fieldIndex, absBound)); - } - - private void notifyVirtualValueChanged(int index) { - AutofillValue autofillValue = mRequest.getFieldNewValue(index); - if (autofillValue == null) return; - mAutofillManager.notifyVirtualValueChanged( - mContainerView, mRequest.getVirtualId((short) index), autofillValue); - } - - @Override - public void onFormSubmitted(int submissionSource) { - // The changes could be missing, like those made by Javascript, we'd better to notify - // AutofillManager current values. also see crbug.com/353001 and crbug.com/732856. - notifyFormValues(); - mAutofillManager.commit(submissionSource); - mRequest = null; - mAutofillUMA.onFormSubmitted(submissionSource); - } - - @Override - public void onFocusChanged( - boolean focusOnForm, int focusField, float x, float y, float width, float height) { - onFocusChangedImpl( - focusOnForm, focusField, x, y, width, height, false /*causedByValueChange*/); - } - - private void notifyViewExitBeforeDestoryRequest() { - if (mRequest == null) return; - FocusField focusField = mRequest.getFocusField(); - if (focusField == null) return; - mAutofillManager.notifyVirtualViewExited( - mContainerView, mRequest.getVirtualId(focusField.fieldIndex)); - mRequest.setFocusField(null); - } - - private void onFocusChangedImpl(boolean focusOnForm, int focusField, float x, float y, - float width, float height, boolean causedByValueChange) { - // Check focusField inside short value? - // FocusNoLongerOnForm is called after form submitted. - if (mRequest == null) return; - FocusField prev = mRequest.getFocusField(); - if (focusOnForm) { - Rect absBound = transformToWindowBounds(new RectF(x, y, x + width, y + height)); - if (prev != null && prev.fieldIndex == focusField && absBound.equals(prev.absBound)) { - return; - } - - // Notify focus changed. - if (prev != null) { - mAutofillManager.notifyVirtualViewExited( - mContainerView, mRequest.getVirtualId(prev.fieldIndex)); - } - - mAutofillManager.notifyVirtualViewEntered( - mContainerView, mRequest.getVirtualId((short) focusField), absBound); - - if (!causedByValueChange) { - // The focus field value might not sync with platform's - // AutofillManager, just notify it value changed. - notifyVirtualValueChanged(focusField); - mAutofillTriggeredTimeMillis = System.currentTimeMillis(); - } - mRequest.setFocusField(new FocusField((short) focusField, absBound)); - } else { - if (prev == null) return; - // Notify focus changed. - mAutofillManager.notifyVirtualViewExited( - mContainerView, mRequest.getVirtualId(prev.fieldIndex)); - mRequest.setFocusField(null); - } - } - - @Override - protected void reset() { - // We don't need to reset anything here, it should be safe to cancel - // current autofill session when new one starts in - // startAutofillSession(). - } - - @Override - protected void setNativeAutofillProvider(long nativeAutofillProvider) { - if (nativeAutofillProvider == mNativeAutofillProvider) return; - // Setting the mNativeAutofillProvider to 0 may occur as a - // result of WebView.destroy, or because a WebView has been - // gc'ed. In the former case we can go ahead and clean up the - // frameworks autofill manager, but in the latter case the - // binder connection has already been dropped in a framework - // finalizer, and so the methods we call will throw. It's not - // possible to know which case we're in, so just catch the exception - // in AutofillManagerWrapper.destroy(). - if (mNativeAutofillProvider != 0) mRequest = null; - mNativeAutofillProvider = nativeAutofillProvider; - if (nativeAutofillProvider == 0) mAutofillManager.destroy(); - } - - @Override - public void setWebContents(WebContents webContents) { - if (webContents == mWebContents) return; - if (mWebContents != null) mRequest = null; - mWebContents = webContents; - } - - @Override - protected void onDidFillAutofillFormData() { - notifyFormValues(); - } - - private void notifyFormValues() { - if (mRequest == null) return; - for (int i = 0; i < mRequest.getFieldCount(); ++i) notifyVirtualValueChanged(i); - } - - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - public Rect transformToWindowBounds(RectF rect) { - // Convert bounds to device pixel. - WindowAndroid windowAndroid = mWebContents.getTopLevelNativeWindow(); - DisplayAndroid displayAndroid = windowAndroid.getDisplay(); - float dipScale = displayAndroid.getDipScale(); - RectF bounds = new RectF(rect); - Matrix matrix = new Matrix(); - matrix.setScale(dipScale, dipScale); - int[] location = new int[2]; - mContainerView.getLocationOnScreen(location); - matrix.postTranslate(location[0], location[1]); - matrix.mapRect(bounds); - return new Rect( - (int) bounds.left, (int) bounds.top, (int) bounds.right, (int) bounds.bottom); - } - - /** - * Transform FormFieldData's bounds to ContainView's coordinates and update the bounds with the - * transformed one. - * - * @param formData the form need to be transformed. - */ - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - public void transformFormFieldToContainViewCoordinates(FormData formData) { - WindowAndroid windowAndroid = mWebContents.getTopLevelNativeWindow(); - DisplayAndroid displayAndroid = windowAndroid.getDisplay(); - float dipScale = displayAndroid.getDipScale(); - Matrix matrix = new Matrix(); - matrix.setScale(dipScale, dipScale); - matrix.postTranslate(mContainerView.getScrollX(), mContainerView.getScrollY()); - - for (FormFieldData field : formData.mFields) { - RectF bounds = new RectF(); - matrix.mapRect(bounds, field.getBounds()); - field.setBoundsInContainerViewCoordinates(bounds); - } - } -} diff --git a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProviderUMA.java b/chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProviderUMA.java deleted file mode 100644 index 394cd849c95..00000000000 --- a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProviderUMA.java +++ /dev/null @@ -1,255 +0,0 @@ -// Copyright 2018 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.content.Context; - -import org.chromium.autofill.mojom.SubmissionSource; -import org.chromium.base.ContextUtils; -import org.chromium.base.metrics.RecordHistogram; - -import java.util.concurrent.TimeUnit; - -/** - * The class for AutofillProvider-related UMA. Note that most of the concrete histogram - * names include "WebView"; when this class was originally developed it was WebView-specific, - * and when generalizing it we did not change these names to maintain continuity when - * analyzing the histograms. - */ -public class AutofillProviderUMA { - // Records whether the Autofill service is enabled or not. - public static final String UMA_AUTOFILL_ENABLED = "Autofill.WebView.Enabled"; - - // Records whether the Autofill provider is created by activity context or not. - public static final String UMA_AUTOFILL_CREATED_BY_ACTIVITY_CONTEXT = - "Autofill.WebView.CreatedByActivityContext"; - - // 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. - public static final int SESSION_UNKNOWN = 0; - public static final int NO_CALLBACK_FORM_FRAMEWORK = 1; - public static final int NO_SUGGESTION_USER_CHANGE_FORM_FORM_SUBMITTED = 2; - public static final int NO_SUGGESTION_USER_CHANGE_FORM_NO_FORM_SUBMITTED = 3; - public static final int NO_SUGGESTION_USER_NOT_CHANGE_FORM_FORM_SUBMITTED = 4; - public static final int NO_SUGGESTION_USER_NOT_CHANGE_FORM_NO_FORM_SUBMITTED = 5; - public static final int USER_SELECT_SUGGESTION_USER_CHANGE_FORM_FORM_SUBMITTED = 6; - public static final int USER_SELECT_SUGGESTION_USER_CHANGE_FORM_NO_FORM_SUBMITTED = 7; - public static final int USER_SELECT_SUGGESTION_USER_NOT_CHANGE_FORM_FORM_SUBMITTED = 8; - public static final int USER_SELECT_SUGGESTION_USER_NOT_CHANGE_FORM_NO_FORM_SUBMITTED = 9; - public static final int USER_NOT_SELECT_SUGGESTION_USER_CHANGE_FORM_FORM_SUBMITTED = 10; - public static final int USER_NOT_SELECT_SUGGESTION_USER_CHANGE_FORM_NO_FORM_SUBMITTED = 11; - public static final int USER_NOT_SELECT_SUGGESTION_USER_NOT_CHANGE_FORM_FORM_SUBMITTED = 12; - 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; - - // 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 = - "Autofill.WebView.UserChangedAutofilledField"; - - public static final String UMA_AUTOFILL_SUBMISSION_SOURCE = "Autofill.WebView.SubmissionSource"; - // The possible value of UMA_AUTOFILL_SUBMISSION_SOURCE. - public static final int SAME_DOCUMENT_NAVIGATION = 0; - public static final int XHR_SUCCEEDED = 1; - public static final int FRAME_DETACHED = 2; - public static final int DOM_MUTATION_AFTER_XHR = 3; - public static final int PROBABLY_FORM_SUBMITTED = 4; - public static final int FORM_SUBMISSION = 5; - public static final int SUBMISSION_SOURCE_HISTOGRAM_COUNT = 6; - - // The million seconds from user touched the field to the autofill session starting. - public static final String UMA_AUTOFILL_TRIGGERING_TIME = "Autofill.WebView.TriggeringTime"; - - // The million seconds from the autofill session starting to the suggestion being displayed. - public static final String UMA_AUTOFILL_SUGGESTION_TIME = "Autofill.WebView.SuggestionTime"; - - // The expected time range of time is from 10ms to 2 seconds, and 50 buckets is sufficient. - private static final long MIN_TIME_MILLIS = 10; - private static final long MAX_TIME_MILLIS = TimeUnit.SECONDS.toMillis(2); - private static final int NUM_OF_BUCKETS = 50; - - private static void recordTimesHistogram(String name, long durationMillis) { - RecordHistogram.recordCustomTimesHistogram( - name, durationMillis, MIN_TIME_MILLIS, MAX_TIME_MILLIS, NUM_OF_BUCKETS); - } - - private static class SessionRecorder { - public static final int EVENT_VIRTUAL_STRUCTURE_PROVIDED = 0x1 << 0; - public static final int EVENT_SUGGESTION_DISPLAYED = 0x1 << 1; - public static final int EVENT_FORM_AUTOFILLED = 0x1 << 2; - public static final int EVENT_USER_CHANGED_FIELD_VALUE = 0x1 << 3; - public static final int EVENT_FORM_SUBMITTED = 0x1 << 4; - public static final int EVENT_USER_CHANGED_AUTOFILLED_FIELD = 0x1 << 5; - - private Long mSuggestionTimeMillis; - - public void record(int event) { - // Not record any event until we get EVENT_VIRTUAL_STRUCTURE_PROVIDED which makes the - // following events meaningful. - if (event != EVENT_VIRTUAL_STRUCTURE_PROVIDED && mState == 0) return; - if (EVENT_USER_CHANGED_FIELD_VALUE == event && mUserChangedAutofilledField == null) { - mUserChangedAutofilledField = Boolean.valueOf(false); - } else if (EVENT_USER_CHANGED_AUTOFILLED_FIELD == event) { - if (mUserChangedAutofilledField == null) { - mUserChangedAutofilledField = Boolean.valueOf(true); - } - mUserChangedAutofilledField = true; - event = EVENT_USER_CHANGED_FIELD_VALUE; - } - mState |= event; - } - - public void setSuggestionTimeMillis(long suggestionTimeMillis) { - // Only record first suggestion. - if (mSuggestionTimeMillis == null) { - mSuggestionTimeMillis = Long.valueOf(suggestionTimeMillis); - } - } - - public void recordHistogram() { - RecordHistogram.recordEnumeratedHistogram(UMA_AUTOFILL_AUTOFILL_SESSION, - toUMAAutofillSessionValue(), AUTOFILL_SESSION_HISTOGRAM_COUNT); - // Only record if user ever changed form. - if (mUserChangedAutofilledField != null) { - RecordHistogram.recordBooleanHistogram( - UMA_AUTOFILL_USER_CHANGED_AUTOFILLED_FIELD, mUserChangedAutofilledField); - } - if (mSuggestionTimeMillis != null) { - recordTimesHistogram(UMA_AUTOFILL_SUGGESTION_TIME, mSuggestionTimeMillis); - } - } - - private int toUMAAutofillSessionValue() { - if (mState == 0) { - return NO_CALLBACK_FORM_FRAMEWORK; - } else if (mState == EVENT_VIRTUAL_STRUCTURE_PROVIDED) { - return NO_SUGGESTION_USER_NOT_CHANGE_FORM_NO_FORM_SUBMITTED; - } else if (mState - == (EVENT_VIRTUAL_STRUCTURE_PROVIDED | EVENT_USER_CHANGED_FIELD_VALUE)) { - return NO_SUGGESTION_USER_CHANGE_FORM_NO_FORM_SUBMITTED; - } else if (mState == (EVENT_VIRTUAL_STRUCTURE_PROVIDED | EVENT_FORM_SUBMITTED)) { - return NO_SUGGESTION_USER_NOT_CHANGE_FORM_FORM_SUBMITTED; - } else if (mState - == (EVENT_VIRTUAL_STRUCTURE_PROVIDED | EVENT_USER_CHANGED_FIELD_VALUE - | EVENT_FORM_SUBMITTED)) { - return NO_SUGGESTION_USER_CHANGE_FORM_FORM_SUBMITTED; - } else if (mState - == (EVENT_VIRTUAL_STRUCTURE_PROVIDED | EVENT_SUGGESTION_DISPLAYED - | EVENT_FORM_AUTOFILLED)) { - return USER_SELECT_SUGGESTION_USER_NOT_CHANGE_FORM_NO_FORM_SUBMITTED; - } else if (mState - == (EVENT_VIRTUAL_STRUCTURE_PROVIDED | EVENT_SUGGESTION_DISPLAYED - | EVENT_FORM_AUTOFILLED | EVENT_FORM_SUBMITTED)) { - return USER_SELECT_SUGGESTION_USER_NOT_CHANGE_FORM_FORM_SUBMITTED; - } else if (mState - == (EVENT_VIRTUAL_STRUCTURE_PROVIDED | EVENT_SUGGESTION_DISPLAYED - | EVENT_FORM_AUTOFILLED | EVENT_USER_CHANGED_FIELD_VALUE - | EVENT_FORM_SUBMITTED)) { - return USER_SELECT_SUGGESTION_USER_CHANGE_FORM_FORM_SUBMITTED; - } else if (mState - == (EVENT_VIRTUAL_STRUCTURE_PROVIDED | EVENT_SUGGESTION_DISPLAYED - | EVENT_FORM_AUTOFILLED | EVENT_USER_CHANGED_FIELD_VALUE)) { - return USER_SELECT_SUGGESTION_USER_CHANGE_FORM_NO_FORM_SUBMITTED; - } else if (mState == (EVENT_VIRTUAL_STRUCTURE_PROVIDED | EVENT_SUGGESTION_DISPLAYED)) { - return USER_NOT_SELECT_SUGGESTION_USER_NOT_CHANGE_FORM_NO_FORM_SUBMITTED; - } else if (mState - == (EVENT_VIRTUAL_STRUCTURE_PROVIDED | EVENT_SUGGESTION_DISPLAYED - | EVENT_FORM_SUBMITTED)) { - return USER_NOT_SELECT_SUGGESTION_USER_NOT_CHANGE_FORM_FORM_SUBMITTED; - } else if (mState - == (EVENT_VIRTUAL_STRUCTURE_PROVIDED | EVENT_SUGGESTION_DISPLAYED - | EVENT_USER_CHANGED_FIELD_VALUE | EVENT_FORM_SUBMITTED)) { - return USER_NOT_SELECT_SUGGESTION_USER_CHANGE_FORM_FORM_SUBMITTED; - } else if (mState - == (EVENT_VIRTUAL_STRUCTURE_PROVIDED | EVENT_SUGGESTION_DISPLAYED - | EVENT_USER_CHANGED_FIELD_VALUE)) { - return USER_NOT_SELECT_SUGGESTION_USER_CHANGE_FORM_NO_FORM_SUBMITTED; - } else { - return SESSION_UNKNOWN; - } - } - - private int mState; - private Boolean mUserChangedAutofilledField; - } - - private SessionRecorder mRecorder; - private Boolean mAutofillDisabled; - - public AutofillProviderUMA(Context context) { - RecordHistogram.recordBooleanHistogram(UMA_AUTOFILL_CREATED_BY_ACTIVITY_CONTEXT, - ContextUtils.activityFromContext(context) != null); - } - - public void onFormSubmitted(int submissionSource) { - if (mRecorder != null) mRecorder.record(SessionRecorder.EVENT_FORM_SUBMITTED); - recordSession(); - // We record this no matter autofill service is disabled or not. - RecordHistogram.recordEnumeratedHistogram(UMA_AUTOFILL_SUBMISSION_SOURCE, - toUMASubmissionSource(submissionSource), SUBMISSION_SOURCE_HISTOGRAM_COUNT); - } - - public void onSessionStarted(boolean autofillDisabled) { - // Record autofill status once per instance and only if user triggers the autofill. - if (mAutofillDisabled == null || mAutofillDisabled.booleanValue() != autofillDisabled) { - RecordHistogram.recordBooleanHistogram(UMA_AUTOFILL_ENABLED, !autofillDisabled); - mAutofillDisabled = Boolean.valueOf(autofillDisabled); - } - - if (mRecorder != null) recordSession(); - mRecorder = new SessionRecorder(); - } - - public void onVirtualStructureProvided() { - if (mRecorder != null) mRecorder.record(SessionRecorder.EVENT_VIRTUAL_STRUCTURE_PROVIDED); - } - - public void onSuggestionDisplayed(long suggestionTimeMillis) { - if (mRecorder != null) { - mRecorder.record(SessionRecorder.EVENT_SUGGESTION_DISPLAYED); - mRecorder.setSuggestionTimeMillis(suggestionTimeMillis); - } - } - - public void onAutofill() { - if (mRecorder != null) mRecorder.record(SessionRecorder.EVENT_FORM_AUTOFILLED); - } - - public void onUserChangeFieldValue(boolean isPreviouslyAutofilled) { - if (mRecorder == null) return; - if (isPreviouslyAutofilled) { - mRecorder.record(SessionRecorder.EVENT_USER_CHANGED_AUTOFILLED_FIELD); - } else { - mRecorder.record(SessionRecorder.EVENT_USER_CHANGED_FIELD_VALUE); - } - } - - private void recordSession() { - if (mAutofillDisabled != null && !mAutofillDisabled.booleanValue() && mRecorder != null) { - mRecorder.recordHistogram(); - } - mRecorder = null; - } - - private int toUMASubmissionSource(int source) { - switch (source) { - case SubmissionSource.SAME_DOCUMENT_NAVIGATION: - return SAME_DOCUMENT_NAVIGATION; - case SubmissionSource.XHR_SUCCEEDED: - return XHR_SUCCEEDED; - case SubmissionSource.FRAME_DETACHED: - return FRAME_DETACHED; - case SubmissionSource.DOM_MUTATION_AFTER_XHR: - return DOM_MUTATION_AFTER_XHR; - case SubmissionSource.PROBABLY_FORM_SUBMITTED: - return PROBABLY_FORM_SUBMITTED; - case SubmissionSource.FORM_SUBMISSION: - return FORM_SUBMISSION; - default: - return SUBMISSION_SOURCE_HISTOGRAM_COUNT; - } - } -} diff --git a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/FormData.java b/chromium/components/autofill/android/java/src/org/chromium/components/autofill/FormData.java deleted file mode 100644 index 8069aa887b6..00000000000 --- a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/FormData.java +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2017 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 androidx.annotation.VisibleForTesting; - -import org.chromium.base.annotations.CalledByNative; -import org.chromium.base.annotations.JNINamespace; -import org.chromium.base.annotations.NativeMethods; - -import java.util.ArrayList; - -/** - * The wrap class of native autofill::FormDataAndroid. - */ -@JNINamespace("autofill") -public class FormData { - public final String mName; - public final String mHost; - public final ArrayList mFields; - - @CalledByNative - private static FormData createFormData( - long nativeObj, String name, String origin, int fieldCount) { - return new FormData(nativeObj, name, origin, fieldCount); - } - - private static ArrayList popupFormFields(long nativeObj, int fieldCount) { - FormFieldData formFieldData = FormDataJni.get().getNextFormFieldData(nativeObj); - ArrayList fields = new ArrayList(fieldCount); - while (formFieldData != null) { - fields.add(formFieldData); - formFieldData = FormDataJni.get().getNextFormFieldData(nativeObj); - } - assert fields.size() == fieldCount; - return fields; - } - - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - public FormData(String name, String host, ArrayList fields) { - mName = name; - mHost = host; - mFields = fields; - } - - private FormData(long nativeObj, String name, String host, int fieldCount) { - this(name, host, popupFormFields(nativeObj, fieldCount)); - } - - @NativeMethods - interface Natives { - FormFieldData getNextFormFieldData(long nativeFormDataAndroid); - } -} diff --git a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/FormFieldData.java b/chromium/components/autofill/android/java/src/org/chromium/components/autofill/FormFieldData.java deleted file mode 100644 index 964c73206c7..00000000000 --- a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/FormFieldData.java +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright 2017 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.graphics.RectF; - -import androidx.annotation.IntDef; -import androidx.annotation.VisibleForTesting; - -import org.chromium.base.annotations.CalledByNative; -import org.chromium.base.annotations.JNINamespace; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * The wrap class of native autofill::FormFieldDataAndroid. - */ -@JNINamespace("autofill") -public class FormFieldData { - /** - * Define the control types supported by android.view.autofill.AutofillValue. - */ - @IntDef({ControlType.TEXT, ControlType.TOGGLE, ControlType.LIST}) - @Retention(RetentionPolicy.SOURCE) - public @interface ControlType { - int TEXT = 0; - int TOGGLE = 1; - int LIST = 2; - } - - public final String mLabel; - public final String mName; - public final String mAutocompleteAttr; - public final boolean mShouldAutocomplete; - public final String mPlaceholder; - public final String mType; - public final String mId; - public final String[] mOptionValues; - public final String[] mOptionContents; - public final @ControlType int mControlType; - public final int mMaxLength; - public final String mHeuristicType; - // The bounds in the viewport's coordinates - private final RectF mBounds; - // The bounds in the container view's coordinates. - private RectF mBoundsInContainerViewCoordinates; - - private boolean mIsChecked; - private String mValue; - // Indicates whether mValue is autofilled. - private boolean mAutofilled; - // Indicates whether this fields was autofilled, but changed by user. - private boolean mPreviouslyAutofilled; - - 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) { - mName = name; - mLabel = label; - mValue = value; - mAutocompleteAttr = autocompleteAttr; - mShouldAutocomplete = shouldAutocomplete; - mPlaceholder = placeholder; - mType = type; - mId = id; - mOptionValues = optionValues; - mOptionContents = optionContents; - mIsChecked = isChecked; - if (mOptionValues != null && mOptionValues.length != 0) { - mControlType = ControlType.LIST; - } else if (isCheckField) { - mControlType = ControlType.TOGGLE; - } else { - mControlType = ControlType.TEXT; - } - mMaxLength = maxLength; - mHeuristicType = heuristicType; - mBounds = new RectF(left, top, right, bottom); - } - - public @ControlType int getControlType() { - return mControlType; - } - - public RectF getBounds() { - return mBounds; - } - - public void setBoundsInContainerViewCoordinates(RectF bounds) { - mBoundsInContainerViewCoordinates = bounds; - } - - public RectF getBoundsInContainerViewCoordinates() { - return mBoundsInContainerViewCoordinates; - } - - /** - * @return value of field. - */ - @CalledByNative - public String getValue() { - return mValue; - } - - public void setAutofillValue(String value) { - mValue = value; - updateAutofillState(true); - } - - public void setChecked(boolean checked) { - mIsChecked = checked; - updateAutofillState(true); - } - - @CalledByNative - private void updateValue(String value) { - mValue = value; - updateAutofillState(false); - } - - @CalledByNative - public boolean isChecked() { - return mIsChecked; - } - - public boolean hasPreviouslyAutofilled() { - return mPreviouslyAutofilled; - } - - private void updateAutofillState(boolean autofilled) { - if (mAutofilled && !autofilled) mPreviouslyAutofilled = true; - mAutofilled = autofilled; - } - - @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) { - return new FormFieldData(name, label, value, autocompleteAttr, shouldAutocomplete, - placeholder, type, id, optionValues, optionContents, isCheckField, isChecked, - maxLength, heuristicType, left, top, right, bottom); - } -} diff --git a/chromium/components/autofill/android/junit/BUILD.gn b/chromium/components/autofill/android/junit/BUILD.gn deleted file mode 100644 index 0f58b8ac6fa..00000000000 --- a/chromium/components/autofill/android/junit/BUILD.gn +++ /dev/null @@ -1,21 +0,0 @@ -# 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") - -java_library("components_autofill_junit_tests") { - # Platform checks are broken for Robolectric. See https://crbug.com/1071638. - bypass_platform_checks = true - testonly = true - sources = - [ "src/org/chromium/components/autofill/AutofillProviderImplTest.java" ] - deps = [ - "//base:base_java_test_support", - "//base:base_junit_test_support", - "//components/autofill/android:provider_java", - "//content/public/android:content_java", - "//ui/android:ui_java", - ] -} diff --git a/chromium/components/autofill/android/junit/OWNERS b/chromium/components/autofill/android/junit/OWNERS deleted file mode 100644 index 44a22b15980..00000000000 --- a/chromium/components/autofill/android/junit/OWNERS +++ /dev/null @@ -1,2 +0,0 @@ -michaelbai@chromium.org - diff --git a/chromium/components/autofill/android/junit/src/org/chromium/components/autofill/AutofillProviderImplTest.java b/chromium/components/autofill/android/junit/src/org/chromium/components/autofill/AutofillProviderImplTest.java deleted file mode 100644 index d8c8c2bfd3d..00000000000 --- a/chromium/components/autofill/android/junit/src/org/chromium/components/autofill/AutofillProviderImplTest.java +++ /dev/null @@ -1,116 +0,0 @@ -// 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 static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.graphics.Rect; -import android.graphics.RectF; -import android.view.ViewGroup; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentMatchers; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import org.robolectric.annotation.Config; - -import org.chromium.base.test.BaseRobolectricTestRunner; -import org.chromium.content_public.browser.WebContents; -import org.chromium.ui.base.WindowAndroid; -import org.chromium.ui.display.DisplayAndroid; - -import java.util.ArrayList; - -/** - * The unit tests for AutofillProviderImpl. - */ -@RunWith(BaseRobolectricTestRunner.class) -@Config(manifest = Config.NONE) -public class AutofillProviderImplTest { - private static final float EXPECTED_DIP_SCALE = 2; - private static final int SCROLL_X = 15; - private static final int SCROLL_Y = 155; - private static final int LOCATION_X = 25; - private static final int LOCATION_Y = 255; - - private Context mContext; - private WindowAndroid mWindowAndroid; - private WebContents mWebContents; - private ViewGroup mContainerView; - private AutofillProviderImpl mAutofillProvider; - private DisplayAndroid mDisplayAndroid; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mContext = Mockito.mock(Context.class); - mWindowAndroid = Mockito.mock(WindowAndroid.class); - mDisplayAndroid = Mockito.mock(DisplayAndroid.class); - mWebContents = Mockito.mock(WebContents.class); - mContainerView = Mockito.mock(ViewGroup.class); - mAutofillProvider = - new AutofillProviderImpl(mContext, mContainerView, "AutofillProviderImplTest"); - mAutofillProvider.setWebContents(mWebContents); - - when(mWebContents.getTopLevelNativeWindow()).thenReturn(mWindowAndroid); - when(mWindowAndroid.getDisplay()).thenReturn(mDisplayAndroid); - when(mDisplayAndroid.getDipScale()).thenReturn(EXPECTED_DIP_SCALE); - when(mContainerView.getScrollX()).thenReturn(SCROLL_X); - when(mContainerView.getScrollY()).thenReturn(SCROLL_Y); - doAnswer(new Answer() { - @Override - public Void answer(InvocationOnMock invocation) { - Object[] args = invocation.getArguments(); - int[] location = (int[]) args[0]; - location[0] = LOCATION_X; - location[1] = LOCATION_Y; - return null; - } - }) - .when(mContainerView) - .getLocationOnScreen(ArgumentMatchers.any()); - } - - @Test - public void testTransformFormFieldToContainViewCoordinates() { - ArrayList fields = new ArrayList(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*/)); - 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*/)); - FormData formData = new FormData(null, null, fields); - mAutofillProvider.transformFormFieldToContainViewCoordinates(formData); - RectF result = formData.mFields.get(0).getBoundsInContainerViewCoordinates(); - assertEquals(10 * EXPECTED_DIP_SCALE + SCROLL_X, result.left, 0); - assertEquals(20 * EXPECTED_DIP_SCALE + SCROLL_Y, result.top, 0); - assertEquals(300 * EXPECTED_DIP_SCALE + SCROLL_X, result.right, 0); - assertEquals(60 * EXPECTED_DIP_SCALE + SCROLL_Y, result.bottom, 0); - - result = formData.mFields.get(1).getBoundsInContainerViewCoordinates(); - assertEquals(20 * EXPECTED_DIP_SCALE + SCROLL_X, result.left, 0); - assertEquals(100 * EXPECTED_DIP_SCALE + SCROLL_Y, result.top, 0); - assertEquals(400 * EXPECTED_DIP_SCALE + SCROLL_X, result.right, 0); - assertEquals(200 * EXPECTED_DIP_SCALE + SCROLL_Y, result.bottom, 0); - } - - @Test - public void testTransformToWindowBounds() { - RectF source = new RectF(10, 20, 300, 400); - Rect result = mAutofillProvider.transformToWindowBounds(source); - assertEquals(10 * EXPECTED_DIP_SCALE + LOCATION_X, result.left, 0); - assertEquals(20 * EXPECTED_DIP_SCALE + LOCATION_Y, result.top, 0); - assertEquals(300 * EXPECTED_DIP_SCALE + LOCATION_X, result.right, 0); - assertEquals(400 * EXPECTED_DIP_SCALE + LOCATION_Y, result.bottom, 0); - } -} diff --git a/chromium/components/autofill/android/provider/BUILD.gn b/chromium/components/autofill/android/provider/BUILD.gn new file mode 100644 index 00000000000..263b88afd5e --- /dev/null +++ b/chromium/components/autofill/android/provider/BUILD.gn @@ -0,0 +1,53 @@ +# 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/rules.gni") +import("//build/config/locales.gni") + +android_library("java") { + deps = [ + "//base:base_java", + "//base:jni_java", + "//components/autofill/android:autofill_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", + "//ui/android:ui_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/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", + ] +} + +generate_jni("jni_headers") { + sources = [ + "java/src/org/chromium/components/autofill/AutofillProvider.java", + "java/src/org/chromium/components/autofill/FormData.java", + "java/src/org/chromium/components/autofill/FormFieldData.java", + ] +} + +static_library("provider") { + sources = [ + "autofill_provider_android.cc", + "autofill_provider_android.h", + "form_data_android.cc", + "form_data_android.h", + "form_field_data_android.cc", + "form_field_data_android.h", + ] + deps = [ + ":jni_headers", + "//components/autofill/core/browser:browser", + "//content/public/browser", + "//ui/android", + ] +} diff --git a/chromium/components/autofill/android/provider/OWNERS b/chromium/components/autofill/android/provider/OWNERS new file mode 100644 index 00000000000..44a22b15980 --- /dev/null +++ b/chromium/components/autofill/android/provider/OWNERS @@ -0,0 +1,2 @@ +michaelbai@chromium.org + diff --git a/chromium/components/autofill/android/provider/autofill_provider_android.cc b/chromium/components/autofill/android/provider/autofill_provider_android.cc new file mode 100644 index 00000000000..408ff96c130 --- /dev/null +++ b/chromium/components/autofill/android/provider/autofill_provider_android.cc @@ -0,0 +1,405 @@ +// Copyright 2017 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/autofill_provider_android.h" + +#include + +#include "base/android/jni_android.h" +#include "base/android/jni_array.h" +#include "base/android/jni_string.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 "content/public/browser/browser_thread.h" +#include "content/public/browser/web_contents.h" +#include "ui/android/window_android.h" +#include "ui/gfx/geometry/rect_f.h" + +using base::android::AttachCurrentThread; +using base::android::ConvertJavaStringToUTF16; +using base::android::ConvertUTF16ToJavaString; +using base::android::ConvertUTF8ToJavaString; +using base::android::JavaRef; +using base::android::ScopedJavaLocalRef; +using base::android::ToJavaArrayOfStrings; +using content::BrowserThread; +using content::WebContents; +using gfx::RectF; + +namespace autofill { + +using mojom::SubmissionSource; + +AutofillProviderAndroid::AutofillProviderAndroid( + const JavaRef& jcaller, + content::WebContents* web_contents) + : id_(kNoQueryId), web_contents_(web_contents), check_submission_(false) { + OnJavaAutofillProviderChanged(AttachCurrentThread(), jcaller); +} + +void AutofillProviderAndroid::OnJavaAutofillProviderChanged( + JNIEnv* env, + const JavaRef& jcaller) { + // If the current Java object isn't null (e.g., because it hasn't been + // garbage-collected yet), clear its reference to this object. + ScopedJavaLocalRef obj = java_ref_.get(env); + if (!obj.is_null()) { + Java_AutofillProvider_setNativeAutofillProvider(env, obj, 0); + } + + java_ref_ = JavaObjectWeakGlobalRef(env, jcaller); + + // If the new Java object isn't null, set its native object to |this|. + obj = java_ref_.get(env); + if (!obj.is_null()) { + Java_AutofillProvider_setNativeAutofillProvider( + env, obj, reinterpret_cast(this)); + } +} + +AutofillProviderAndroid::~AutofillProviderAndroid() { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef obj = java_ref_.get(env); + if (obj.is_null()) + return; + + // Remove the reference to this object on the Java side. + Java_AutofillProvider_setNativeAutofillProvider(env, obj, 0); +} + +void AutofillProviderAndroid::OnQueryFormFieldAutofill( + AutofillHandlerProxy* handler, + int32_t id, + const FormData& form, + const FormFieldData& field, + const gfx::RectF& bounding_box, + bool /*unused_autoselect_first_suggestion*/) { + // The id isn't passed to Java side because Android API guarantees the + // response is always for current session, so we just use the current id + // in response, see OnAutofillAvailable. + DCHECK_CURRENTLY_ON(BrowserThread::UI); + id_ = id; + + // Focus or field value change will also trigger the query, so it should be + // ignored if the form is same. + if (ShouldStartNewSession(handler, form)) + StartNewSession(handler, form, field, bounding_box); + + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef obj = java_ref_.get(env); + if (obj.is_null()) + return; + + if (!field.datalist_values.empty()) { + ScopedJavaLocalRef jdatalist_values = + ToJavaArrayOfStrings(env, field.datalist_values); + ScopedJavaLocalRef jdatalist_labels = + ToJavaArrayOfStrings(env, field.datalist_labels); + Java_AutofillProvider_showDatalistPopup( + env, obj, jdatalist_values, jdatalist_labels, + field.text_direction == base::i18n::RIGHT_TO_LEFT); + } +} + +bool AutofillProviderAndroid::ShouldStartNewSession( + AutofillHandlerProxy* handler, + const FormData& form) { + // Only start a new session when form or handler is changed, the change of + // handler indicates query from other frame and a new session is needed. + return !IsCurrentlyLinkedForm(form) || !IsCurrentlyLinkedHandler(handler); +} + +void AutofillProviderAndroid::StartNewSession(AutofillHandlerProxy* handler, + const FormData& form, + const FormFieldData& field, + const gfx::RectF& bounding_box) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef obj = java_ref_.get(env); + if (obj.is_null()) + return; + + form_ = std::make_unique( + form, base::BindRepeating( + &AutofillDriver::TransformBoundingBoxToViewportCoordinates, + base::Unretained(handler->driver()))); + + size_t index; + if (!form_->GetFieldIndex(field, &index)) { + form_.reset(); + return; + } + + FormStructure* form_structure = nullptr; + AutofillField* autofill_field = nullptr; + if (!handler->GetCachedFormAndField(form, field, &form_structure, + &autofill_field)) { + form_structure = nullptr; + } + gfx::RectF transformed_bounding = ToClientAreaBound(bounding_box); + + ScopedJavaLocalRef form_obj = form_->GetJavaPeer(form_structure); + handler_ = handler->GetWeakPtr(); + Java_AutofillProvider_startAutofillSession( + env, obj, form_obj, index, transformed_bounding.x(), + transformed_bounding.y(), transformed_bounding.width(), + transformed_bounding.height()); +} + +void AutofillProviderAndroid::OnAutofillAvailable(JNIEnv* env, + jobject jcaller, + jobject formData) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (handler_) { + const FormData& form = form_->GetAutofillValues(); + SendFormDataToRenderer(handler_.get(), id_, form); + } +} + +void AutofillProviderAndroid::OnAcceptDataListSuggestion(JNIEnv* env, + jobject jcaller, + jstring value) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (auto* handler = handler_.get()) { + RendererShouldAcceptDataListSuggestion( + handler, ConvertJavaStringToUTF16(env, value)); + } +} + +void AutofillProviderAndroid::SetAnchorViewRect(JNIEnv* env, + jobject jcaller, + jobject anchor_view, + jfloat x, + jfloat y, + jfloat width, + jfloat height) { + ui::ViewAndroid* view_android = web_contents_->GetNativeView(); + if (!view_android) + return; + + view_android->SetAnchorRect(ScopedJavaLocalRef(env, anchor_view), + gfx::RectF(x, y, width, height)); +} + +void AutofillProviderAndroid::OnTextFieldDidChange( + AutofillHandlerProxy* handler, + const FormData& form, + const FormFieldData& field, + const gfx::RectF& bounding_box, + const base::TimeTicks timestamp) { + FireFormFieldDidChanged(handler, form, field, bounding_box); +} + +void AutofillProviderAndroid::OnTextFieldDidScroll( + AutofillHandlerProxy* handler, + const FormData& form, + const FormFieldData& field, + const gfx::RectF& bounding_box) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + size_t index; + if (!IsCurrentlyLinkedHandler(handler) || !IsCurrentlyLinkedForm(form) || + !form_->GetSimilarFieldIndex(field, &index)) + return; + + form_->OnFormFieldDidChange(index, field.value); + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef obj = java_ref_.get(env); + if (obj.is_null()) + return; + + gfx::RectF transformed_bounding = ToClientAreaBound(bounding_box); + Java_AutofillProvider_onTextFieldDidScroll( + env, obj, index, transformed_bounding.x(), transformed_bounding.y(), + transformed_bounding.width(), transformed_bounding.height()); +} + +void AutofillProviderAndroid::OnSelectControlDidChange( + AutofillHandlerProxy* handler, + const FormData& form, + const FormFieldData& field, + const gfx::RectF& bounding_box) { + if (ShouldStartNewSession(handler, form)) + StartNewSession(handler, form, field, bounding_box); + FireFormFieldDidChanged(handler, form, field, bounding_box); +} + +void AutofillProviderAndroid::FireSuccessfulSubmission( + SubmissionSource source) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef obj = java_ref_.get(env); + if (obj.is_null()) + return; + + Java_AutofillProvider_onFormSubmitted(env, obj, (int)source); + Reset(); +} + +void AutofillProviderAndroid::OnFormSubmitted(AutofillHandlerProxy* handler, + const FormData& form, + bool known_success, + SubmissionSource source) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (!IsCurrentlyLinkedHandler(handler) || !IsCurrentlyLinkedForm(form)) + return; + + if (known_success || source == SubmissionSource::FORM_SUBMISSION) { + FireSuccessfulSubmission(source); + return; + } + + check_submission_ = true; + pending_submission_source_ = source; +} + +void AutofillProviderAndroid::OnFocusNoLongerOnForm( + AutofillHandlerProxy* handler) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (!IsCurrentlyLinkedHandler(handler)) + return; + + OnFocusChanged(false, 0, RectF()); +} + +void AutofillProviderAndroid::OnFocusOnFormField( + AutofillHandlerProxy* handler, + const FormData& form, + const FormFieldData& field, + const gfx::RectF& bounding_box) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + + size_t index; + if (!IsCurrentlyLinkedHandler(handler) || !IsCurrentlyLinkedForm(form) || + !form_->GetSimilarFieldIndex(field, &index)) + return; + + // Because this will trigger a suggestion query, set request id to browser + // initiated request. + id_ = kNoQueryId; + + OnFocusChanged(true, index, ToClientAreaBound(bounding_box)); +} + +void AutofillProviderAndroid::OnFocusChanged(bool focus_on_form, + size_t index, + const gfx::RectF& bounding_box) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef obj = java_ref_.get(env); + if (obj.is_null()) + return; + + Java_AutofillProvider_onFocusChanged( + env, obj, focus_on_form, index, bounding_box.x(), bounding_box.y(), + bounding_box.width(), bounding_box.height()); +} + +void AutofillProviderAndroid::FireFormFieldDidChanged( + AutofillHandlerProxy* handler, + const FormData& form, + const FormFieldData& field, + const gfx::RectF& bounding_box) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + size_t index; + if (!IsCurrentlyLinkedHandler(handler) || !IsCurrentlyLinkedForm(form) || + !form_->GetSimilarFieldIndex(field, &index)) + return; + + form_->OnFormFieldDidChange(index, field.value); + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef obj = java_ref_.get(env); + if (obj.is_null()) + return; + + gfx::RectF transformed_bounding = ToClientAreaBound(bounding_box); + Java_AutofillProvider_onFormFieldDidChange( + env, obj, index, transformed_bounding.x(), transformed_bounding.y(), + transformed_bounding.width(), transformed_bounding.height()); +} + +void AutofillProviderAndroid::OnDidFillAutofillFormData( + AutofillHandlerProxy* handler, + const FormData& form, + base::TimeTicks timestamp) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (handler != handler_.get() || !IsCurrentlyLinkedForm(form)) + return; + + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef obj = java_ref_.get(env); + if (obj.is_null()) + return; + + Java_AutofillProvider_onDidFillAutofillFormData(env, obj); +} + +void AutofillProviderAndroid::OnFormsSeen(AutofillHandlerProxy* handler, + const std::vector& forms, + const base::TimeTicks) { + handler_for_testing_ = handler->GetWeakPtr(); + if (!check_submission_) + return; + + if (handler != handler_.get()) + return; + + if (form_.get() == nullptr) + return; + + for (auto const& form : forms) { + if (form_->SimilarFormAs(form)) + return; + } + // The form_ disappeared after it was submitted, we consider the submission + // succeeded. + FireSuccessfulSubmission(pending_submission_source_); +} + +void AutofillProviderAndroid::OnHidePopup(AutofillHandlerProxy* handler) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (handler == handler_.get()) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef obj = java_ref_.get(env); + if (obj.is_null()) + return; + + Java_AutofillProvider_hidePopup(env, obj); + } +} + +void AutofillProviderAndroid::Reset(AutofillHandlerProxy* handler) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (handler == handler_.get()) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef obj = java_ref_.get(env); + if (obj.is_null()) + return; + + Java_AutofillProvider_reset(env, obj); + } +} + +bool AutofillProviderAndroid::IsCurrentlyLinkedHandler( + AutofillHandlerProxy* handler) { + return handler == handler_.get(); +} + +bool AutofillProviderAndroid::IsCurrentlyLinkedForm(const FormData& form) { + return form_ && form_->SimilarFormAs(form); +} + +gfx::RectF AutofillProviderAndroid::ToClientAreaBound( + const gfx::RectF& bounding_box) { + gfx::Rect client_area = web_contents_->GetContainerBounds(); + return bounding_box + client_area.OffsetFromOrigin(); +} + +void AutofillProviderAndroid::Reset() { + form_.reset(nullptr); + id_ = kNoQueryId; + check_submission_ = false; +} + +} // namespace autofill diff --git a/chromium/components/autofill/android/provider/autofill_provider_android.h b/chromium/components/autofill/android/provider/autofill_provider_android.h new file mode 100644 index 00000000000..b0830e2338e --- /dev/null +++ b/chromium/components/autofill/android/provider/autofill_provider_android.h @@ -0,0 +1,127 @@ +// Copyright 2017 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. + +#ifndef COMPONENTS_AUTOFILL_ANDROID_PROVIDER_AUTOFILL_PROVIDER_ANDROID_H_ +#define COMPONENTS_AUTOFILL_ANDROID_PROVIDER_AUTOFILL_PROVIDER_ANDROID_H_ + +#include "base/android/jni_weak_ref.h" +#include "base/memory/weak_ptr.h" +#include "components/autofill/core/browser/autofill_provider.h" + +namespace content { +class WebContents; +} + +namespace autofill { + +class FormDataAndroid; + +// Android implementation of AutofillProvider, it has one instance per +// WebContents, this class is native peer of AutofillProvider.java. +class AutofillProviderAndroid : public AutofillProvider { + public: + AutofillProviderAndroid(const base::android::JavaRef& jcaller, + content::WebContents* web_contents); + // Invoked when the Java-side AutofillProvider counterpart of this object + // has been changed (either to null or to a new object). + void OnJavaAutofillProviderChanged( + JNIEnv* env, + const base::android::JavaRef& jcaller); + + ~AutofillProviderAndroid() override; + + // AutofillProvider: + void OnQueryFormFieldAutofill( + AutofillHandlerProxy* handler, + int32_t id, + const FormData& form, + const FormFieldData& field, + const gfx::RectF& bounding_box, + bool /*unused_autoselect_first_suggestion*/) override; + void OnTextFieldDidChange(AutofillHandlerProxy* handler, + const FormData& form, + const FormFieldData& field, + const gfx::RectF& bounding_box, + const base::TimeTicks timestamp) override; + void OnTextFieldDidScroll(AutofillHandlerProxy* handler, + const FormData& form, + const FormFieldData& field, + const gfx::RectF& bounding_box) override; + void OnSelectControlDidChange(AutofillHandlerProxy* handler, + const FormData& form, + const FormFieldData& field, + const gfx::RectF& bounding_box) override; + void OnFormSubmitted(AutofillHandlerProxy* handler, + const FormData& form, + bool known_success, + mojom::SubmissionSource source) override; + void OnFocusNoLongerOnForm(AutofillHandlerProxy* handler) override; + void OnFocusOnFormField(AutofillHandlerProxy* handler, + const FormData& form, + const FormFieldData& field, + const gfx::RectF& bounding_box) override; + void OnDidFillAutofillFormData(AutofillHandlerProxy* handler, + const FormData& form, + base::TimeTicks timestamp) override; + void OnFormsSeen(AutofillHandlerProxy* handler, + const std::vector& forms, + const base::TimeTicks timestamp) override; + void OnHidePopup(AutofillHandlerProxy* handler) override; + + void Reset(AutofillHandlerProxy* handler) override; + + // Methods called by Java. + void OnAutofillAvailable(JNIEnv* env, jobject jcaller, jobject form_data); + void OnAcceptDataListSuggestion(JNIEnv* env, jobject jcaller, jstring value); + + void SetAnchorViewRect(JNIEnv* env, + jobject jcaller, + jobject anchor_view, + jfloat x, + jfloat y, + jfloat width, + jfloat height); + + private: + void FireSuccessfulSubmission(mojom::SubmissionSource source); + void OnFocusChanged(bool focus_on_form, + size_t index, + const gfx::RectF& bounding_box); + void FireFormFieldDidChanged(AutofillHandlerProxy* handler, + const FormData& form, + const FormFieldData& field, + const gfx::RectF& bounding_box); + + bool IsCurrentlyLinkedHandler(AutofillHandlerProxy* handler); + + bool IsCurrentlyLinkedForm(const FormData& form); + + gfx::RectF ToClientAreaBound(const gfx::RectF& bounding_box); + + bool ShouldStartNewSession(AutofillHandlerProxy* handler, + const FormData& form); + + void StartNewSession(AutofillHandlerProxy* handler, + const FormData& form, + const FormFieldData& field, + const gfx::RectF& bounding_box); + + void Reset(); + + int32_t id_; + std::unique_ptr form_; + base::WeakPtr handler_; + JavaObjectWeakGlobalRef java_ref_; + content::WebContents* web_contents_; + bool check_submission_; + // Valid only if check_submission_ is true. + mojom::SubmissionSource pending_submission_source_; + + base::WeakPtr handler_for_testing_; + + DISALLOW_COPY_AND_ASSIGN(AutofillProviderAndroid); +}; +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_ANDROID_PROVIDER_AUTOFILL_PROVIDER_ANDROID_H_ diff --git a/chromium/components/autofill/android/provider/form_data_android.cc b/chromium/components/autofill/android/provider/form_data_android.cc new file mode 100644 index 00000000000..7f24f8967c5 --- /dev/null +++ b/chromium/components/autofill/android/provider/form_data_android.cc @@ -0,0 +1,112 @@ +// Copyright 2017 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/form_data_android.h" + +#include "base/android/jni_string.h" +#include "components/autofill/android/provider/form_field_data_android.h" +#include "components/autofill/android/provider/jni_headers/FormData_jni.h" +#include "components/autofill/core/browser/form_structure.h" + +using base::android::AttachCurrentThread; +using base::android::ConvertJavaStringToUTF16; +using base::android::ConvertUTF16ToJavaString; +using base::android::ConvertUTF8ToJavaString; +using base::android::JavaParamRef; +using base::android::ScopedJavaGlobalRef; +using base::android::ScopedJavaLocalRef; + +namespace autofill { + +FormDataAndroid::FormDataAndroid(const FormData& form, + const TransformCallback& callback) + : form_(form), index_(0) { + for (FormFieldData& field : form_.fields) + field.bounds = callback.Run(field.bounds); +} + +FormDataAndroid::~FormDataAndroid() = default; + +ScopedJavaLocalRef FormDataAndroid::GetJavaPeer( + const FormStructure* form_structure) { + // |form_structure| is ephemeral and shouldn't be used outside this call + // stack. + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef obj = java_ref_.get(env); + if (obj.is_null()) { + for (size_t i = 0; i < form_.fields.size(); ++i) { + fields_.push_back(std::unique_ptr( + new FormFieldDataAndroid(&form_.fields[i]))); + } + if (form_structure) + ApplyHeuristicFieldType(*form_structure); + ScopedJavaLocalRef jname = + ConvertUTF16ToJavaString(env, form_.name); + ScopedJavaLocalRef jhost = + ConvertUTF8ToJavaString(env, form_.url.GetOrigin().spec()); + obj = Java_FormData_createFormData(env, reinterpret_cast(this), + jname, jhost, form_.fields.size()); + java_ref_ = JavaObjectWeakGlobalRef(env, obj); + } + return obj; +} + +const FormData& FormDataAndroid::GetAutofillValues() { + for (std::unique_ptr& field : fields_) + field->GetValue(); + return form_; +} + +ScopedJavaLocalRef FormDataAndroid::GetNextFormFieldData(JNIEnv* env) { + DCHECK(index_ <= fields_.size()); + if (index_ == fields_.size()) + return ScopedJavaLocalRef(); + return fields_[index_++]->GetJavaPeer(); +} + +void FormDataAndroid::OnFormFieldDidChange(size_t index, + const base::string16& value) { + form_.fields[index].value = value; + fields_[index]->OnFormFieldDidChange(value); +} + +bool FormDataAndroid::GetFieldIndex(const FormFieldData& field, size_t* index) { + for (size_t i = 0; i < form_.fields.size(); ++i) { + if (form_.fields[i].SameFieldAs(field)) { + *index = i; + return true; + } + } + return false; +} + +bool FormDataAndroid::GetSimilarFieldIndex(const FormFieldData& field, + size_t* index) { + for (size_t i = 0; i < form_.fields.size(); ++i) { + if (form_.fields[i].SimilarFieldAs(field)) { + *index = i; + return true; + } + } + return false; +} + +bool FormDataAndroid::SimilarFormAs(const FormData& form) { + return form_.SimilarFormAs(form); +} + +void FormDataAndroid::ApplyHeuristicFieldType( + 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())); + if (++form_field_data_android == fields_.end()) + break; + } +} + +} // namespace autofill diff --git a/chromium/components/autofill/android/provider/form_data_android.h b/chromium/components/autofill/android/provider/form_data_android.h new file mode 100644 index 00000000000..8882a788673 --- /dev/null +++ b/chromium/components/autofill/android/provider/form_data_android.h @@ -0,0 +1,73 @@ +// Copyright 2017 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. + +#ifndef COMPONENTS_AUTOFILL_ANDROID_PROVIDER_FORM_DATA_ANDROID_H_ +#define COMPONENTS_AUTOFILL_ANDROID_PROVIDER_FORM_DATA_ANDROID_H_ + +#include "base/android/jni_weak_ref.h" +#include "base/android/scoped_java_ref.h" +#include "components/autofill/core/common/form_data.h" + +namespace autofill { + +class FormFieldDataAndroid; +class FormStructure; + +// This class is native peer of FormData.java, to make autofill::FormData +// available in Java. +class FormDataAndroid { + public: + // The callback func to transform FormFieldData's bounds to viewport's + // coordinates, it is only used in FormDataAndroid constructor and transforms + // bounds in place to avoids an extra copy of FormData. + using TransformCallback = + base::RepeatingCallback; + + FormDataAndroid(const FormData& form, const TransformCallback& callback); + virtual ~FormDataAndroid(); + + base::android::ScopedJavaLocalRef GetJavaPeer( + const FormStructure* form_structure); + + // Get autofill values from Java side and return FormData. + const FormData& GetAutofillValues(); + + base::android::ScopedJavaLocalRef GetNextFormFieldData(JNIEnv* env); + + // Get index of given field, return True and index of focus field if found. + bool GetFieldIndex(const FormFieldData& field, size_t* index); + + // Get index of given field, return True and index of focus field if + // similar field is found. This method compares less attributes than + // GetFieldIndex() does, and should be used when field could be changed + // dynamically, but the changed has no impact on autofill purpose, e.g. css + // style change, see FormFieldData::SimilarFieldAs() for details. + bool GetSimilarFieldIndex(const FormFieldData& field, size_t* index); + + // Return true if this form is similar to the given form. + bool SimilarFormAs(const FormData& form); + + // Invoked when form field which specified by |index| is charged to new + // |value|. + void OnFormFieldDidChange(size_t index, const base::string16& value); + + void ApplyHeuristicFieldType(const FormStructure& form); + + const FormData& form_for_testing() { return form_; } + + private: + // Same as the form passed in from constructor, but FormFieldData's bounds is + // transformed to viewport coordinates. + FormData form_; + std::vector> fields_; + JavaObjectWeakGlobalRef java_ref_; + // keep track of index when popping up fields to Java. + size_t index_; + + DISALLOW_COPY_AND_ASSIGN(FormDataAndroid); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_ANDROID_PROVIDER_FORM_DATA_ANDROID_H_ diff --git a/chromium/components/autofill/android/provider/form_field_data_android.cc b/chromium/components/autofill/android/provider/form_field_data_android.cc new file mode 100644 index 00000000000..ee5471f5239 --- /dev/null +++ b/chromium/components/autofill/android/provider/form_field_data_android.cc @@ -0,0 +1,106 @@ +// Copyright 2017 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/form_field_data_android.h" + +#include "base/android/jni_array.h" +#include "base/android/jni_string.h" +#include "components/autofill/android/provider/jni_headers/FormFieldData_jni.h" +#include "components/autofill/core/common/autofill_util.h" + +using base::android::AttachCurrentThread; +using base::android::ConvertJavaStringToUTF16; +using base::android::ConvertUTF16ToJavaString; +using base::android::ConvertUTF8ToJavaString; +using base::android::JavaParamRef; +using base::android::JavaRef; +using base::android::ScopedJavaGlobalRef; +using base::android::ScopedJavaLocalRef; +using base::android::ToJavaArrayOfStrings; + +namespace autofill { + +FormFieldDataAndroid::FormFieldDataAndroid(FormFieldData* field) + : heuristic_type_(AutofillType(UNKNOWN_TYPE)), field_ptr_(field) {} + +ScopedJavaLocalRef FormFieldDataAndroid::GetJavaPeer() { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef obj = java_ref_.get(env); + if (obj.is_null()) { + ScopedJavaLocalRef jname = + ConvertUTF16ToJavaString(env, field_ptr_->name); + ScopedJavaLocalRef jlabel = + ConvertUTF16ToJavaString(env, field_ptr_->label); + ScopedJavaLocalRef jvalue = + ConvertUTF16ToJavaString(env, field_ptr_->value); + ScopedJavaLocalRef jautocomplete_attr = + ConvertUTF8ToJavaString(env, field_ptr_->autocomplete_attribute); + ScopedJavaLocalRef jplaceholder = + ConvertUTF16ToJavaString(env, field_ptr_->placeholder); + ScopedJavaLocalRef jid = + ConvertUTF16ToJavaString(env, field_ptr_->id_attribute); + ScopedJavaLocalRef jtype = + ConvertUTF8ToJavaString(env, field_ptr_->form_control_type); + ScopedJavaLocalRef joption_values = + ToJavaArrayOfStrings(env, field_ptr_->option_values); + ScopedJavaLocalRef joption_contents = + ToJavaArrayOfStrings(env, field_ptr_->option_contents); + ScopedJavaLocalRef jheuristic_type; + if (!heuristic_type_.IsUnknown()) + jheuristic_type = + ConvertUTF8ToJavaString(env, heuristic_type_.ToString()); + ScopedJavaLocalRef jdatalist_values = + ToJavaArrayOfStrings(env, field_ptr_->datalist_values); + ScopedJavaLocalRef jdatalist_labels = + ToJavaArrayOfStrings(env, field_ptr_->datalist_labels); + + obj = Java_FormFieldData_createFormFieldData( + env, jname, jlabel, jvalue, jautocomplete_attr, + 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(), + field_ptr_->bounds.right(), field_ptr_->bounds.bottom(), + jdatalist_values, jdatalist_labels); + java_ref_ = JavaObjectWeakGlobalRef(env, obj); + } + return obj; +} + +void FormFieldDataAndroid::GetValue() { + JNIEnv* env = AttachCurrentThread(); + + ScopedJavaLocalRef obj = java_ref_.get(env); + if (obj.is_null()) + return; + + if (IsCheckable(field_ptr_->check_status)) { + bool checked = Java_FormFieldData_isChecked(env, obj); + SetCheckStatus(field_ptr_, true, checked); + } else { + ScopedJavaLocalRef jvalue = Java_FormFieldData_getValue(env, obj); + if (jvalue.is_null()) + return; + field_ptr_->value = ConvertJavaStringToUTF16(env, jvalue); + } + field_ptr_->is_autofilled = true; +} + +void FormFieldDataAndroid::OnFormFieldDidChange(const base::string16& value) { + field_ptr_->value = value; + field_ptr_->is_autofilled = false; + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef obj = java_ref_.get(env); + if (obj.is_null()) + return; + + Java_FormFieldData_updateValue(env, obj, + ConvertUTF16ToJavaString(env, value)); +} + +bool FormFieldDataAndroid::SimilarFieldAs(const FormFieldData& field) const { + return field_ptr_->SimilarFieldAs(field); +} + +} // 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 new file mode 100644 index 00000000000..a12ad979bfe --- /dev/null +++ b/chromium/components/autofill/android/provider/form_field_data_android.h @@ -0,0 +1,42 @@ +// Copyright 2017 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. + +#ifndef COMPONENTS_AUTOFILL_ANDROID_PROVIDER_FORM_FIELD_DATA_ANDROID_H_ +#define COMPONENTS_AUTOFILL_ANDROID_PROVIDER_FORM_FIELD_DATA_ANDROID_H_ + +#include "base/android/jni_weak_ref.h" +#include "base/android/scoped_java_ref.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/common/form_field_data.h" + +namespace autofill { + +// This class is native peer of FormFieldData.java, makes +// autofill::FormFieldData available in Java. +class FormFieldDataAndroid { + public: + FormFieldDataAndroid(FormFieldData* field); + virtual ~FormFieldDataAndroid() {} + + base::android::ScopedJavaLocalRef 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; + } + + private: + AutofillType heuristic_type_; + // Not owned. + FormFieldData* field_ptr_; + JavaObjectWeakGlobalRef java_ref_; + + DISALLOW_COPY_AND_ASSIGN(FormFieldDataAndroid); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_ANDROID_PROVIDER_FORM_FIELD_DATA_ANDROID_H_ diff --git a/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillActionModeCallback.java b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillActionModeCallback.java new file mode 100644 index 00000000000..6921fcab52c --- /dev/null +++ b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillActionModeCallback.java @@ -0,0 +1,60 @@ +// Copyright 2017 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.content.Context; +import android.view.ActionMode; +import android.view.Menu; +import android.view.MenuItem; + +/** + * The class to implement autofill context menu. To match the Android native view behavior, the + * autofill context menu only appears when there is no text selected. + */ +public class AutofillActionModeCallback implements ActionMode.Callback { + private final Context mContext; + private final AutofillProvider mAutofillProvider; + private final int mAutofillMenuItemTitle; + private final int mAutofillMenuItem; + + public AutofillActionModeCallback(Context context, AutofillProvider autofillProvider) { + mContext = context; + mAutofillProvider = autofillProvider; + // TODO(michaelbai): Uses the resource directly after sdk roll to Android O MR1. + // crbug.com/740628 + mAutofillMenuItemTitle = + mContext.getResources().getIdentifier("autofill", "string", "android"); + mAutofillMenuItem = mContext.getResources().getIdentifier("autofill", "id", "android"); + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + return mAutofillMenuItemTitle != 0 && mAutofillMenuItem != 0; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + if (mAutofillMenuItemTitle != 0 && mAutofillProvider.shouldQueryAutofillSuggestion()) { + MenuItem item = menu.add( + Menu.NONE, mAutofillMenuItem, Menu.CATEGORY_SECONDARY, mAutofillMenuItemTitle); + item.setShowAsActionFlags( + MenuItem.SHOW_AS_ACTION_NEVER | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + return true; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + if (item.getItemId() == mAutofillMenuItem) { + mAutofillProvider.queryAutofillSuggestion(); + mode.finish(); + return true; + } + return false; + } + + @Override + public void onDestroyActionMode(ActionMode mode) {} +} 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 new file mode 100644 index 00000000000..0287ac38f97 --- /dev/null +++ b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java @@ -0,0 +1,206 @@ +// Copyright 2017 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.content.Context; +import android.graphics.Rect; +import android.os.Build; +import android.view.View; +import android.view.autofill.AutofillManager; +import android.view.autofill.AutofillValue; + +import androidx.annotation.VisibleForTesting; + +import org.chromium.base.Log; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Iterator; + +/** + * The class to call Android's AutofillManager. + */ +@TargetApi(Build.VERSION_CODES.O) +public class AutofillManagerWrapper { + // Don't change TAG, it is used for runtime log. + // 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"; + + /** + * The observer of suggestion window. + */ + public static interface InputUIObserver { void onInputUIShown(); } + + private static class AutofillInputUIMonitor extends AutofillManager.AutofillCallback { + private WeakReference mManager; + + public AutofillInputUIMonitor(AutofillManagerWrapper manager) { + mManager = new WeakReference(manager); + } + + @Override + public void onAutofillEvent(View view, int virtualId, int event) { + AutofillManagerWrapper manager = mManager.get(); + if (manager == null) return; + manager.mIsAutofillInputUIShowing = (event == EVENT_INPUT_SHOWN); + if (event == EVENT_INPUT_SHOWN) manager.notifyInputUIChange(); + } + } + + private static boolean sIsLoggable; + private AutofillManager mAutofillManager; + private boolean mIsAutofillInputUIShowing; + private AutofillInputUIMonitor mMonitor; + private boolean mDestroyed; + private boolean mDisabled; + private ArrayList> mInputUIObservers; + + public AutofillManagerWrapper(Context context) { + updateLogStat(); + if (isLoggable()) log("constructor"); + mAutofillManager = context.getSystemService(AutofillManager.class); + mDisabled = mAutofillManager == null || !mAutofillManager.isEnabled(); + if (mDisabled) { + if (isLoggable()) log("disabled"); + return; + } + + mMonitor = new AutofillInputUIMonitor(this); + mAutofillManager.registerCallback(mMonitor); + } + + public void notifyVirtualValueChanged(View parent, int childId, AutofillValue value) { + if (mDisabled || checkAndWarnIfDestroyed()) return; + if (isLoggable()) log("notifyVirtualValueChanged"); + mAutofillManager.notifyValueChanged(parent, childId, value); + } + + public void commit(int submissionSource) { + if (mDisabled || checkAndWarnIfDestroyed()) return; + if (isLoggable()) log("commit source:" + submissionSource); + mAutofillManager.commit(); + } + + public void cancel() { + if (mDisabled || checkAndWarnIfDestroyed()) return; + if (isLoggable()) log("cancel"); + mAutofillManager.cancel(); + } + + public void notifyVirtualViewEntered(View parent, int childId, Rect absBounds) { + // Log warning only when the autofill is triggered. + if (mDisabled) { + Log.w(TAG, "Autofill is disabled: AutofillManager isn't available in given Context."); + return; + } + if (checkAndWarnIfDestroyed()) return; + if (isLoggable()) log("notifyVirtualViewEntered"); + mAutofillManager.notifyViewEntered(parent, childId, absBounds); + } + + public void notifyVirtualViewExited(View parent, int childId) { + if (mDisabled || checkAndWarnIfDestroyed()) return; + if (isLoggable()) log("notifyVirtualViewExited"); + mAutofillManager.notifyViewExited(parent, childId); + } + + public void requestAutofill(View parent, int virtualId, Rect absBounds) { + if (mDisabled || checkAndWarnIfDestroyed()) return; + if (isLoggable()) log("requestAutofill"); + mAutofillManager.requestAutofill(parent, virtualId, absBounds); + } + + public boolean isAutofillInputUIShowing() { + if (mDisabled || checkAndWarnIfDestroyed()) return false; + if (isLoggable()) log("isAutofillInputUIShowing: " + mIsAutofillInputUIShowing); + return mIsAutofillInputUIShowing; + } + + public void destroy() { + if (mDisabled || checkAndWarnIfDestroyed()) return; + if (isLoggable()) log("destroy"); + try { + // The binder in the autofill service side might already be dropped, + // unregisterCallback() will cause various exceptions in this + // scenario (see crbug.com/1078337), catching RuntimeException here prevents crash. + mAutofillManager.unregisterCallback(mMonitor); + } catch (RuntimeException e) { + // We are not logging anything here since some of the exceptions are raised as 'generic' + // RuntimeException which makes it difficult to catch and ignore separately; and the + // RuntimeException seemed only happen in Android O, therefore, isn't actionable. + } finally { + mAutofillManager = null; + mDestroyed = true; + } + } + + public boolean isDisabled() { + return mDisabled; + } + + private boolean checkAndWarnIfDestroyed() { + if (mDestroyed) { + Log.w(TAG, "Application attempted to call on a destroyed AutofillManagerWrapper", + new Throwable()); + } + return mDestroyed; + } + + public void addInputUIObserver(InputUIObserver observer) { + if (observer == null) return; + if (mInputUIObservers == null) { + mInputUIObservers = new ArrayList>(); + } + mInputUIObservers.add(new WeakReference(observer)); + } + + public void removeInputUIObserver(InputUIObserver observer) { + if (observer == null) return; + for (Iterator> i = mInputUIObservers.listIterator(); + i.hasNext();) { + WeakReference o = i.next(); + if (o.get() == null || o.get() == observer) i.remove(); + } + } + + @VisibleForTesting + public void notifyInputUIChange() { + for (Iterator> i = mInputUIObservers.listIterator(); + i.hasNext();) { + WeakReference o = i.next(); + InputUIObserver observer = o.get(); + if (observer == null) { + i.remove(); + continue; + } + observer.onInputUIShown(); + } + } + + public void notifyNewSessionStarted() { + updateLogStat(); + if (isLoggable()) log("Session starts"); + } + + /** + * Always check isLoggable() before call this method. + */ + public static void log(String log) { + // Log.i() instead of Log.d() is used here because log.d() is stripped out in release build. + Log.i(TAG, log); + } + + public static boolean isLoggable() { + return sIsLoggable; + } + + private static void updateLogStat() { + // Use 'setprop log.tag.AwAutofillManager DEBUG' to enable the log at runtime. + // NOTE: See the comment on TAG above for why this is still AwAutofillManager. + sIsLoggable = Log.isLoggable(TAG, Log.DEBUG); + } +} 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 new file mode 100644 index 00000000000..acbf7a61a22 --- /dev/null +++ b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillProvider.java @@ -0,0 +1,786 @@ +// Copyright 2017 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.content.Context; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.RectF; +import android.os.Build; +import android.os.Bundle; +import android.util.SparseArray; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewStructure; +import android.view.autofill.AutofillValue; + +import androidx.annotation.VisibleForTesting; + +import org.chromium.base.ContextUtils; +import org.chromium.base.Log; +import org.chromium.base.StrictModeContext; +import org.chromium.base.ThreadUtils; +import org.chromium.base.annotations.CalledByNative; +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.version_info.VersionConstants; +import org.chromium.content_public.browser.WebContents; +import org.chromium.content_public.browser.WebContentsAccessibility; +import org.chromium.ui.DropdownItem; +import org.chromium.ui.base.ViewAndroidDelegate; +import org.chromium.ui.base.WindowAndroid; +import org.chromium.ui.display.DisplayAndroid; + +/** + * 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. + * + * AutofillProvider handles one autofill session at time, each call of + * queryFormFieldAutofill cancels previous session and starts a new one, the + * calling of other methods shall associate with current session. + * + * This class doesn't have 1:1 mapping to native AutofillProviderAndroid; the + * normal ownership model is that this object is owned by the embedder-specific + * Java WebContents wrapper (e.g., AwContents.java in //android_webview), and + * AutofillProviderAndroid is owned by the embedder-specific C++ WebContents + * wrapper (e.g., native AwContents in //android_webview). + * + * VerifiesOnO since it causes class verification errors, see crbug.com/991851. + */ +@VerifiesOnO +@TargetApi(Build.VERSION_CODES.O) +@JNINamespace("autofill") +public class AutofillProvider { + private static final String TAG = "AutofillProvider"; + private static class FocusField { + public final short fieldIndex; + public final Rect absBound; + + public FocusField(short fieldIndex, Rect absBound) { + this.fieldIndex = fieldIndex; + this.absBound = absBound; + } + } + /** + * The class to wrap the request to framework. + * + * Though framework guarantees always giving us the autofill value of current + * session, we still want to verify this by using unique virtual id which is + * composed of sessionId and form field index, we don't use the request id + * which comes from renderer as session id because it is not unique. + */ + private static class AutofillRequest { + private static final int INIT_ID = 1; // ID can't be 0 in Android. + private static int sSessionId = INIT_ID; + public final int sessionId; + private FormData mFormData; + private FocusField mFocusField; + + public AutofillRequest(FormData formData, FocusField focus) { + sessionId = getNextClientId(); + mFormData = formData; + mFocusField = focus; + } + + public void fillViewStructure(ViewStructure structure) { + structure.setWebDomain(mFormData.mHost); + structure.setHtmlInfo(structure.newHtmlInfoBuilder("form") + .addAttribute("name", mFormData.mName) + .build()); + int index = structure.addChildCount(mFormData.mFields.size()); + short fieldIndex = 0; + for (FormFieldData field : mFormData.mFields) { + ViewStructure child = structure.newChild(index++); + int virtualId = toVirtualId(sessionId, fieldIndex++); + child.setAutofillId(structure.getAutofillId(), virtualId); + if (field.mAutocompleteAttr != null && !field.mAutocompleteAttr.isEmpty()) { + child.setAutofillHints(field.mAutocompleteAttr.split(" +")); + } + child.setHint(field.mPlaceholder); + + RectF bounds = field.getBoundsInContainerViewCoordinates(); + // Field has no scroll. + child.setDimens((int) bounds.left, (int) bounds.top, 0 /* scrollX*/, + 0 /* scrollY */, (int) bounds.width(), (int) bounds.height()); + + ViewStructure.HtmlInfo.Builder builder = + child.newHtmlInfoBuilder("input") + .addAttribute("name", field.mName) + .addAttribute("type", field.mType) + .addAttribute("label", field.mLabel) + .addAttribute("ua-autofill-hints", field.mHeuristicType) + .addAttribute("id", field.mId); + + switch (field.getControlType()) { + case FormFieldData.ControlType.LIST: + child.setAutofillType(View.AUTOFILL_TYPE_LIST); + child.setAutofillOptions(field.mOptionContents); + int i = findIndex(field.mOptionValues, field.getValue()); + if (i != -1) { + child.setAutofillValue(AutofillValue.forList(i)); + } + break; + case FormFieldData.ControlType.TOGGLE: + child.setAutofillType(View.AUTOFILL_TYPE_TOGGLE); + child.setAutofillValue(AutofillValue.forToggle(field.isChecked())); + break; + case FormFieldData.ControlType.TEXT: + case FormFieldData.ControlType.DATALIST: + child.setAutofillType(View.AUTOFILL_TYPE_TEXT); + child.setAutofillValue(AutofillValue.forText(field.getValue())); + if (field.mMaxLength != 0) { + builder.addAttribute("maxlength", String.valueOf(field.mMaxLength)); + } + if (field.getControlType() == FormFieldData.ControlType.DATALIST) { + child.setAutofillOptions(field.mDatalistValues); + } + break; + default: + break; + } + child.setHtmlInfo(builder.build()); + } + } + + public boolean autofill(final SparseArray values) { + for (int i = 0; i < values.size(); ++i) { + int id = values.keyAt(i); + if (toSessionId(id) != sessionId) return false; + AutofillValue value = values.get(id); + if (value == null) continue; + short index = toIndex(id); + if (index < 0 || index >= mFormData.mFields.size()) return false; + FormFieldData field = mFormData.mFields.get(index); + if (field == null) return false; + try { + switch (field.getControlType()) { + case FormFieldData.ControlType.LIST: + int j = value.getListValue(); + if (j < 0 && j >= field.mOptionValues.length) continue; + field.setAutofillValue(field.mOptionValues[j]); + break; + case FormFieldData.ControlType.TOGGLE: + field.setChecked(value.getToggleValue()); + break; + case FormFieldData.ControlType.TEXT: + case FormFieldData.ControlType.DATALIST: + field.setAutofillValue((String) value.getTextValue()); + break; + default: + break; + } + } catch (IllegalStateException e) { + // Refer to crbug.com/1080580 . + Log.e(TAG, "The given AutofillValue wasn't expected, abort autofill.", e); + return false; + } + } + return true; + } + + public void setFocusField(FocusField focusField) { + mFocusField = focusField; + } + + public FocusField getFocusField() { + return mFocusField; + } + + public int getFieldCount() { + return mFormData.mFields.size(); + } + + public AutofillValue getFieldNewValue(int index) { + FormFieldData field = mFormData.mFields.get(index); + if (field == null) return null; + switch (field.getControlType()) { + case FormFieldData.ControlType.LIST: + int i = findIndex(field.mOptionValues, field.getValue()); + if (i == -1) return null; + return AutofillValue.forList(i); + case FormFieldData.ControlType.TOGGLE: + return AutofillValue.forToggle(field.isChecked()); + case FormFieldData.ControlType.TEXT: + case FormFieldData.ControlType.DATALIST: + return AutofillValue.forText(field.getValue()); + default: + return null; + } + } + + public int getVirtualId(short index) { + return toVirtualId(sessionId, index); + } + + public FormFieldData getField(short index) { + return mFormData.mFields.get(index); + } + + private static int findIndex(String[] values, String value) { + if (values != null && value != null) { + for (int i = 0; i < values.length; i++) { + if (value.equals(values[i])) return i; + } + } + return -1; + } + + private static int getNextClientId() { + ThreadUtils.assertOnUiThread(); + if (sSessionId == 0xffff) sSessionId = INIT_ID; + return sSessionId++; + } + + private static int toSessionId(int virtualId) { + return (virtualId & 0xffff0000) >> 16; + } + + private static short toIndex(int virtualId) { + return (short) (virtualId & 0xffff); + } + + private static int toVirtualId(int clientId, short index) { + return (clientId << 16) | index; + } + } + + private final String mProviderName; + private AutofillManagerWrapper mAutofillManager; + private ViewGroup mContainerView; + private WebContents mWebContents; + + private AutofillRequest mRequest; + private long mNativeAutofillProvider; + private AutofillProviderUMA mAutofillUMA; + private AutofillManagerWrapper.InputUIObserver mInputUIObserver; + private long mAutofillTriggeredTimeMillis; + private Context mContext; + private AutofillPopup mDatalistPopup; + private WebContentsAccessibility mWebContentsAccessibility; + private View mAnchorView; + + public AutofillProvider(Context context, ViewGroup containerView, String providerName) { + this(containerView, new AutofillManagerWrapper(context), context, providerName); + } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + public AutofillProvider(ViewGroup containerView, AutofillManagerWrapper manager, + Context context, String providerName) { + mProviderName = providerName; + try (ScopedSysTraceEvent e = ScopedSysTraceEvent.scoped("AutofillProvider.constructor")) { + assert Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; + mAutofillManager = manager; + mContainerView = containerView; + mAutofillUMA = new AutofillProviderUMA(context); + mInputUIObserver = new AutofillManagerWrapper.InputUIObserver() { + @Override + public void onInputUIShown() { + // Not need to report suggestion window displayed if there is no live autofill + // session. + if (mRequest == null) return; + mAutofillUMA.onSuggestionDisplayed( + System.currentTimeMillis() - mAutofillTriggeredTimeMillis); + } + }; + mAutofillManager.addInputUIObserver(mInputUIObserver); + mContext = context; + } + } + + /** + * Invoked when container view is changed. + * + * @param containerView new container view. + */ + public void onContainerViewChanged(ViewGroup containerView) { + mContainerView = containerView; + } + + /** + * Invoked when autofill service needs the form structure. + * + * @param structure see View.onProvideAutofillVirtualStructure() + * @param flags see View.onProvideAutofillVirtualStructure() + */ + public void onProvideAutoFillVirtualStructure(ViewStructure structure, int flags) { + // This method could be called for the session started by the native + // control outside of the scope of autofill, e.g. the URL bar, in this case, we simply + // return. + if (mRequest == null) return; + + Bundle bundle = structure.getExtras(); + if (bundle != null) { + bundle.putCharSequence("VIRTUAL_STRUCTURE_PROVIDER_NAME", mProviderName); + bundle.putCharSequence( + "VIRTUAL_STRUCTURE_PROVIDER_VERSION", VersionConstants.PRODUCT_VERSION); + } + mRequest.fillViewStructure(structure); + if (AutofillManagerWrapper.isLoggable()) { + AutofillManagerWrapper.log( + "onProvideAutoFillVirtualStructure fields:" + structure.getChildCount()); + } + mAutofillUMA.onVirtualStructureProvided(); + } + + /** + * Invoked when autofill value is available, AutofillProvider shall fill the + * form with the provided values. + * + * @param values the array of autofill values, the key is virtual id of form + * field. + */ + public void autofill(final SparseArray values) { + if (mNativeAutofillProvider != 0 && mRequest != null && mRequest.autofill((values))) { + autofill(mNativeAutofillProvider, mRequest.mFormData); + if (AutofillManagerWrapper.isLoggable()) { + AutofillManagerWrapper.log("autofill values:" + values.size()); + } + mAutofillUMA.onAutofill(); + } + } + + /** + * @return whether query autofill suggestion. + */ + public boolean shouldQueryAutofillSuggestion() { + return mRequest != null && mRequest.getFocusField() != null + && !mAutofillManager.isAutofillInputUIShowing(); + } + + public void queryAutofillSuggestion() { + if (shouldQueryAutofillSuggestion()) { + FocusField focusField = mRequest.getFocusField(); + mAutofillManager.requestAutofill(mContainerView, + mRequest.getVirtualId(focusField.fieldIndex), focusField.absBound); + } + } + + /** + * Invoked when filling form is need. AutofillProvider shall ask autofill + * service for the values with which to fill the form. + * + * @param formData the form needs to fill. + * @param focus the index of focus field in formData + * @param x the boundary of focus field. + * @param y the boundary of focus field. + * @param width the boundary of focus field. + * @param height the boundary of focus field. + */ + @CalledByNative + public void startAutofillSession( + FormData formData, int focus, float x, float y, float width, float height) { + // Check focusField inside short value? + // Autofill Manager might have session that wasn't started by AutofillProvider, + // we just always cancel existing session here. + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { + mAutofillManager.cancel(); + } + + 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)); + int virtualId = mRequest.getVirtualId((short) focus); + notifyVirtualViewEntered(mContainerView, virtualId, absBound); + mAutofillUMA.onSessionStarted(mAutofillManager.isDisabled()); + mAutofillTriggeredTimeMillis = System.currentTimeMillis(); + + mAutofillManager.notifyNewSessionStarted(); + } + + /** + * Invoked when form field's value is changed. + * + * @param index index of field in current form. + * @param x the boundary of focus field. + * @param y the boundary of focus field. + * @param width the boundary of focus field. + * @param height the boundary of focus field. + * + */ + @CalledByNative + public void onFormFieldDidChange(int index, float x, float y, float width, float height) { + // Check index inside short value? + if (mRequest == null) return; + + short sIndex = (short) index; + FocusField focusField = mRequest.getFocusField(); + if (focusField == null || sIndex != focusField.fieldIndex) { + onFocusChangedImpl(true, index, x, y, width, height, true /*causedByValueChange*/); + } else { + // Currently there is no api to notify both value and position + // change, before the API is available, we still need to call + // notifyVirtualViewEntered() to tell current coordinates because + // the position could be changed. + int virtualId = mRequest.getVirtualId(sIndex); + Rect absBound = transformToWindowBounds(new RectF(x, y, x + width, y + height)); + if (!focusField.absBound.equals(absBound)) { + notifyVirtualViewExited(mContainerView, virtualId); + notifyVirtualViewEntered(mContainerView, virtualId, absBound); + // Update focus field position. + mRequest.setFocusField(new FocusField(focusField.fieldIndex, absBound)); + } + } + notifyVirtualValueChanged(index, /* forceNotify = */ false); + mAutofillUMA.onUserChangeFieldValue(mRequest.getField(sIndex).hasPreviouslyAutofilled()); + } + + /** + * Invoked when text field is scrolled. + * + * @param index index of field in current form. + * @param x the boundary of focus field. + * @param y the boundary of focus field. + * @param width the boundary of focus field. + * @param height the boundary of focus field. + * + */ + @CalledByNative + public void onTextFieldDidScroll(int index, float x, float y, float width, float height) { + // crbug.com/730764 - from P and above, Android framework listens to the onScrollChanged() + // and repositions the autofill UI automatically. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) return; + if (mRequest == null) return; + + short sIndex = (short) index; + FocusField focusField = mRequest.getFocusField(); + if (focusField == null || sIndex != focusField.fieldIndex) return; + + int virtualId = mRequest.getVirtualId(sIndex); + Rect absBound = transformToWindowBounds(new RectF(x, y, x + width, y + height)); + // Notify the new position to the Android framework. Note that we do not call + // notifyVirtualViewExited() here intentionally to avoid flickering. + notifyVirtualViewEntered(mContainerView, virtualId, absBound); + + // Update focus field position. + mRequest.setFocusField(new FocusField(focusField.fieldIndex, absBound)); + } + + private boolean isDatalistField(int childId) { + FormFieldData field = mRequest.getField((short) childId); + return field.mControlType == FormFieldData.ControlType.DATALIST; + } + + private void notifyVirtualValueChanged(int index, boolean forceNotify) { + // The ValueChanged, ViewEntered and ViewExited aren't notified to the autofill service for + // the focused datalist to avoid the potential UI conflict. + // The datalist support was added later and the option list is displayed by WebView, the + // autofill service might also show its suggestions when the datalist (associated the input + // field) is focused, the two UI overlap, the solution is to completely hide the fact that + // the datalist is being focused to the autofill service to prevent it from displaying the + // suggestion. + // The ValueChange will still be sent to autofill service when the form + // submitted or autofilled. + if (!forceNotify && isDatalistField(index)) return; + AutofillValue autofillValue = mRequest.getFieldNewValue(index); + if (autofillValue == null) return; + mAutofillManager.notifyVirtualValueChanged( + mContainerView, mRequest.getVirtualId((short) index), autofillValue); + } + + private void notifyVirtualViewEntered(View parent, int childId, Rect absBounds) { + // Refer to notifyVirtualValueChanged() for the reason of the datalist's special handling. + if (isDatalistField(childId)) return; + mAutofillManager.notifyVirtualViewEntered(parent, childId, absBounds); + } + + private void notifyVirtualViewExited(View parent, int childId) { + // Refer to notifyVirtualValueChanged() for the reason of the datalist's special handling. + if (isDatalistField(childId)) return; + mAutofillManager.notifyVirtualViewExited(parent, childId); + } + + /** + * Invoked when current form will be submitted. + * @param submissionSource the submission source, could be any member defined in + * SubmissionSource.java + */ + @CalledByNative + public void onFormSubmitted(int submissionSource) { + // The changes could be missing, like those made by Javascript, we'd better to notify + // AutofillManager current values. also see crbug.com/353001 and crbug.com/732856. + forceNotifyFormValues(); + mAutofillManager.commit(submissionSource); + mRequest = null; + mAutofillUMA.onFormSubmitted(submissionSource); + } + + /** + * Invoked when focus field changed. + * + * @param focusOnForm whether focus is still on form. + * @param focusItem the index of field has focus + * @param x the boundary of focus field. + * @param y the boundary of focus field. + * @param width the boundary of focus field. + * @param height the boundary of focus field. + */ + @CalledByNative + public void onFocusChanged( + boolean focusOnForm, int focusField, float x, float y, float width, float height) { + onFocusChangedImpl( + focusOnForm, focusField, x, y, width, height, false /*causedByValueChange*/); + } + + @CalledByNative + public void hidePopup() { + if (mDatalistPopup != null) { + mDatalistPopup.dismiss(); + mDatalistPopup = null; + } + if (mWebContentsAccessibility != null) { + mWebContentsAccessibility.onAutofillPopupDismissed(); + } + } + + private void notifyViewExitBeforeDestroyRequest() { + if (mRequest == null) return; + FocusField focusField = mRequest.getFocusField(); + if (focusField == null) return; + notifyVirtualViewExited(mContainerView, mRequest.getVirtualId(focusField.fieldIndex)); + mRequest.setFocusField(null); + } + + private void onFocusChangedImpl(boolean focusOnForm, int focusField, float x, float y, + float width, float height, boolean causedByValueChange) { + // Check focusField inside short value? + // FocusNoLongerOnForm is called after form submitted. + if (mRequest == null) return; + FocusField prev = mRequest.getFocusField(); + if (focusOnForm) { + Rect absBound = transformToWindowBounds(new RectF(x, y, x + width, y + height)); + if (prev != null && prev.fieldIndex == focusField && absBound.equals(prev.absBound)) { + return; + } + + // Notify focus changed. + if (prev != null) { + notifyVirtualViewExited(mContainerView, mRequest.getVirtualId(prev.fieldIndex)); + } + + notifyVirtualViewEntered( + mContainerView, mRequest.getVirtualId((short) focusField), absBound); + + if (!causedByValueChange) { + // The focus field value might not sync with platform's + // AutofillManager, just notify it value changed. + notifyVirtualValueChanged(focusField, /* forceNotify = */ false); + mAutofillTriggeredTimeMillis = System.currentTimeMillis(); + } + mRequest.setFocusField(new FocusField((short) focusField, absBound)); + } else { + if (prev == null) return; + // Notify focus changed. + notifyVirtualViewExited(mContainerView, mRequest.getVirtualId(prev.fieldIndex)); + mRequest.setFocusField(null); + } + } + + @CalledByNative + protected void showDatalistPopup( + String[] datalistValues, String[] datalistLabels, boolean isRtl) { + if (mRequest == null) return; + FocusField focusField = mRequest.getFocusField(); + if (focusField != null) { + showDatalistPopup(datalistValues, datalistLabels, + mRequest.getField(focusField.fieldIndex).getBounds(), isRtl); + } + } + + /** + * Display the simplest popup for the datalist. This is same as WebView's datalist popup in + * Android pre-o. No suggestion from the autofill service will be presented, No advance + * features of AutofillPopup are used. + */ + private void showDatalistPopup( + String[] datalistValues, String[] datalistLabels, RectF bounds, boolean isRtl) { + final AutofillSuggestion[] suggestions = new AutofillSuggestion[datalistValues.length]; + for (int i = 0; i < suggestions.length; i++) { + suggestions[i] = new AutofillSuggestion(datalistValues[i], datalistLabels[i], + DropdownItem.NO_ICON, false /* isIconAtLeft */, i, false /* isDeletable */, + false /* isMultilineLabel */, false /* isBoldLabel */); + } + if (mWebContentsAccessibility == null) { + mWebContentsAccessibility = WebContentsAccessibility.fromWebContents(mWebContents); + } + if (mDatalistPopup == null) { + if (ContextUtils.activityFromContext(mContext) == null) return; + ViewAndroidDelegate delegate = mWebContents.getViewAndroidDelegate(); + if (mAnchorView == null) mAnchorView = delegate.acquireView(); + setAnchorViewRect(bounds); + try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) { + mDatalistPopup = new AutofillPopup(mContext, mAnchorView, new AutofillDelegate() { + @Override + public void dismissed() { + onDatalistPopupDismissed(); + } + + @Override + public void suggestionSelected(int listIndex) { + onSuggestionSelected(suggestions[listIndex].getLabel()); + } + + @Override + public void deleteSuggestion(int listIndex) {} + + @Override + public void accessibilityFocusCleared() { + mWebContentsAccessibility.onAutofillPopupAccessibilityFocusCleared(); + } + }); + } catch (RuntimeException e) { + // Deliberately swallowing exception because bad framework implementation can + // throw exceptions in ListPopupWindow constructor. + onDatalistPopupDismissed(); + return; + } + } + mDatalistPopup.filterAndShow(suggestions, isRtl, false); + if (mWebContentsAccessibility != null) { + mWebContentsAccessibility.onAutofillPopupDisplayed(mDatalistPopup.getListView()); + } + } + + private void onDatalistPopupDismissed() { + ViewAndroidDelegate delegate = mWebContents.getViewAndroidDelegate(); + delegate.removeView(mAnchorView); + mAnchorView = null; + } + + private void onSuggestionSelected(String value) { + acceptDataListSuggestion(mNativeAutofillProvider, value); + hidePopup(); + } + + private void setAnchorViewRect(RectF rect) { + setAnchorViewRect(mNativeAutofillProvider, mAnchorView, rect); + } + + /** + * Invoked when current query need to be reset. + */ + @CalledByNative + protected void reset() { + // We don't need to reset anything here, it should be safe to cancel + // current autofill session when new one starts in + // startAutofillSession(). + } + + @CalledByNative + protected void setNativeAutofillProvider(long nativeAutofillProvider) { + if (nativeAutofillProvider == mNativeAutofillProvider) return; + // Setting the mNativeAutofillProvider to 0 may occur as a + // result of WebView.destroy, or because a WebView has been + // gc'ed. In the former case we can go ahead and clean up the + // frameworks autofill manager, but in the latter case the + // binder connection has already been dropped in a framework + // finalizer, and so the methods we call will throw. It's not + // possible to know which case we're in, so just catch the exception + // in AutofillManagerWrapper.destroy(). + if (mNativeAutofillProvider != 0) mRequest = null; + mNativeAutofillProvider = nativeAutofillProvider; + if (nativeAutofillProvider == 0) mAutofillManager.destroy(); + } + + public void setWebContents(WebContents webContents) { + if (webContents == mWebContents) return; + if (mWebContents != null) mRequest = null; + mWebContents = webContents; + } + + @CalledByNative + protected void onDidFillAutofillFormData() { + // The changes were caused by the autofill service autofill form, + // notified it about the result. + forceNotifyFormValues(); + } + + private void forceNotifyFormValues() { + if (mRequest == null) return; + for (int i = 0; i < mRequest.getFieldCount(); ++i) { + notifyVirtualValueChanged(i, /* forceNotify = */ true); + } + } + + @VisibleForTesting + public AutofillPopup getDatalistPopupForTesting() { + return mDatalistPopup; + } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + public Rect transformToWindowBounds(RectF rect) { + // Convert bounds to device pixel. + WindowAndroid windowAndroid = mWebContents.getTopLevelNativeWindow(); + DisplayAndroid displayAndroid = windowAndroid.getDisplay(); + float dipScale = displayAndroid.getDipScale(); + RectF bounds = new RectF(rect); + Matrix matrix = new Matrix(); + matrix.setScale(dipScale, dipScale); + int[] location = new int[2]; + mContainerView.getLocationOnScreen(location); + matrix.postTranslate(location[0], location[1]); + matrix.mapRect(bounds); + return new Rect( + (int) bounds.left, (int) bounds.top, (int) bounds.right, (int) bounds.bottom); + } + + /** + * Transform FormFieldData's bounds to ContainView's coordinates and update the bounds with the + * transformed one. + * + * @param formData the form need to be transformed. + */ + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + public void transformFormFieldToContainViewCoordinates(FormData formData) { + WindowAndroid windowAndroid = mWebContents.getTopLevelNativeWindow(); + DisplayAndroid displayAndroid = windowAndroid.getDisplay(); + float dipScale = displayAndroid.getDipScale(); + Matrix matrix = new Matrix(); + matrix.setScale(dipScale, dipScale); + matrix.postTranslate(mContainerView.getScrollX(), mContainerView.getScrollY()); + + for (FormFieldData field : formData.mFields) { + RectF bounds = new RectF(); + matrix.mapRect(bounds, field.getBounds()); + field.setBoundsInContainerViewCoordinates(bounds); + } + } + + /** + * Send form to renderer for filling. + * + * @param nativeAutofillProvider the native autofill provider. + * @param formData the form to fill. + */ + private void autofill(long nativeAutofillProvider, FormData formData) { + AutofillProviderJni.get().onAutofillAvailable( + nativeAutofillProvider, AutofillProvider.this, formData); + } + + private void acceptDataListSuggestion(long nativeAutofillProvider, String value) { + AutofillProviderJni.get().onAcceptDataListSuggestion( + nativeAutofillProvider, AutofillProvider.this, value); + } + + private void setAnchorViewRect(long nativeAutofillProvider, View anchorView, RectF rect) { + AutofillProviderJni.get().setAnchorViewRect(nativeAutofillProvider, AutofillProvider.this, + anchorView, rect.left, rect.top, rect.width(), rect.height()); + } + + @NativeMethods + interface Natives { + void onAutofillAvailable( + long nativeAutofillProviderAndroid, AutofillProvider caller, FormData formData); + + void onAcceptDataListSuggestion( + long nativeAutofillProviderAndroid, AutofillProvider caller, String value); + + void setAnchorViewRect(long nativeAutofillProviderAndroid, AutofillProvider caller, + View anchorView, float x, float y, float width, float height); + } +} 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 new file mode 100644 index 00000000000..394cd849c95 --- /dev/null +++ b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillProviderUMA.java @@ -0,0 +1,255 @@ +// Copyright 2018 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.content.Context; + +import org.chromium.autofill.mojom.SubmissionSource; +import org.chromium.base.ContextUtils; +import org.chromium.base.metrics.RecordHistogram; + +import java.util.concurrent.TimeUnit; + +/** + * The class for AutofillProvider-related UMA. Note that most of the concrete histogram + * names include "WebView"; when this class was originally developed it was WebView-specific, + * and when generalizing it we did not change these names to maintain continuity when + * analyzing the histograms. + */ +public class AutofillProviderUMA { + // Records whether the Autofill service is enabled or not. + public static final String UMA_AUTOFILL_ENABLED = "Autofill.WebView.Enabled"; + + // Records whether the Autofill provider is created by activity context or not. + public static final String UMA_AUTOFILL_CREATED_BY_ACTIVITY_CONTEXT = + "Autofill.WebView.CreatedByActivityContext"; + + // 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. + public static final int SESSION_UNKNOWN = 0; + public static final int NO_CALLBACK_FORM_FRAMEWORK = 1; + public static final int NO_SUGGESTION_USER_CHANGE_FORM_FORM_SUBMITTED = 2; + public static final int NO_SUGGESTION_USER_CHANGE_FORM_NO_FORM_SUBMITTED = 3; + public static final int NO_SUGGESTION_USER_NOT_CHANGE_FORM_FORM_SUBMITTED = 4; + public static final int NO_SUGGESTION_USER_NOT_CHANGE_FORM_NO_FORM_SUBMITTED = 5; + public static final int USER_SELECT_SUGGESTION_USER_CHANGE_FORM_FORM_SUBMITTED = 6; + public static final int USER_SELECT_SUGGESTION_USER_CHANGE_FORM_NO_FORM_SUBMITTED = 7; + public static final int USER_SELECT_SUGGESTION_USER_NOT_CHANGE_FORM_FORM_SUBMITTED = 8; + public static final int USER_SELECT_SUGGESTION_USER_NOT_CHANGE_FORM_NO_FORM_SUBMITTED = 9; + public static final int USER_NOT_SELECT_SUGGESTION_USER_CHANGE_FORM_FORM_SUBMITTED = 10; + public static final int USER_NOT_SELECT_SUGGESTION_USER_CHANGE_FORM_NO_FORM_SUBMITTED = 11; + public static final int USER_NOT_SELECT_SUGGESTION_USER_NOT_CHANGE_FORM_FORM_SUBMITTED = 12; + 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; + + // 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 = + "Autofill.WebView.UserChangedAutofilledField"; + + public static final String UMA_AUTOFILL_SUBMISSION_SOURCE = "Autofill.WebView.SubmissionSource"; + // The possible value of UMA_AUTOFILL_SUBMISSION_SOURCE. + public static final int SAME_DOCUMENT_NAVIGATION = 0; + public static final int XHR_SUCCEEDED = 1; + public static final int FRAME_DETACHED = 2; + public static final int DOM_MUTATION_AFTER_XHR = 3; + public static final int PROBABLY_FORM_SUBMITTED = 4; + public static final int FORM_SUBMISSION = 5; + public static final int SUBMISSION_SOURCE_HISTOGRAM_COUNT = 6; + + // The million seconds from user touched the field to the autofill session starting. + public static final String UMA_AUTOFILL_TRIGGERING_TIME = "Autofill.WebView.TriggeringTime"; + + // The million seconds from the autofill session starting to the suggestion being displayed. + public static final String UMA_AUTOFILL_SUGGESTION_TIME = "Autofill.WebView.SuggestionTime"; + + // The expected time range of time is from 10ms to 2 seconds, and 50 buckets is sufficient. + private static final long MIN_TIME_MILLIS = 10; + private static final long MAX_TIME_MILLIS = TimeUnit.SECONDS.toMillis(2); + private static final int NUM_OF_BUCKETS = 50; + + private static void recordTimesHistogram(String name, long durationMillis) { + RecordHistogram.recordCustomTimesHistogram( + name, durationMillis, MIN_TIME_MILLIS, MAX_TIME_MILLIS, NUM_OF_BUCKETS); + } + + private static class SessionRecorder { + public static final int EVENT_VIRTUAL_STRUCTURE_PROVIDED = 0x1 << 0; + public static final int EVENT_SUGGESTION_DISPLAYED = 0x1 << 1; + public static final int EVENT_FORM_AUTOFILLED = 0x1 << 2; + public static final int EVENT_USER_CHANGED_FIELD_VALUE = 0x1 << 3; + public static final int EVENT_FORM_SUBMITTED = 0x1 << 4; + public static final int EVENT_USER_CHANGED_AUTOFILLED_FIELD = 0x1 << 5; + + private Long mSuggestionTimeMillis; + + public void record(int event) { + // Not record any event until we get EVENT_VIRTUAL_STRUCTURE_PROVIDED which makes the + // following events meaningful. + if (event != EVENT_VIRTUAL_STRUCTURE_PROVIDED && mState == 0) return; + if (EVENT_USER_CHANGED_FIELD_VALUE == event && mUserChangedAutofilledField == null) { + mUserChangedAutofilledField = Boolean.valueOf(false); + } else if (EVENT_USER_CHANGED_AUTOFILLED_FIELD == event) { + if (mUserChangedAutofilledField == null) { + mUserChangedAutofilledField = Boolean.valueOf(true); + } + mUserChangedAutofilledField = true; + event = EVENT_USER_CHANGED_FIELD_VALUE; + } + mState |= event; + } + + public void setSuggestionTimeMillis(long suggestionTimeMillis) { + // Only record first suggestion. + if (mSuggestionTimeMillis == null) { + mSuggestionTimeMillis = Long.valueOf(suggestionTimeMillis); + } + } + + public void recordHistogram() { + RecordHistogram.recordEnumeratedHistogram(UMA_AUTOFILL_AUTOFILL_SESSION, + toUMAAutofillSessionValue(), AUTOFILL_SESSION_HISTOGRAM_COUNT); + // Only record if user ever changed form. + if (mUserChangedAutofilledField != null) { + RecordHistogram.recordBooleanHistogram( + UMA_AUTOFILL_USER_CHANGED_AUTOFILLED_FIELD, mUserChangedAutofilledField); + } + if (mSuggestionTimeMillis != null) { + recordTimesHistogram(UMA_AUTOFILL_SUGGESTION_TIME, mSuggestionTimeMillis); + } + } + + private int toUMAAutofillSessionValue() { + if (mState == 0) { + return NO_CALLBACK_FORM_FRAMEWORK; + } else if (mState == EVENT_VIRTUAL_STRUCTURE_PROVIDED) { + return NO_SUGGESTION_USER_NOT_CHANGE_FORM_NO_FORM_SUBMITTED; + } else if (mState + == (EVENT_VIRTUAL_STRUCTURE_PROVIDED | EVENT_USER_CHANGED_FIELD_VALUE)) { + return NO_SUGGESTION_USER_CHANGE_FORM_NO_FORM_SUBMITTED; + } else if (mState == (EVENT_VIRTUAL_STRUCTURE_PROVIDED | EVENT_FORM_SUBMITTED)) { + return NO_SUGGESTION_USER_NOT_CHANGE_FORM_FORM_SUBMITTED; + } else if (mState + == (EVENT_VIRTUAL_STRUCTURE_PROVIDED | EVENT_USER_CHANGED_FIELD_VALUE + | EVENT_FORM_SUBMITTED)) { + return NO_SUGGESTION_USER_CHANGE_FORM_FORM_SUBMITTED; + } else if (mState + == (EVENT_VIRTUAL_STRUCTURE_PROVIDED | EVENT_SUGGESTION_DISPLAYED + | EVENT_FORM_AUTOFILLED)) { + return USER_SELECT_SUGGESTION_USER_NOT_CHANGE_FORM_NO_FORM_SUBMITTED; + } else if (mState + == (EVENT_VIRTUAL_STRUCTURE_PROVIDED | EVENT_SUGGESTION_DISPLAYED + | EVENT_FORM_AUTOFILLED | EVENT_FORM_SUBMITTED)) { + return USER_SELECT_SUGGESTION_USER_NOT_CHANGE_FORM_FORM_SUBMITTED; + } else if (mState + == (EVENT_VIRTUAL_STRUCTURE_PROVIDED | EVENT_SUGGESTION_DISPLAYED + | EVENT_FORM_AUTOFILLED | EVENT_USER_CHANGED_FIELD_VALUE + | EVENT_FORM_SUBMITTED)) { + return USER_SELECT_SUGGESTION_USER_CHANGE_FORM_FORM_SUBMITTED; + } else if (mState + == (EVENT_VIRTUAL_STRUCTURE_PROVIDED | EVENT_SUGGESTION_DISPLAYED + | EVENT_FORM_AUTOFILLED | EVENT_USER_CHANGED_FIELD_VALUE)) { + return USER_SELECT_SUGGESTION_USER_CHANGE_FORM_NO_FORM_SUBMITTED; + } else if (mState == (EVENT_VIRTUAL_STRUCTURE_PROVIDED | EVENT_SUGGESTION_DISPLAYED)) { + return USER_NOT_SELECT_SUGGESTION_USER_NOT_CHANGE_FORM_NO_FORM_SUBMITTED; + } else if (mState + == (EVENT_VIRTUAL_STRUCTURE_PROVIDED | EVENT_SUGGESTION_DISPLAYED + | EVENT_FORM_SUBMITTED)) { + return USER_NOT_SELECT_SUGGESTION_USER_NOT_CHANGE_FORM_FORM_SUBMITTED; + } else if (mState + == (EVENT_VIRTUAL_STRUCTURE_PROVIDED | EVENT_SUGGESTION_DISPLAYED + | EVENT_USER_CHANGED_FIELD_VALUE | EVENT_FORM_SUBMITTED)) { + return USER_NOT_SELECT_SUGGESTION_USER_CHANGE_FORM_FORM_SUBMITTED; + } else if (mState + == (EVENT_VIRTUAL_STRUCTURE_PROVIDED | EVENT_SUGGESTION_DISPLAYED + | EVENT_USER_CHANGED_FIELD_VALUE)) { + return USER_NOT_SELECT_SUGGESTION_USER_CHANGE_FORM_NO_FORM_SUBMITTED; + } else { + return SESSION_UNKNOWN; + } + } + + private int mState; + private Boolean mUserChangedAutofilledField; + } + + private SessionRecorder mRecorder; + private Boolean mAutofillDisabled; + + public AutofillProviderUMA(Context context) { + RecordHistogram.recordBooleanHistogram(UMA_AUTOFILL_CREATED_BY_ACTIVITY_CONTEXT, + ContextUtils.activityFromContext(context) != null); + } + + public void onFormSubmitted(int submissionSource) { + if (mRecorder != null) mRecorder.record(SessionRecorder.EVENT_FORM_SUBMITTED); + recordSession(); + // We record this no matter autofill service is disabled or not. + RecordHistogram.recordEnumeratedHistogram(UMA_AUTOFILL_SUBMISSION_SOURCE, + toUMASubmissionSource(submissionSource), SUBMISSION_SOURCE_HISTOGRAM_COUNT); + } + + public void onSessionStarted(boolean autofillDisabled) { + // Record autofill status once per instance and only if user triggers the autofill. + if (mAutofillDisabled == null || mAutofillDisabled.booleanValue() != autofillDisabled) { + RecordHistogram.recordBooleanHistogram(UMA_AUTOFILL_ENABLED, !autofillDisabled); + mAutofillDisabled = Boolean.valueOf(autofillDisabled); + } + + if (mRecorder != null) recordSession(); + mRecorder = new SessionRecorder(); + } + + public void onVirtualStructureProvided() { + if (mRecorder != null) mRecorder.record(SessionRecorder.EVENT_VIRTUAL_STRUCTURE_PROVIDED); + } + + public void onSuggestionDisplayed(long suggestionTimeMillis) { + if (mRecorder != null) { + mRecorder.record(SessionRecorder.EVENT_SUGGESTION_DISPLAYED); + mRecorder.setSuggestionTimeMillis(suggestionTimeMillis); + } + } + + public void onAutofill() { + if (mRecorder != null) mRecorder.record(SessionRecorder.EVENT_FORM_AUTOFILLED); + } + + public void onUserChangeFieldValue(boolean isPreviouslyAutofilled) { + if (mRecorder == null) return; + if (isPreviouslyAutofilled) { + mRecorder.record(SessionRecorder.EVENT_USER_CHANGED_AUTOFILLED_FIELD); + } else { + mRecorder.record(SessionRecorder.EVENT_USER_CHANGED_FIELD_VALUE); + } + } + + private void recordSession() { + if (mAutofillDisabled != null && !mAutofillDisabled.booleanValue() && mRecorder != null) { + mRecorder.recordHistogram(); + } + mRecorder = null; + } + + private int toUMASubmissionSource(int source) { + switch (source) { + case SubmissionSource.SAME_DOCUMENT_NAVIGATION: + return SAME_DOCUMENT_NAVIGATION; + case SubmissionSource.XHR_SUCCEEDED: + return XHR_SUCCEEDED; + case SubmissionSource.FRAME_DETACHED: + return FRAME_DETACHED; + case SubmissionSource.DOM_MUTATION_AFTER_XHR: + return DOM_MUTATION_AFTER_XHR; + case SubmissionSource.PROBABLY_FORM_SUBMITTED: + return PROBABLY_FORM_SUBMITTED; + case SubmissionSource.FORM_SUBMISSION: + return FORM_SUBMISSION; + default: + return SUBMISSION_SOURCE_HISTOGRAM_COUNT; + } + } +} diff --git a/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/FormData.java b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/FormData.java new file mode 100644 index 00000000000..8069aa887b6 --- /dev/null +++ b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/FormData.java @@ -0,0 +1,56 @@ +// Copyright 2017 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 androidx.annotation.VisibleForTesting; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeMethods; + +import java.util.ArrayList; + +/** + * The wrap class of native autofill::FormDataAndroid. + */ +@JNINamespace("autofill") +public class FormData { + public final String mName; + public final String mHost; + public final ArrayList mFields; + + @CalledByNative + private static FormData createFormData( + long nativeObj, String name, String origin, int fieldCount) { + return new FormData(nativeObj, name, origin, fieldCount); + } + + private static ArrayList popupFormFields(long nativeObj, int fieldCount) { + FormFieldData formFieldData = FormDataJni.get().getNextFormFieldData(nativeObj); + ArrayList fields = new ArrayList(fieldCount); + while (formFieldData != null) { + fields.add(formFieldData); + formFieldData = FormDataJni.get().getNextFormFieldData(nativeObj); + } + assert fields.size() == fieldCount; + return fields; + } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + public FormData(String name, String host, ArrayList fields) { + mName = name; + mHost = host; + mFields = fields; + } + + private FormData(long nativeObj, String name, String host, int fieldCount) { + this(name, host, popupFormFields(nativeObj, fieldCount)); + } + + @NativeMethods + interface Natives { + FormFieldData getNextFormFieldData(long nativeFormDataAndroid); + } +} 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 new file mode 100644 index 00000000000..eb0a94a6bce --- /dev/null +++ b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/FormFieldData.java @@ -0,0 +1,162 @@ +// Copyright 2017 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.graphics.RectF; + +import androidx.annotation.IntDef; +import androidx.annotation.VisibleForTesting; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * The wrap class of native autofill::FormFieldDataAndroid. + */ +@JNINamespace("autofill") +public class FormFieldData { + /** + * Define the control types supported by android.view.autofill.AutofillValue. + * + * Android doesn't have DATALIST control, it is sent to the Autofill service as + * View.AUTOFILL_TYPE_TEXT with AutofillOptions. + */ + @IntDef({ControlType.TEXT, ControlType.TOGGLE, ControlType.LIST, ControlType.DATALIST}) + @Retention(RetentionPolicy.SOURCE) + public @interface ControlType { + int TEXT = 0; + int TOGGLE = 1; + int LIST = 2; + int DATALIST = 3; + } + + public final String mLabel; + public final String mName; + public final String mAutocompleteAttr; + public final boolean mShouldAutocomplete; + public final String mPlaceholder; + public final String mType; + public final String mId; + public final String[] mOptionValues; + public final String[] mOptionContents; + public final @ControlType int mControlType; + public final int mMaxLength; + public final String mHeuristicType; + public final String[] mDatalistValues; + public final String[] mDatalistLabels; + + // The bounds in the viewport's coordinates + private final RectF mBounds; + // The bounds in the container view's coordinates. + private RectF mBoundsInContainerViewCoordinates; + + private boolean mIsChecked; + private String mValue; + // Indicates whether mValue is autofilled. + private boolean mAutofilled; + // Indicates whether this fields was autofilled, but changed by user. + private boolean mPreviouslyAutofilled; + + 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, + String[] datalistValues, String[] datalistLabels) { + mName = name; + mLabel = label; + mValue = value; + mAutocompleteAttr = autocompleteAttr; + mShouldAutocomplete = shouldAutocomplete; + mPlaceholder = placeholder; + mType = type; + mId = id; + mOptionValues = optionValues; + mOptionContents = optionContents; + mIsChecked = isChecked; + mDatalistLabels = datalistLabels; + mDatalistValues = datalistValues; + if (mOptionValues != null && mOptionValues.length != 0) { + mControlType = ControlType.LIST; + } else if (mDatalistValues != null && mDatalistValues.length != 0) { + mControlType = ControlType.DATALIST; + } else if (isCheckField) { + mControlType = ControlType.TOGGLE; + } else { + mControlType = ControlType.TEXT; + } + mMaxLength = maxLength; + mHeuristicType = heuristicType; + mBounds = new RectF(left, top, right, bottom); + } + + public @ControlType int getControlType() { + return mControlType; + } + + public RectF getBounds() { + return mBounds; + } + + public void setBoundsInContainerViewCoordinates(RectF bounds) { + mBoundsInContainerViewCoordinates = bounds; + } + + public RectF getBoundsInContainerViewCoordinates() { + return mBoundsInContainerViewCoordinates; + } + + /** + * @return value of field. + */ + @CalledByNative + public String getValue() { + return mValue; + } + + public void setAutofillValue(String value) { + mValue = value; + updateAutofillState(true); + } + + public void setChecked(boolean checked) { + mIsChecked = checked; + updateAutofillState(true); + } + + @CalledByNative + private void updateValue(String value) { + mValue = value; + updateAutofillState(false); + } + + @CalledByNative + public boolean isChecked() { + return mIsChecked; + } + + public boolean hasPreviouslyAutofilled() { + return mPreviouslyAutofilled; + } + + private void updateAutofillState(boolean autofilled) { + if (mAutofilled && !autofilled) mPreviouslyAutofilled = true; + mAutofilled = autofilled; + } + + @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) { + return new FormFieldData(name, label, value, autocompleteAttr, shouldAutocomplete, + placeholder, type, id, optionValues, optionContents, isCheckField, isChecked, + maxLength, heuristicType, left, top, right, bottom, datalistValues, datalistLabels); + } +} diff --git a/chromium/components/autofill/android/provider/junit/BUILD.gn b/chromium/components/autofill/android/provider/junit/BUILD.gn new file mode 100644 index 00000000000..95cea50bf90 --- /dev/null +++ b/chromium/components/autofill/android/provider/junit/BUILD.gn @@ -0,0 +1,23 @@ +# 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") + +java_library("components_autofill_junit_tests") { + # Platform checks are broken for Robolectric. See https://crbug.com/1071638. + bypass_platform_checks = true + testonly = true + sources = [ "src/org/chromium/components/autofill/AutofillProviderTest.java" ] + deps = [ + "//base:base_java_test_support", + "//base:base_junit_test_support", + "//components/autofill/android/provider:java", + "//content/public/android:content_java", + "//third_party/android_deps:robolectric_all_java", + "//third_party/junit", + "//third_party/mockito:mockito_java", + "//ui/android:ui_java", + ] +} 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 new file mode 100644 index 00000000000..e4b12cec808 --- /dev/null +++ b/chromium/components/autofill/android/provider/junit/src/org/chromium/components/autofill/AutofillProviderTest.java @@ -0,0 +1,115 @@ +// 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 static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.graphics.Rect; +import android.graphics.RectF; +import android.view.ViewGroup; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.robolectric.annotation.Config; + +import org.chromium.base.test.BaseRobolectricTestRunner; +import org.chromium.content_public.browser.WebContents; +import org.chromium.ui.base.WindowAndroid; +import org.chromium.ui.display.DisplayAndroid; + +import java.util.ArrayList; + +/** + * The unit tests for AutofillProvider. + */ +@RunWith(BaseRobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class AutofillProviderTest { + private static final float EXPECTED_DIP_SCALE = 2; + private static final int SCROLL_X = 15; + private static final int SCROLL_Y = 155; + private static final int LOCATION_X = 25; + private static final int LOCATION_Y = 255; + + private Context mContext; + private WindowAndroid mWindowAndroid; + private WebContents mWebContents; + private ViewGroup mContainerView; + private AutofillProvider mAutofillProvider; + private DisplayAndroid mDisplayAndroid; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = Mockito.mock(Context.class); + mWindowAndroid = Mockito.mock(WindowAndroid.class); + mDisplayAndroid = Mockito.mock(DisplayAndroid.class); + mWebContents = Mockito.mock(WebContents.class); + mContainerView = Mockito.mock(ViewGroup.class); + mAutofillProvider = new AutofillProvider(mContext, mContainerView, "AutofillProviderTest"); + mAutofillProvider.setWebContents(mWebContents); + + when(mWebContents.getTopLevelNativeWindow()).thenReturn(mWindowAndroid); + when(mWindowAndroid.getDisplay()).thenReturn(mDisplayAndroid); + when(mDisplayAndroid.getDipScale()).thenReturn(EXPECTED_DIP_SCALE); + when(mContainerView.getScrollX()).thenReturn(SCROLL_X); + when(mContainerView.getScrollY()).thenReturn(SCROLL_Y); + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) { + Object[] args = invocation.getArguments(); + int[] location = (int[]) args[0]; + location[0] = LOCATION_X; + location[1] = LOCATION_Y; + return null; + } + }) + .when(mContainerView) + .getLocationOnScreen(ArgumentMatchers.any()); + } + + @Test + public void testTransformFormFieldToContainViewCoordinates() { + ArrayList fields = new ArrayList(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)); + 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)); + FormData formData = new FormData(null, null, fields); + mAutofillProvider.transformFormFieldToContainViewCoordinates(formData); + RectF result = formData.mFields.get(0).getBoundsInContainerViewCoordinates(); + assertEquals(10 * EXPECTED_DIP_SCALE + SCROLL_X, result.left, 0); + assertEquals(20 * EXPECTED_DIP_SCALE + SCROLL_Y, result.top, 0); + assertEquals(300 * EXPECTED_DIP_SCALE + SCROLL_X, result.right, 0); + assertEquals(60 * EXPECTED_DIP_SCALE + SCROLL_Y, result.bottom, 0); + + result = formData.mFields.get(1).getBoundsInContainerViewCoordinates(); + assertEquals(20 * EXPECTED_DIP_SCALE + SCROLL_X, result.left, 0); + assertEquals(100 * EXPECTED_DIP_SCALE + SCROLL_Y, result.top, 0); + assertEquals(400 * EXPECTED_DIP_SCALE + SCROLL_X, result.right, 0); + assertEquals(200 * EXPECTED_DIP_SCALE + SCROLL_Y, result.bottom, 0); + } + + @Test + public void testTransformToWindowBounds() { + RectF source = new RectF(10, 20, 300, 400); + Rect result = mAutofillProvider.transformToWindowBounds(source); + assertEquals(10 * EXPECTED_DIP_SCALE + LOCATION_X, result.left, 0); + assertEquals(20 * EXPECTED_DIP_SCALE + LOCATION_Y, result.top, 0); + assertEquals(300 * EXPECTED_DIP_SCALE + LOCATION_X, result.right, 0); + assertEquals(400 * EXPECTED_DIP_SCALE + LOCATION_Y, result.bottom, 0); + } +} -- cgit v1.2.1