1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 package org.chromium.ui.autofill;
7 import android.content.Context;
8 import android.graphics.Paint;
9 import android.graphics.Rect;
10 import android.text.TextUtils;
11 import android.view.LayoutInflater;
12 import android.view.View;
13 import android.view.View.OnLayoutChangeListener;
14 import android.widget.AdapterView;
15 import android.widget.ListPopupWindow;
16 import android.widget.TextView;
18 import org.chromium.ui.R;
19 import org.chromium.ui.base.ViewAndroidDelegate;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.HashSet;
24 import java.util.List;
27 * The Autofill suggestion popup that lists relevant suggestions.
29 public class AutofillPopup extends ListPopupWindow implements AdapterView.OnItemClickListener {
32 * Constants defining types of Autofill suggestion entries.
33 * Has to be kept in sync with enum in WebAutofillClient.h
35 * Not supported: MenuItemIDWarningMessage, MenuItemIDClearForm, and
36 * MenuItemIDAutofillOptions.
38 private static final int ITEM_ID_AUTOCOMPLETE_ENTRY = 0;
39 private static final int ITEM_ID_PASSWORD_ENTRY = -2;
40 private static final int ITEM_ID_SEPARATOR_ENTRY = -3;
41 private static final int ITEM_ID_DATA_LIST_ENTRY = -6;
43 private static final int TEXT_PADDING_DP = 30;
45 private final AutofillPopupDelegate mAutofillCallback;
46 private final Context mContext;
47 private final ViewAndroidDelegate mViewAndroidDelegate;
48 private View mAnchorView;
49 private float mAnchorWidth;
50 private float mAnchorHeight;
51 private float mAnchorX;
52 private float mAnchorY;
53 private Paint mLabelViewPaint;
54 private Paint mSublabelViewPaint;
55 private OnLayoutChangeListener mLayoutChangeListener;
56 private List<AutofillSuggestion> mSuggestions;
59 * An interface that can be injected to log field names selected by
62 public interface AutofillLogger {
63 public void logSuggestionSelected(String fieldName);
66 private static AutofillLogger sAutofillLogger = null;
68 public static void setAutofillLogger(AutofillLogger autofillLogger) {
69 sAutofillLogger = autofillLogger;
73 * An interface to handle the touch interaction with an AutofillPopup object.
75 public interface AutofillPopupDelegate {
77 * Requests the controller to hide AutofillPopup.
79 public void requestHide();
82 * Handles the selection of an Autofill suggestion from an AutofillPopup.
83 * @param listIndex The index of the selected Autofill suggestion.
85 public void suggestionSelected(int listIndex);
89 * Creates an AutofillWindow with specified parameters.
90 * @param context Application context.
91 * @param viewAndroidDelegate View delegate used to add and remove views.
92 * @param autofillCallback A object that handles the calls to the native AutofillPopupView.
94 public AutofillPopup(Context context, ViewAndroidDelegate viewAndroidDelegate,
95 AutofillPopupDelegate autofillCallback) {
96 super(context, null, 0, R.style.AutofillPopupWindow);
98 mViewAndroidDelegate = viewAndroidDelegate;
99 mAutofillCallback = autofillCallback;
101 setOnItemClickListener(this);
103 mAnchorView = mViewAndroidDelegate.acquireAnchorView();
104 mAnchorView.setId(R.id.autofill_popup_window);
105 mAnchorView.setTag(this);
107 mViewAndroidDelegate.setAnchorViewPosition(mAnchorView, mAnchorX, mAnchorY, mAnchorWidth,
110 mLayoutChangeListener = new OnLayoutChangeListener() {
112 public void onLayoutChange(View v, int left, int top, int right, int bottom,
113 int oldLeft, int oldTop, int oldRight, int oldBottom) {
114 if (v == mAnchorView) AutofillPopup.this.show();
118 mAnchorView.addOnLayoutChangeListener(mLayoutChangeListener);
119 setAnchorView(mAnchorView);
124 // An ugly hack to keep the popup from expanding on top of the keyboard.
125 setInputMethodMode(INPUT_METHOD_NEEDED);
127 getListView().setDividerHeight(0);
131 * Sets the location and the size of the anchor view that the AutofillPopup will use to attach
133 * @param x X coordinate of the top left corner of the anchor view.
134 * @param y Y coordinate of the top left corner of the anchor view.
135 * @param width The width of the anchor view.
136 * @param height The height of the anchor view.
138 public void setAnchorRect(float x, float y, float width, float height) {
139 mAnchorWidth = width;
140 mAnchorHeight = height;
143 if (mAnchorView != null) {
144 mViewAndroidDelegate.setAnchorViewPosition(mAnchorView, mAnchorX, mAnchorY,
145 mAnchorWidth, mAnchorHeight);
150 * Sets the Autofill suggestions to display in the popup and shows the popup.
151 * @param suggestions Autofill suggestion data.
153 public void show(AutofillSuggestion[] suggestions) {
154 mSuggestions = new ArrayList<AutofillSuggestion>(Arrays.asList(suggestions));
155 // Remove the AutofillSuggestions with IDs that are not supported by Android
156 ArrayList<AutofillSuggestion> cleanedData = new ArrayList<AutofillSuggestion>();
157 HashSet<Integer> separators = new HashSet<Integer>();
158 for (int i = 0; i < suggestions.length; i++) {
159 int itemId = suggestions[i].mUniqueId;
160 if (itemId > 0 || itemId == ITEM_ID_AUTOCOMPLETE_ENTRY ||
161 itemId == ITEM_ID_PASSWORD_ENTRY || itemId == ITEM_ID_DATA_LIST_ENTRY) {
162 cleanedData.add(suggestions[i]);
163 } else if (itemId == ITEM_ID_SEPARATOR_ENTRY) {
164 separators.add(cleanedData.size());
167 setAdapter(new AutofillListAdapter(mContext, cleanedData, separators));
168 // Once the mAnchorRect is resized and placed correctly, it will show the Autofill popup.
169 mAnchorWidth = Math.max(getDesiredWidth(cleanedData), mAnchorWidth);
170 mViewAndroidDelegate.setAnchorViewPosition(mAnchorView, mAnchorX, mAnchorY, mAnchorWidth,
175 * Overrides the default dismiss behavior to request the controller to dismiss the view.
178 public void dismiss() {
179 mAutofillCallback.requestHide();
183 * Hides the popup and removes the anchor view from the ContainerView.
187 mAnchorView.removeOnLayoutChangeListener(mLayoutChangeListener);
188 mAnchorView.setTag(null);
189 mViewAndroidDelegate.releaseAnchorView(mAnchorView);
193 * Get desired popup window width by calculating the maximum text length from Autofill data.
194 * @param data Autofill suggestion data.
195 * @return The popup window width in DIP.
197 private float getDesiredWidth(ArrayList<AutofillSuggestion> data) {
198 if (mLabelViewPaint == null || mSublabelViewPaint == null) {
199 LayoutInflater inflater =
200 (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
201 View layout = inflater.inflate(R.layout.autofill_text, null);
202 TextView labelView = (TextView) layout.findViewById(R.id.autofill_label);
203 mLabelViewPaint = labelView.getPaint();
204 TextView sublabelView = (TextView) layout.findViewById(R.id.autofill_sublabel);
205 mSublabelViewPaint = sublabelView.getPaint();
208 float maxTextWidth = 0;
209 Rect bounds = new Rect();
210 for (int i = 0; i < data.size(); ++i) {
212 String label = data.get(i).mLabel;
213 if (!TextUtils.isEmpty(label)) {
214 mLabelViewPaint.getTextBounds(label, 0, label.length(), bounds);
216 float labelWidth = bounds.width();
219 String sublabel = data.get(i).mSublabel;
220 if (!TextUtils.isEmpty(sublabel)) {
221 mSublabelViewPaint.getTextBounds(sublabel, 0, sublabel.length(), bounds);
224 float localMax = Math.max(labelWidth, bounds.width());
225 maxTextWidth = Math.max(maxTextWidth, localMax);
227 // Scale it down to make it unscaled by screen density.
228 maxTextWidth = maxTextWidth / mContext.getResources().getDisplayMetrics().density;
230 return maxTextWidth + TEXT_PADDING_DP;
234 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
235 AutofillListAdapter adapter = (AutofillListAdapter) parent.getAdapter();
236 AutofillSuggestion selectedSuggestion = adapter.getItem(position);
237 int listIndex = mSuggestions.indexOf(selectedSuggestion);
238 assert listIndex > -1;
239 if (sAutofillLogger != null) {
240 sAutofillLogger.logSuggestionSelected(selectedSuggestion.mLabel);
242 mAutofillCallback.suggestionSelected(listIndex);