AutofillPopup.java revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
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.
4
5package org.chromium.ui.autofill;
6
7import android.content.Context;
8import android.graphics.Paint;
9import android.graphics.Rect;
10import android.text.TextUtils;
11import android.view.LayoutInflater;
12import android.view.View;
13import android.view.View.OnLayoutChangeListener;
14import android.widget.AdapterView;
15import android.widget.ListPopupWindow;
16import android.widget.TextView;
17
18import java.util.ArrayList;
19
20import org.chromium.ui.R;
21import org.chromium.ui.ViewAndroidDelegate;
22
23/**
24 * The Autofill suggestion popup that lists relevant suggestions.
25 */
26public class AutofillPopup extends ListPopupWindow implements AdapterView.OnItemClickListener {
27
28    /**
29     * Constants defining types of Autofill suggestion entries.
30     * Has to be kept in sync with enum in WebAutofillClient.h
31     *
32     * Not supported: MenuItemIDWarningMessage, MenuItemIDSeparator, MenuItemIDClearForm, and
33     * MenuItemIDAutofillOptions.
34     */
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;
38
39    private static final int TEXT_PADDING_DP = 30;
40
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;
52
53    /**
54     * An interface to handle the touch interaction with an AutofillPopup object.
55     */
56    public interface AutofillPopupDelegate {
57        /**
58         * Requests the controller to hide AutofillPopup.
59         */
60        public void requestHide();
61
62        /**
63         * Handles the selection of an Autofill suggestion from an AutofillPopup.
64         * @param listIndex The index of the selected Autofill suggestion.
65         */
66        public void suggestionSelected(int listIndex);
67    }
68
69    /**
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.
74     */
75    public AutofillPopup(Context context, ViewAndroidDelegate viewAndroidDelegate,
76            AutofillPopupDelegate autofillCallback) {
77        super(context, null, 0, R.style.AutofillPopupWindow);
78        mContext = context;
79        mViewAndroidDelegate = viewAndroidDelegate ;
80        mAutofillCallback = autofillCallback;
81
82        setOnItemClickListener(this);
83
84        mAnchorView = mViewAndroidDelegate.acquireAnchorView();
85        mAnchorView.setId(R.id.autofill_popup_window);
86        mAnchorView.setTag(this);
87
88        mViewAndroidDelegate.setAnchorViewPosition(mAnchorView, mAnchorX, mAnchorY, mAnchorWidth,
89                mAnchorHeight);
90
91        mLayoutChangeListener = new OnLayoutChangeListener() {
92            @Override
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();
96            }
97        };
98
99        mAnchorView.addOnLayoutChangeListener(mLayoutChangeListener);
100        setAnchorView(mAnchorView);
101    }
102
103    @Override
104    public void show() {
105        // An ugly hack to keep the popup from expanding on top of the keyboard.
106        setInputMethodMode(INPUT_METHOD_NEEDED);
107        super.show();
108    }
109
110    /**
111     * Sets the location and the size of the anchor view that the AutofillPopup will use to attach
112     * itself.
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.
117     */
118    public void setAnchorRect(float x, float y, float width, float height) {
119        mAnchorWidth = width;
120        mAnchorHeight = height;
121        mAnchorX = x;
122        mAnchorY = y;
123        if (mAnchorView != null) {
124            mViewAndroidDelegate.setAnchorViewPosition(mAnchorView, mAnchorX, mAnchorY,
125                    mAnchorWidth, mAnchorHeight);
126        }
127    }
128
129    /**
130     * Sets the Autofill suggestions to display in the popup and shows the popup.
131     * @param suggestions Autofill suggestion data.
132     */
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]);
141            }
142        }
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,
147                mAnchorHeight);
148    }
149
150    /**
151     * Overrides the default dismiss behavior to request the controller to dismiss the view.
152     */
153    @Override
154    public void dismiss() {
155        mAutofillCallback.requestHide();
156    }
157
158    /**
159     * Hides the popup and removes the anchor view from the ContainerView.
160     */
161    public void hide() {
162        super.dismiss();
163        mAnchorView.removeOnLayoutChangeListener(mLayoutChangeListener);
164        mAnchorView.setTag(null);
165        mViewAndroidDelegate.releaseAnchorView(mAnchorView);
166    }
167
168    /**
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.
172     */
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();
182        }
183
184        float maxTextWidth = 0;
185        Rect bounds = new Rect();
186        for (int i = 0; i < data.size(); ++i) {
187            bounds.setEmpty();
188            String label = data.get(i).mLabel;
189            if (!TextUtils.isEmpty(label)) {
190                mLabelViewPaint.getTextBounds(label, 0, label.length(), bounds);
191            }
192            float labelWidth = bounds.width();
193
194            bounds.setEmpty();
195            String sublabel = data.get(i).mSublabel;
196            if (!TextUtils.isEmpty(sublabel)) {
197                mSublabelViewPaint.getTextBounds(sublabel, 0, sublabel.length(), bounds);
198            }
199
200            float localMax = Math.max(labelWidth, bounds.width());
201            maxTextWidth = Math.max(maxTextWidth, localMax);
202        }
203        // Scale it down to make it unscaled by screen density.
204        maxTextWidth = maxTextWidth / mContext.getResources().getDisplayMetrics().density;
205        // Adding padding.
206        return maxTextWidth + TEXT_PADDING_DP;
207    }
208
209    @Override
210    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
211        mAutofillCallback.suggestionSelected(position);
212    }
213
214}
215