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 java.util.ArrayList;
20 import org.chromium.ui.R;
21 import org.chromium.ui.ViewAndroidDelegate;
24 * The Autofill suggestion popup that lists relevant suggestions.
26 public class AutofillPopup extends ListPopupWindow implements AdapterView.OnItemClickListener {
29 * Constants defining types of Autofill suggestion entries.
30 * Has to be kept in sync with enum in WebAutofillClient.h
32 * Not supported: MenuItemIDWarningMessage, MenuItemIDSeparator, MenuItemIDClearForm, and
33 * MenuItemIDAutofillOptions.
35 private static final int ITEM_ID_AUTOCOMPLETE_ENTRY = 0;
36 private static final int ITEM_ID_PASSWORD_ENTRY = -2;
37 private static final int ITEM_ID_DATA_LIST_ENTRY = -6;
39 private static final int TEXT_PADDING_DP = 30;
41 private final AutofillPopupDelegate mAutofillCallback;
42 private final Context mContext;
43 private final ViewAndroidDelegate mViewAndroidDelegate;
44 private View mAnchorView;
45 private float mAnchorWidth;
46 private float mAnchorHeight;
47 private float mAnchorX;
48 private float mAnchorY;
49 private Paint mLabelViewPaint;
50 private Paint mSublabelViewPaint;
51 private OnLayoutChangeListener mLayoutChangeListener;
54 * An interface to handle the touch interaction with an AutofillPopup object.
56 public interface AutofillPopupDelegate {
58 * Requests the controller to hide AutofillPopup.
60 public void requestHide();
63 * Handles the selection of an Autofill suggestion from an AutofillPopup.
64 * @param listIndex The index of the selected Autofill suggestion.
66 public void suggestionSelected(int listIndex);
70 * Creates an AutofillWindow with specified parameters.
71 * @param context Application context.
72 * @param viewAndroidDelegate View delegate used to add and remove views.
73 * @param autofillCallback A object that handles the calls to the native AutofillPopupView.
75 public AutofillPopup(Context context, ViewAndroidDelegate viewAndroidDelegate,
76 AutofillPopupDelegate autofillCallback) {
77 super(context, null, 0, R.style.AutofillPopupWindow);
79 mViewAndroidDelegate = viewAndroidDelegate ;
80 mAutofillCallback = autofillCallback;
82 setOnItemClickListener(this);
84 mAnchorView = mViewAndroidDelegate.acquireAnchorView();
85 mAnchorView.setId(R.id.autofill_popup_window);
86 mAnchorView.setTag(this);
88 mViewAndroidDelegate.setAnchorViewPosition(mAnchorView, mAnchorX, mAnchorY, mAnchorWidth,
91 mLayoutChangeListener = new OnLayoutChangeListener() {
93 public void onLayoutChange(View v, int left, int top, int right, int bottom,
94 int oldLeft, int oldTop, int oldRight, int oldBottom) {
95 if (v == mAnchorView) AutofillPopup.this.show();
99 mAnchorView.addOnLayoutChangeListener(mLayoutChangeListener);
100 setAnchorView(mAnchorView);
105 // An ugly hack to keep the popup from expanding on top of the keyboard.
106 setInputMethodMode(INPUT_METHOD_NEEDED);
111 * Sets the location and the size of the anchor view that the AutofillPopup will use to attach
113 * @param x X coordinate of the top left corner of the anchor view.
114 * @param y Y coordinate of the top left corner of the anchor view.
115 * @param width The width of the anchor view.
116 * @param height The height of the anchor view.
118 public void setAnchorRect(float x, float y, float width, float height) {
119 mAnchorWidth = width;
120 mAnchorHeight = height;
123 if (mAnchorView != null) {
124 mViewAndroidDelegate.setAnchorViewPosition(mAnchorView, mAnchorX, mAnchorY,
125 mAnchorWidth, mAnchorHeight);
130 * Sets the Autofill suggestions to display in the popup and shows the popup.
131 * @param suggestions Autofill suggestion data.
133 public void show(AutofillSuggestion[] suggestions) {
134 // Remove the AutofillSuggestions with IDs that are not supported by Android
135 ArrayList<AutofillSuggestion> cleanedData = new ArrayList<AutofillSuggestion>();
136 for (int i = 0; i < suggestions.length; i++) {
137 int itemId = suggestions[i].mUniqueId;
138 if (itemId > 0 || itemId == ITEM_ID_AUTOCOMPLETE_ENTRY ||
139 itemId == ITEM_ID_PASSWORD_ENTRY || itemId == ITEM_ID_DATA_LIST_ENTRY) {
140 cleanedData.add(suggestions[i]);
143 setAdapter(new AutofillListAdapter(mContext, cleanedData));
144 // Once the mAnchorRect is resized and placed correctly, it will show the Autofill popup.
145 mAnchorWidth = Math.max(getDesiredWidth(cleanedData), mAnchorWidth);
146 mViewAndroidDelegate.setAnchorViewPosition(mAnchorView, mAnchorX, mAnchorY, mAnchorWidth,
151 * Overrides the default dismiss behavior to request the controller to dismiss the view.
154 public void dismiss() {
155 mAutofillCallback.requestHide();
159 * Hides the popup and removes the anchor view from the ContainerView.
163 mAnchorView.removeOnLayoutChangeListener(mLayoutChangeListener);
164 mAnchorView.setTag(null);
165 mViewAndroidDelegate.releaseAnchorView(mAnchorView);
169 * Get desired popup window width by calculating the maximum text length from Autofill data.
170 * @param data Autofill suggestion data.
171 * @return The popup window width in DIP.
173 private float getDesiredWidth(ArrayList<AutofillSuggestion> data) {
174 if (mLabelViewPaint == null || mSublabelViewPaint == null) {
175 LayoutInflater inflater =
176 (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
177 View layout = inflater.inflate(R.layout.autofill_text, null);
178 TextView labelView = (TextView) layout.findViewById(R.id.autofill_label);
179 mLabelViewPaint = labelView.getPaint();
180 TextView sublabelView = (TextView) layout.findViewById(R.id.autofill_sublabel);
181 mSublabelViewPaint = sublabelView.getPaint();
184 float maxTextWidth = 0;
185 Rect bounds = new Rect();
186 for (int i = 0; i < data.size(); ++i) {
188 String label = data.get(i).mLabel;
189 if (!TextUtils.isEmpty(label)) {
190 mLabelViewPaint.getTextBounds(label, 0, label.length(), bounds);
192 float labelWidth = bounds.width();
195 String sublabel = data.get(i).mSublabel;
196 if (!TextUtils.isEmpty(sublabel)) {
197 mSublabelViewPaint.getTextBounds(sublabel, 0, sublabel.length(), bounds);
200 float localMax = Math.max(labelWidth, bounds.width());
201 maxTextWidth = Math.max(maxTextWidth, localMax);
203 // Scale it down to make it unscaled by screen density.
204 maxTextWidth = maxTextWidth / mContext.getResources().getDisplayMetrics().density;
206 return maxTextWidth + TEXT_PADDING_DP;
210 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
211 mAutofillCallback.suggestionSelected(position);