summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorestade <estade@chromium.org>2015-04-28 11:07:42 -0700
committerCommit bot <commit-bot@chromium.org>2015-04-28 18:08:35 +0000
commitb5efbab6f71b0e2660e3d5231d99df698c0e5b38 (patch)
tree4a6ddaebaeec6b83fef16a36f5fa85fe7a81e038
parent51689d131ef4401d3349684ab466e666469f2c7e (diff)
downloadchromium_src-b5efbab6f71b0e2660e3d5231d99df698c0e5b38.zip
chromium_src-b5efbab6f71b0e2660e3d5231d99df698c0e5b38.tar.gz
chromium_src-b5efbab6f71b0e2660e3d5231d99df698c0e5b38.tar.bz2
Android - Introduce "keyboard accessory" for Autofill suggestions.
The new UI is exposed behind a flag, --enable-autofill-keyboard-accessory-view. The intent is that there should be no behavior change when the flag is not passed, but eventually the flag will become the default and AutofillPopup[Bridge] and related code will be removed. This maintains the same appearance for the list items, and matching the mocks (switching to white on blue, etc.) is a TODO. See go/wzhwq for goal state. There are also TODOs surrounding behavior: - we likely want to hide the keyboard accessory if the keyboard hide - add animations - somehow deal with very small amounts of available space (comes up in landscape mode) BUG=428087 TBR=isherman@chromium.org Review URL: https://codereview.chromium.org/1082183002 Cr-Commit-Position: refs/heads/master@{#327324}
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillKeyboardAccessoryBridge.java116
-rw-r--r--chrome/app/generated_resources.grd9
-rw-r--r--chrome/browser/about_flags.cc9
-rw-r--r--chrome/browser/android/chrome_jni_registrar.cc4
-rw-r--r--chrome/browser/chrome_content_browser_client.cc1
-rw-r--r--chrome/browser/ui/android/autofill/autofill_keyboard_accessory_view.cc105
-rw-r--r--chrome/browser/ui/android/autofill/autofill_keyboard_accessory_view.h60
-rw-r--r--chrome/browser/ui/android/autofill/autofill_popup_view_android.cc8
-rw-r--r--chrome/chrome_browser.gypi1
-rw-r--r--chrome/chrome_browser_ui.gypi2
-rw-r--r--components/autofill/content/renderer/autofill_agent.cc4
-rw-r--r--components/autofill/content/renderer/page_click_tracker.cc17
-rw-r--r--components/autofill/content/renderer/page_click_tracker.h1
-rw-r--r--components/autofill/core/browser/autofill_external_delegate.cc6
-rw-r--r--components/autofill/core/browser/autofill_external_delegate_unittest.cc54
-rw-r--r--components/autofill/core/common/autofill_switches.cc3
-rw-r--r--components/autofill/core/common/autofill_switches.h1
-rw-r--r--tools/metrics/histograms/histograms.xml1
-rw-r--r--ui/android/java/res/drawable/autofill_accessory_view_border.xml16
-rw-r--r--ui/android/java/src/org/chromium/ui/autofill/AutofillKeyboardAccessory.java117
-rw-r--r--ui/android/java/src/org/chromium/ui/base/WindowAndroid.java19
21 files changed, 529 insertions, 25 deletions
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillKeyboardAccessoryBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillKeyboardAccessoryBridge.java
new file mode 100644
index 0000000..d99de66
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillKeyboardAccessoryBridge.java
@@ -0,0 +1,116 @@
+// Copyright 2014 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.chrome.browser.autofill;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.chrome.browser.ResourceId;
+import org.chromium.ui.DropdownItem;
+import org.chromium.ui.autofill.AutofillKeyboardAccessory;
+import org.chromium.ui.autofill.AutofillKeyboardAccessory.AutofillKeyboardAccessoryDelegate;
+import org.chromium.ui.autofill.AutofillSuggestion;
+import org.chromium.ui.base.WindowAndroid;
+
+/**
+* JNI call glue for AutofillExternalDelagate C++ and Java objects.
+* This provides an alternative UI for Autofill suggestions, and replaces AutofillPopupBridge when
+* --enable-autofill-keyboard-accessory-view is passed on the command line.
+*/
+@JNINamespace("autofill")
+public class AutofillKeyboardAccessoryBridge implements AutofillKeyboardAccessoryDelegate {
+ private long mNativeAutofillKeyboardAccessory;
+ private AutofillKeyboardAccessory mAccessoryView;
+
+ private AutofillKeyboardAccessoryBridge() {
+ }
+
+ @CalledByNative
+ private static AutofillKeyboardAccessoryBridge create() {
+ return new AutofillKeyboardAccessoryBridge();
+ }
+
+ @Override
+ public void dismissed() {
+ if (mNativeAutofillKeyboardAccessory == 0) return;
+ nativeViewDismissed(mNativeAutofillKeyboardAccessory);
+ }
+
+ @Override
+ public void suggestionSelected(int listIndex) {
+ if (mNativeAutofillKeyboardAccessory == 0) return;
+ nativeSuggestionSelected(mNativeAutofillKeyboardAccessory, listIndex);
+ }
+
+ /**
+ * Initializes this object.
+ * This function should be called at most one time.
+ * @param nativeAutofillKeyboardAccessory Handle to the native counterpart.
+ * @param windowAndroid The window on which to show the suggestions.
+ */
+ @CalledByNative
+ private void init(long nativeAutofillKeyboardAccessory, WindowAndroid windowAndroid) {
+ if (windowAndroid == null || windowAndroid.getActivity().get() == null) {
+ nativeViewDismissed(nativeAutofillKeyboardAccessory);
+ dismissed();
+ return;
+ }
+
+ mNativeAutofillKeyboardAccessory = nativeAutofillKeyboardAccessory;
+ mAccessoryView = new AutofillKeyboardAccessory(windowAndroid, this);
+ }
+
+ /**
+ * Clears the reference to the native view.
+ */
+ @CalledByNative
+ private void resetNativeViewPointer() {
+ mNativeAutofillKeyboardAccessory = 0;
+ }
+
+ /**
+ * Hides the Autofill view.
+ */
+ @CalledByNative
+ private void dismiss() {
+ if (mAccessoryView != null) mAccessoryView.dismiss();
+ }
+
+ /**
+ * Shows an Autofill view with specified suggestions.
+ * @param suggestions Autofill suggestions to be displayed.
+ */
+ @CalledByNative
+ private void show(AutofillSuggestion[] suggestions, boolean isRtl) {
+ if (mAccessoryView != null) mAccessoryView.showWithSuggestions(suggestions, isRtl);
+ }
+
+ // Helper methods for AutofillSuggestion. These are copied from AutofillPopupBridge (which
+ // should
+ // eventually disappear).
+
+ @CalledByNative
+ private static AutofillSuggestion[] createAutofillSuggestionArray(int size) {
+ return new AutofillSuggestion[size];
+ }
+
+ /**
+ * @param array AutofillSuggestion array that should get a new suggestion added.
+ * @param index Index in the array where to place a new suggestion.
+ * @param label First line of the suggestion.
+ * @param sublabel Second line of the suggestion.
+ * @param iconId The resource ID for the icon associated with the suggestion, or 0 for no icon.
+ * @param suggestionId Identifier for the suggestion type.
+ */
+ @CalledByNative
+ private static void addToAutofillSuggestionArray(AutofillSuggestion[] array, int index,
+ String label, String sublabel, int iconId, int suggestionId) {
+ int drawableId = iconId == 0 ? DropdownItem.NO_ICON : ResourceId.mapToDrawableId(iconId);
+ array[index] = new AutofillSuggestion(label, sublabel, drawableId, suggestionId);
+ }
+
+ private native void nativeViewDismissed(long nativeAutofillKeyboardAccessoryView);
+ private native void nativeSuggestionSelected(
+ long nativeAutofillKeyboardAccessoryView, int listIndex);
+}
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 8922cec..2298421 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -15132,6 +15132,15 @@ After you create a new supervised user, you can manage their settings at any tim
Import cards from my Wallet.
</message>
+ <if expr="is_android">
+ <message name="IDS_FLAGS_AUTOFILL_ACCESSORY_VIEW_NAME" desc="Title for the flag to show Autofill suggestions at top of keyboard">
+ Autofill suggestions as keyboard accessory view
+ </message>
+ <message name="IDS_FLAGS_AUTOFILL_ACCESSORY_VIEW_DESCRIPTION" desc="Description for the flag to show Autofill suggestions at top of keyboard">
+ Shows Autofill suggestions on top of the keyboard rather than in a dropdown.
+ </message>
+ </if>
+
<!-- Reader mode experiment flags -->
<if expr="is_android">
<message name="IDS_FLAGS_READER_MODE_HEURISTICS_NAME" desc="A name of an about:flags experiment for controlling when to show the reader mode button">
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 9aae179..9596ea5 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -2396,6 +2396,15 @@ const Experiment kExperiments[] = {
kOsAndroid | kOsMac | kOsWin | kOsLinux | kOsCrOS,
MULTI_VALUE_TYPE(kSupervisedUserSafeSitesChoices)
},
+#if defined(OS_ANDROID)
+ {
+ "enable-autofill-keyboard-accessory-view",
+ IDS_FLAGS_AUTOFILL_ACCESSORY_VIEW_NAME,
+ IDS_FLAGS_AUTOFILL_ACCESSORY_VIEW_DESCRIPTION,
+ kOsAndroid,
+ SINGLE_VALUE_TYPE(autofill::switches::kEnableAccessorySuggestionView)
+ },
+#endif // defined(OS_ANDROID)
// NOTE: Adding new command-line switches requires adding corresponding
// entries to enum "LoginCustomFlags" in histograms.xml. See note in
// histograms.xml and don't forget to run AboutFlagsHistogramTest unit test.
diff --git a/chrome/browser/android/chrome_jni_registrar.cc b/chrome/browser/android/chrome_jni_registrar.cc
index 3cb80b2..b9b210b 100644
--- a/chrome/browser/android/chrome_jni_registrar.cc
+++ b/chrome/browser/android/chrome_jni_registrar.cc
@@ -81,6 +81,7 @@
#include "chrome/browser/sync/profile_sync_service_android.h"
#include "chrome/browser/ui/android/autofill/autofill_dialog_controller_android.h"
#include "chrome/browser/ui/android/autofill/autofill_dialog_result.h"
+#include "chrome/browser/ui/android/autofill/autofill_keyboard_accessory_view.h"
#include "chrome/browser/ui/android/autofill/autofill_logger_android.h"
#include "chrome/browser/ui/android/autofill/autofill_popup_view_android.h"
#include "chrome/browser/ui/android/autofill/card_unmask_prompt_view_android.h"
@@ -152,6 +153,9 @@ static base::android::RegistrationMethod kChromeRegisteredMethods[] = {
RegisterAutofillDialogControllerAndroid},
{"AutofillDialogResult",
autofill::AutofillDialogResult::RegisterAutofillDialogResult},
+ {"AutofillKeyboardAccessory",
+ autofill::AutofillKeyboardAccessoryView::
+ RegisterAutofillKeyboardAccessoryView},
{"AutofillLoggerAndroid", autofill::AutofillLoggerAndroid::Register},
{"AutofillPopup",
autofill::AutofillPopupViewAndroid::RegisterAutofillPopupViewAndroid},
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index b2cb439..c947e54 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -1340,6 +1340,7 @@ void ChromeContentBrowserClient::AppendExtraCommandLineSwitches(
static const char* const kSwitchNames[] = {
autofill::switches::kDisableFillOnAccountSelect,
autofill::switches::kDisablePasswordGeneration,
+ autofill::switches::kEnableAccessorySuggestionView,
autofill::switches::kEnableFillOnAccountSelect,
autofill::switches::kEnableFillOnAccountSelectNoHighlighting,
autofill::switches::kEnablePasswordGeneration,
diff --git a/chrome/browser/ui/android/autofill/autofill_keyboard_accessory_view.cc b/chrome/browser/ui/android/autofill/autofill_keyboard_accessory_view.cc
new file mode 100644
index 0000000..cc18308
--- /dev/null
+++ b/chrome/browser/ui/android/autofill/autofill_keyboard_accessory_view.cc
@@ -0,0 +1,105 @@
+// Copyright (c) 2015 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 "chrome/browser/ui/android/autofill/autofill_keyboard_accessory_view.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "chrome/browser/android/resource_mapper.h"
+#include "chrome/browser/ui/android/window_android_helper.h"
+#include "chrome/browser/ui/autofill/autofill_popup_controller.h"
+#include "components/autofill/core/browser/suggestion.h"
+#include "jni/AutofillKeyboardAccessoryBridge_jni.h"
+#include "ui/android/view_android.h"
+#include "ui/android/window_android.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/geometry/rect.h"
+
+namespace autofill {
+
+AutofillKeyboardAccessoryView::AutofillKeyboardAccessoryView(
+ AutofillPopupController* controller)
+ : controller_(controller) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ java_object_.Reset(Java_AutofillKeyboardAccessoryBridge_create(env));
+}
+
+AutofillKeyboardAccessoryView::~AutofillKeyboardAccessoryView() {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ Java_AutofillKeyboardAccessoryBridge_resetNativeViewPointer(
+ env, java_object_.obj());
+}
+
+void AutofillKeyboardAccessoryView::Show() {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ ui::ViewAndroid* view_android = controller_->container_view();
+ DCHECK(view_android);
+ Java_AutofillKeyboardAccessoryBridge_init(
+ env, java_object_.obj(),
+ reinterpret_cast<intptr_t>(this),
+ view_android->GetWindowAndroid()->GetJavaObject().obj());
+
+ UpdateBoundsAndRedrawPopup();
+}
+
+void AutofillKeyboardAccessoryView::Hide() {
+ controller_ = nullptr;
+ JNIEnv* env = base::android::AttachCurrentThread();
+ Java_AutofillKeyboardAccessoryBridge_dismiss(env, java_object_.obj());
+}
+
+void AutofillKeyboardAccessoryView::UpdateBoundsAndRedrawPopup() {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ size_t count = controller_->GetLineCount();
+ ScopedJavaLocalRef<jobjectArray> data_array =
+ Java_AutofillKeyboardAccessoryBridge_createAutofillSuggestionArray(env,
+ count);
+
+ for (size_t i = 0; i < count; ++i) {
+ ScopedJavaLocalRef<jstring> value = base::android::ConvertUTF16ToJavaString(
+ env, controller_->GetElidedValueAt(i));
+ ScopedJavaLocalRef<jstring> label = base::android::ConvertUTF16ToJavaString(
+ env, controller_->GetElidedLabelAt(i));
+ int android_icon_id = 0;
+
+ const autofill::Suggestion& suggestion = controller_->GetSuggestionAt(i);
+ if (!suggestion.icon.empty()) {
+ android_icon_id = ResourceMapper::MapFromChromiumId(
+ controller_->GetIconResourceID(suggestion.icon));
+ }
+
+ Java_AutofillKeyboardAccessoryBridge_addToAutofillSuggestionArray(
+ env, data_array.obj(), i, value.obj(), label.obj(), android_icon_id,
+ suggestion.frontend_id);
+ }
+
+ Java_AutofillKeyboardAccessoryBridge_show(
+ env, java_object_.obj(), data_array.obj(), controller_->IsRTL());
+}
+
+void AutofillKeyboardAccessoryView::SuggestionSelected(JNIEnv* env,
+ jobject obj,
+ jint list_index) {
+ // Race: Hide() may have already run.
+ if (controller_)
+ controller_->AcceptSuggestion(list_index);
+}
+
+void AutofillKeyboardAccessoryView::ViewDismissed(JNIEnv* env, jobject obj) {
+ if (controller_)
+ controller_->ViewDestroyed();
+
+ delete this;
+}
+
+void AutofillKeyboardAccessoryView::InvalidateRow(size_t) {
+}
+
+// static
+bool AutofillKeyboardAccessoryView::RegisterAutofillKeyboardAccessoryView(
+ JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+} // namespace autofill
diff --git a/chrome/browser/ui/android/autofill/autofill_keyboard_accessory_view.h b/chrome/browser/ui/android/autofill/autofill_keyboard_accessory_view.h
new file mode 100644
index 0000000..a146b38
--- /dev/null
+++ b/chrome/browser/ui/android/autofill/autofill_keyboard_accessory_view.h
@@ -0,0 +1,60 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_ANDROID_AUTOFILL_AUTOFILL_KEYBOARD_ACCESSORY_VIEW_H_
+#define CHROME_BROWSER_UI_ANDROID_AUTOFILL_AUTOFILL_KEYBOARD_ACCESSORY_VIEW_H_
+
+#include <jni.h>
+
+#include "base/android/scoped_java_ref.h"
+#include "base/compiler_specific.h"
+#include "chrome/browser/ui/autofill/autofill_popup_view.h"
+
+namespace gfx {
+class Rect;
+}
+
+namespace autofill {
+
+class AutofillPopupController;
+
+// A suggestion view that acts as an alternative to the field-attached popup
+// window. This view appears above the keyboard and spans the width of the
+// screen, condensing rather than overlaying the content area. Enable via
+// --enable-autofill-keyboard-accessory-view.
+class AutofillKeyboardAccessoryView : public AutofillPopupView {
+ public:
+ explicit AutofillKeyboardAccessoryView(AutofillPopupController* controller);
+
+ // --------------------------------------------------------------------------
+ // Methods called from Java via JNI
+ // --------------------------------------------------------------------------
+ // Called when an autofill item was selected.
+ void SuggestionSelected(JNIEnv* env, jobject obj, jint list_index);
+
+ void ViewDismissed(JNIEnv* env, jobject obj);
+
+ static bool RegisterAutofillKeyboardAccessoryView(JNIEnv* env);
+
+ protected:
+ // AutofillPopupView implementation.
+ void Show() override;
+ void Hide() override;
+ void InvalidateRow(size_t row) override;
+ void UpdateBoundsAndRedrawPopup() override;
+
+ private:
+ ~AutofillKeyboardAccessoryView() override;
+
+ AutofillPopupController* controller_; // weak.
+
+ // The corresponding java object.
+ base::android::ScopedJavaGlobalRef<jobject> java_object_;
+
+ DISALLOW_COPY_AND_ASSIGN(AutofillKeyboardAccessoryView);
+};
+
+} // namespace autofill
+
+#endif // CHROME_BROWSER_UI_ANDROID_AUTOFILL_AUTOFILL_KEYBOARD_ACCESSORY_VIEW_H_
diff --git a/chrome/browser/ui/android/autofill/autofill_popup_view_android.cc b/chrome/browser/ui/android/autofill/autofill_popup_view_android.cc
index 4607df8c..b376d9c 100644
--- a/chrome/browser/ui/android/autofill/autofill_popup_view_android.cc
+++ b/chrome/browser/ui/android/autofill/autofill_popup_view_android.cc
@@ -6,10 +6,13 @@
#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
+#include "base/command_line.h"
#include "chrome/browser/android/resource_mapper.h"
+#include "chrome/browser/ui/android/autofill/autofill_keyboard_accessory_view.h"
#include "chrome/browser/ui/android/window_android_helper.h"
#include "chrome/browser/ui/autofill/autofill_popup_controller.h"
#include "components/autofill/core/browser/suggestion.h"
+#include "components/autofill/core/common/autofill_switches.h"
#include "content/public/browser/android/content_view_core.h"
#include "jni/AutofillPopupBridge_jni.h"
#include "ui/android/view_android.h"
@@ -113,6 +116,11 @@ bool AutofillPopupViewAndroid::RegisterAutofillPopupViewAndroid(JNIEnv* env) {
// static
AutofillPopupView* AutofillPopupView::Create(
AutofillPopupController* controller) {
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableAccessorySuggestionView)) {
+ return new AutofillKeyboardAccessoryView(controller);
+ }
+
return new AutofillPopupViewAndroid(controller);
}
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index d761f82..6433723 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -1623,6 +1623,7 @@
'android/java/src/org/chromium/chrome/browser/appmenu/AppMenuDragHelper.java',
'android/java/src/org/chromium/chrome/browser/autofill/AutofillDialogControllerAndroid.java',
'android/java/src/org/chromium/chrome/browser/autofill/AutofillDialogResult.java',
+ 'android/java/src/org/chromium/chrome/browser/autofill/AutofillKeyboardAccessoryBridge.java',
'android/java/src/org/chromium/chrome/browser/autofill/AutofillLogger.java',
'android/java/src/org/chromium/chrome/browser/autofill/AutofillPopupBridge.java',
'android/java/src/org/chromium/chrome/browser/autofill/CardUnmaskBridge.java',
diff --git a/chrome/chrome_browser_ui.gypi b/chrome/chrome_browser_ui.gypi
index c366e15..5dca8bb 100644
--- a/chrome/chrome_browser_ui.gypi
+++ b/chrome/chrome_browser_ui.gypi
@@ -17,6 +17,8 @@
'browser/ui/android/autofill/autofill_dialog_controller_android.h',
'browser/ui/android/autofill/autofill_dialog_result.cc',
'browser/ui/android/autofill/autofill_dialog_result.h',
+ 'browser/ui/android/autofill/autofill_keyboard_accessory_view.cc',
+ 'browser/ui/android/autofill/autofill_keyboard_accessory_view.h',
'browser/ui/android/autofill/autofill_logger_android.cc',
'browser/ui/android/autofill/autofill_logger_android.h',
'browser/ui/android/autofill/autofill_popup_view_android.cc',
diff --git a/components/autofill/content/renderer/autofill_agent.cc b/components/autofill/content/renderer/autofill_agent.cc
index 16099c0..b994e4d 100644
--- a/components/autofill/content/renderer/autofill_agent.cc
+++ b/components/autofill/content/renderer/autofill_agent.cc
@@ -247,6 +247,10 @@ void AutofillAgent::WillSubmitForm(const WebFormElement& form) {
}
void AutofillAgent::DidChangeScrollOffset() {
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableAccessorySuggestionView)) {
+ return;
+ }
HidePopup();
}
diff --git a/components/autofill/content/renderer/page_click_tracker.cc b/components/autofill/content/renderer/page_click_tracker.cc
index 6297bc0..8f1b0c2 100644
--- a/components/autofill/content/renderer/page_click_tracker.cc
+++ b/components/autofill/content/renderer/page_click_tracker.cc
@@ -4,8 +4,10 @@
#include "components/autofill/content/renderer/page_click_tracker.h"
+#include "base/command_line.h"
#include "components/autofill/content/renderer/form_autofill_util.h"
#include "components/autofill/content/renderer/page_click_listener.h"
+#include "components/autofill/core/common/autofill_switches.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_view.h"
#include "third_party/WebKit/public/platform/WebPoint.h"
@@ -98,9 +100,24 @@ void PageClickTracker::DidHandleGestureEvent(const WebGestureEvent& event) {
void PageClickTracker::FocusedNodeChanged(const WebNode& node) {
was_focused_before_now_ = false;
+
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableAccessorySuggestionView)) {
+ focused_node_was_last_clicked_ = true;
+ DoFocusChangeComplete();
+ }
}
void PageClickTracker::FocusChangeComplete() {
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableAccessorySuggestionView)) {
+ return;
+ }
+
+ DoFocusChangeComplete();
+}
+
+void PageClickTracker::DoFocusChangeComplete() {
WebNode focused_node = render_frame()->GetFocusedElement();
if (focused_node_was_last_clicked_ && !focused_node.isNull()) {
const WebInputElement input_element = GetTextWebInputElement(focused_node);
diff --git a/components/autofill/content/renderer/page_click_tracker.h b/components/autofill/content/renderer/page_click_tracker.h
index 41b163d..0c6585a 100644
--- a/components/autofill/content/renderer/page_click_tracker.h
+++ b/components/autofill/content/renderer/page_click_tracker.h
@@ -59,6 +59,7 @@ class PageClickTracker : public content::RenderFrameObserver {
void DidHandleMouseEvent(const blink::WebMouseEvent& event);
void DidHandleGestureEvent(const blink::WebGestureEvent& event);
void FocusChangeComplete();
+ void DoFocusChangeComplete();
// True when the last click was on the focused node.
bool focused_node_was_last_clicked_;
diff --git a/components/autofill/core/browser/autofill_external_delegate.cc b/components/autofill/core/browser/autofill_external_delegate.cc
index 1f711c7..5f8a3e0 100644
--- a/components/autofill/core/browser/autofill_external_delegate.cc
+++ b/components/autofill/core/browser/autofill_external_delegate.cc
@@ -88,9 +88,11 @@ void AutofillExternalDelegate::OnSuggestionsReturned(
// Add or hide warnings as appropriate.
ApplyAutofillWarnings(&suggestions);
+#if !defined(OS_ANDROID)
// Add a separator to go between the values and menu items.
suggestions.push_back(Suggestion());
suggestions.back().frontend_id = POPUP_ITEM_ID_SEPARATOR;
+#endif
if (should_show_scan_credit_card_) {
Suggestion scan_credit_card(
@@ -118,10 +120,12 @@ void AutofillExternalDelegate::OnSuggestionsReturned(
if (has_suggestion_)
ApplyAutofillOptions(&suggestions);
+#if !defined(OS_ANDROID)
// Remove the separator if it is the last element.
DCHECK_GT(suggestions.size(), 0U);
if (suggestions.back().frontend_id == POPUP_ITEM_ID_SEPARATOR)
suggestions.pop_back();
+#endif
// If anything else is added to modify the values after inserting the data
// list, AutofillPopupControllerImpl::UpdateDataListValues will need to be
@@ -359,12 +363,14 @@ void AutofillExternalDelegate::InsertDataListValues(
if (data_list_values_.empty())
return;
+#if !defined(OS_ANDROID)
// Insert the separator between the datalist and Autofill values (if there
// are any).
if (!suggestions->empty()) {
suggestions->insert(suggestions->begin(), Suggestion());
(*suggestions)[0].frontend_id = POPUP_ITEM_ID_SEPARATOR;
}
+#endif
// Insert the datalist elements at the beginning.
suggestions->insert(suggestions->begin(), data_list_values_.size(),
diff --git a/components/autofill/core/browser/autofill_external_delegate_unittest.cc b/components/autofill/core/browser/autofill_external_delegate_unittest.cc
index 04af535..e9bef06 100644
--- a/components/autofill/core/browser/autofill_external_delegate_unittest.cc
+++ b/components/autofill/core/browser/autofill_external_delegate_unittest.cc
@@ -157,15 +157,15 @@ TEST_F(AutofillExternalDelegateUnitTest, TestExternalDelegateVirtualCalls) {
IssueOnQuery(kQueryId);
// The enums must be cast to ints to prevent compile errors on linux_rel.
+ auto element_ids = testing::ElementsAre(
+ kAutofillProfileId,
+#if !defined(OS_ANDROID)
+ static_cast<int>(POPUP_ITEM_ID_SEPARATOR),
+#endif
+ static_cast<int>(POPUP_ITEM_ID_AUTOFILL_OPTIONS));
EXPECT_CALL(
autofill_client_,
- ShowAutofillPopup(_,
- _,
- SuggestionVectorIdsAre(testing::ElementsAre(
- kAutofillProfileId,
- static_cast<int>(POPUP_ITEM_ID_SEPARATOR),
- static_cast<int>(POPUP_ITEM_ID_AUTOFILL_OPTIONS))),
- _));
+ ShowAutofillPopup(_, _, SuggestionVectorIdsAre(element_ids), _));
// This should call ShowAutofillPopup.
std::vector<Suggestion> autofill_item;
@@ -199,17 +199,19 @@ TEST_F(AutofillExternalDelegateUnitTest, ExternalDelegateDataList) {
data_list_items);
// The enums must be cast to ints to prevent compile errors on linux_rel.
+ auto element_ids = testing::ElementsAre(
+ static_cast<int>(POPUP_ITEM_ID_DATALIST_ENTRY),
+#if !defined(OS_ANDROID)
+ static_cast<int>(POPUP_ITEM_ID_SEPARATOR),
+#endif
+ kAutofillProfileId,
+#if !defined(OS_ANDROID)
+ static_cast<int>(POPUP_ITEM_ID_SEPARATOR),
+#endif
+ static_cast<int>(POPUP_ITEM_ID_AUTOFILL_OPTIONS));
EXPECT_CALL(
autofill_client_,
- ShowAutofillPopup(_,
- _,
- SuggestionVectorIdsAre(testing::ElementsAre(
- static_cast<int>(POPUP_ITEM_ID_DATALIST_ENTRY),
- static_cast<int>(POPUP_ITEM_ID_SEPARATOR),
- kAutofillProfileId,
- static_cast<int>(POPUP_ITEM_ID_SEPARATOR),
- static_cast<int>(POPUP_ITEM_ID_AUTOFILL_OPTIONS))),
- _));
+ ShowAutofillPopup(_, _, SuggestionVectorIdsAre(element_ids), _));
// This should call ShowAutofillPopup.
std::vector<Suggestion> autofill_item;
@@ -253,17 +255,19 @@ TEST_F(AutofillExternalDelegateUnitTest, UpdateDataListWhileShowingPopup) {
data_list_items);
// The enums must be cast to ints to prevent compile errors on linux_rel.
+ auto element_ids = testing::ElementsAre(
+ static_cast<int>(POPUP_ITEM_ID_DATALIST_ENTRY),
+#if !defined(OS_ANDROID)
+ static_cast<int>(POPUP_ITEM_ID_SEPARATOR),
+#endif
+ kAutofillProfileId,
+#if !defined(OS_ANDROID)
+ static_cast<int>(POPUP_ITEM_ID_SEPARATOR),
+#endif
+ static_cast<int>(POPUP_ITEM_ID_AUTOFILL_OPTIONS));
EXPECT_CALL(
autofill_client_,
- ShowAutofillPopup(_,
- _,
- SuggestionVectorIdsAre(testing::ElementsAre(
- static_cast<int>(POPUP_ITEM_ID_DATALIST_ENTRY),
- static_cast<int>(POPUP_ITEM_ID_SEPARATOR),
- kAutofillProfileId,
- static_cast<int>(POPUP_ITEM_ID_SEPARATOR),
- static_cast<int>(POPUP_ITEM_ID_AUTOFILL_OPTIONS))),
- _));
+ ShowAutofillPopup(_, _, SuggestionVectorIdsAre(element_ids), _));
// Ensure the popup is displayed.
std::vector<Suggestion> autofill_item;
diff --git a/components/autofill/core/common/autofill_switches.cc b/components/autofill/core/common/autofill_switches.cc
index 6cf1c85..83c38a1 100644
--- a/components/autofill/core/common/autofill_switches.cc
+++ b/components/autofill/core/common/autofill_switches.cc
@@ -28,6 +28,9 @@ const char kDisablePasswordGeneration[] = "disable-password-generation";
// The "disable" flag for kEnableSingleClickAutofill.
const char kDisableSingleClickAutofill[] = "disable-single-click-autofill";
+const char kEnableAccessorySuggestionView[] =
+ "enable-autofill-keyboard-accessory-view";
+
// Enables using device's camera to scan a new credit card when filling out a
// credit card form.
const char kEnableCreditCardScan[] = "enable-credit-card-scan";
diff --git a/components/autofill/core/common/autofill_switches.h b/components/autofill/core/common/autofill_switches.h
index 7ab7e07..bc1840d 100644
--- a/components/autofill/core/common/autofill_switches.h
+++ b/components/autofill/core/common/autofill_switches.h
@@ -15,6 +15,7 @@ extern const char kDisableFillOnAccountSelect[];
extern const char kDisableOfferStoreUnmaskedWalletCards[];
extern const char kDisablePasswordGeneration[];
extern const char kDisableSingleClickAutofill[];
+extern const char kEnableAccessorySuggestionView[];
extern const char kEnableCreditCardScan[];
extern const char kEnableFillOnAccountSelect[];
extern const char kEnableFillOnAccountSelectNoHighlighting[];
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 7eb0f36..64aa2d7 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -55452,6 +55452,7 @@ To add a new entry, add it with any value and run test to compute valid value.
<int value="625273056" label="disable-boot-animation"/>
<int value="630947363" label="touch-events"/>
<int value="643725031" label="disable-touch-feedback"/>
+ <int value="650602639" label="enable-autofill-keyboard-accessory-view"/>
<int value="683410401"
label="enable-proximity-auth-bluetooth-low-energy-discovery"/>
<int value="689489984" label="disable-zero-suggest"/>
diff --git a/ui/android/java/res/drawable/autofill_accessory_view_border.xml b/ui/android/java/res/drawable/autofill_accessory_view_border.xml
new file mode 100644
index 0000000..c4d8813
--- /dev/null
+++ b/ui/android/java/res/drawable/autofill_accessory_view_border.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2015 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.
+-->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:insetBottom="-1dp"
+ android:insetLeft="-1dp"
+ android:insetRight="-1dp">
+
+ <shape android:shape="rectangle">
+ <stroke android:width="1dp" android:color="@android:color/black" />
+ <solid android:color="@android:color/white" />
+ </shape>
+</inset>
diff --git a/ui/android/java/src/org/chromium/ui/autofill/AutofillKeyboardAccessory.java b/ui/android/java/src/org/chromium/ui/autofill/AutofillKeyboardAccessory.java
new file mode 100644
index 0000000..d114ccd
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/autofill/AutofillKeyboardAccessory.java
@@ -0,0 +1,117 @@
+// Copyright 2015 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.ui.autofill;
+
+import android.annotation.SuppressLint;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.AdapterView;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.base.annotations.SuppressFBWarnings;
+import org.chromium.ui.DropdownAdapter;
+import org.chromium.ui.R;
+import org.chromium.ui.base.WindowAndroid;
+
+import java.util.HashSet;
+
+/**
+ * The Autofill suggestion view that lists relevant suggestions. It sits above the keyboard and
+ * below the content area.
+ */
+public class AutofillKeyboardAccessory extends ListView implements AdapterView.OnItemClickListener {
+ private final WindowAndroid mWindowAndroid;
+ private final AutofillKeyboardAccessoryDelegate mAutofillCallback;
+
+ /**
+ * An interface to handle the touch interaction with an AutofillKeyboardAccessory object.
+ */
+ public interface AutofillKeyboardAccessoryDelegate {
+ /**
+ * Informs the controller the AutofillKeyboardAccessory was hidden.
+ */
+ public void dismissed();
+
+ /**
+ * Handles the selection of an Autofill suggestion from an AutofillKeyboardAccessory.
+ * @param listIndex The index of the selected Autofill suggestion.
+ */
+ public void suggestionSelected(int listIndex);
+ }
+
+ /**
+ * Creates an AutofillKeyboardAccessory with specified parameters.
+ * @param windowAndroid The owning WindowAndroid.
+ * @param autofillCallback A object that handles the calls to the native
+ * AutofillKeyboardAccessoryView.
+ */
+ @SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
+ public AutofillKeyboardAccessory(
+ WindowAndroid windowAndroid, AutofillKeyboardAccessoryDelegate autofillCallback) {
+ super(windowAndroid.getActivity().get());
+ assert autofillCallback != null;
+ assert windowAndroid.getActivity().get() != null;
+ mWindowAndroid = windowAndroid;
+ mAutofillCallback = autofillCallback;
+
+ setBackgroundResource(R.drawable.autofill_accessory_view_border);
+ setOnItemClickListener(this);
+ setContentDescription(windowAndroid.getActivity().get().getString(
+ R.string.autofill_popup_content_description));
+ }
+
+ /**
+ * Shows the given suggestions.
+ * @param suggestions Autofill suggestion data.
+ * @param isRtl Gives the layout direction for the <input> field.
+ */
+ @SuppressLint("InlinedApi")
+ public void showWithSuggestions(AutofillSuggestion[] suggestions, boolean isRtl) {
+ setAdapter(new DropdownAdapter(
+ mWindowAndroid.getActivity().get(), suggestions, new HashSet<Integer>()));
+ ApiCompatibilityUtils.setLayoutDirection(
+ this, isRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
+
+ int height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ // Limit the visible number of suggestions (others are accessible via scrolling).
+ final int suggestionLimit = 2;
+ ListAdapter listAdapter = getAdapter();
+ if (listAdapter.getCount() > suggestionLimit) {
+ height = 0;
+ for (int i = 0; i < suggestionLimit; i++) {
+ View listItem = listAdapter.getView(i, null, this);
+ listItem.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ height += listItem.getMeasuredHeight();
+ }
+ }
+
+ setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height));
+
+ if (getParent() == null) {
+ ViewGroup container = mWindowAndroid.getKeyboardAccessoryView();
+ container.addView(this);
+ container.setVisibility(View.VISIBLE);
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ }
+ }
+
+ /**
+ * Called to hide the suggestion view.
+ */
+ public void dismiss() {
+ ViewGroup container = mWindowAndroid.getKeyboardAccessoryView();
+ container.removeView(this);
+ container.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ mAutofillCallback.suggestionSelected(position);
+ }
+}
diff --git a/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java b/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java
index 0870221..86cb72d 100644
--- a/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java
+++ b/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java
@@ -16,6 +16,7 @@ import android.os.Bundle;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.Toast;
import org.chromium.base.CalledByNative;
@@ -55,6 +56,8 @@ public class WindowAndroid {
private HashSet<Animator> mAnimationsOverContent = new HashSet<Animator>();
private View mAnimationPlaceholderView;
+ private ViewGroup mKeyboardAccessoryView;
+
private final VSyncMonitor.Listener mVSyncListener = new VSyncMonitor.Listener() {
@Override
public void onVSync(VSyncMonitor monitor, long vsyncTimeMicros) {
@@ -308,6 +311,22 @@ public class WindowAndroid {
}
/**
+ * Sets the keyboard accessory view.
+ * @param view This view sits at the bottom of the content area and pushes the content up rather
+ * than overlaying it. Currently used as a container for Autofill suggestions.
+ */
+ public void setKeyboardAccessoryView(ViewGroup view) {
+ mKeyboardAccessoryView = view;
+ }
+
+ /**
+ * {@see setKeyboardAccessoryView(ViewGroup)}.
+ */
+ public ViewGroup getKeyboardAccessoryView() {
+ return mKeyboardAccessoryView;
+ }
+
+ /**
* Start a post-layout animation on top of web content.
*
* By default, Android optimizes what it shows on top of SurfaceViews (saves power).