diff options
author | estade <estade@chromium.org> | 2015-04-28 11:07:42 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-04-28 18:08:35 +0000 |
commit | b5efbab6f71b0e2660e3d5231d99df698c0e5b38 (patch) | |
tree | 4a6ddaebaeec6b83fef16a36f5fa85fe7a81e038 | |
parent | 51689d131ef4401d3349684ab466e666469f2c7e (diff) | |
download | chromium_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}
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). |