AutofillPopup.java revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
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        mViewAndroidDelegate.setAnchorViewPosition(mAnchorView, mAnchorX, mAnchorY, mAnchorWidth,
86                mAnchorHeight);
87
88        mLayoutChangeListener = new OnLayoutChangeListener() {
89            @Override
90            public void onLayoutChange(View v, int left, int top, int right, int bottom,
91                    int oldLeft, int oldTop, int oldRight, int oldBottom) {
92                if (v == mAnchorView) AutofillPopup.this.show();
93            }
94        };
95
96        mAnchorView.addOnLayoutChangeListener(mLayoutChangeListener);
97        setAnchorView(mAnchorView);
98    }
99
100    @Override
101    public void show() {
102        // An ugly hack to keep the popup from expanding on top of the keyboard.
103        setInputMethodMode(INPUT_METHOD_NEEDED);
104        super.show();
105    }
106
107    /**
108     * Sets the location and the size of the anchor view that the AutofillPopup will use to attach
109     * itself.
110     * @param x X coordinate of the top left corner of the anchor view.
111     * @param y Y coordinate of the top left corner of the anchor view.
112     * @param width The width of the anchor view.
113     * @param height The height of the anchor view.
114     */
115    public void setAnchorRect(float x, float y, float width, float height) {
116        mAnchorWidth = width;
117        mAnchorHeight = height;
118        mAnchorX = x;
119        mAnchorY = y;
120        if (mAnchorView != null) {
121            mViewAndroidDelegate.setAnchorViewPosition(mAnchorView, mAnchorX, mAnchorY,
122                    mAnchorWidth, mAnchorHeight);
123        }
124    }
125
126    /**
127     * Sets the Autofill suggestions to display in the popup and shows the popup.
128     * @param suggestions Autofill suggestion data.
129     */
130    public void show(AutofillSuggestion[] suggestions) {
131        // Remove the AutofillSuggestions with IDs that are not supported by Android
132        ArrayList<AutofillSuggestion> cleanedData = new ArrayList<AutofillSuggestion>();
133        for (int i = 0; i < suggestions.length; i++) {
134            int itemId = suggestions[i].mUniqueId;
135            if (itemId > 0 || itemId == ITEM_ID_AUTOCOMPLETE_ENTRY ||
136                    itemId == ITEM_ID_PASSWORD_ENTRY || itemId == ITEM_ID_DATA_LIST_ENTRY) {
137                cleanedData.add(suggestions[i]);
138            }
139        }
140        setAdapter(new AutofillListAdapter(mContext, cleanedData));
141        // Once the mAnchorRect is resized and placed correctly, it will show the Autofill popup.
142        mAnchorWidth = Math.max(getDesiredWidth(cleanedData), mAnchorWidth);
143        mViewAndroidDelegate.setAnchorViewPosition(mAnchorView, mAnchorX, mAnchorY, mAnchorWidth,
144                mAnchorHeight);
145    }
146
147    /**
148     * Overrides the default dismiss behavior to request the controller to dismiss the view.
149     */
150    @Override
151    public void dismiss() {
152        mAutofillCallback.requestHide();
153    }
154
155    /**
156     * Hides the popup and removes the anchor view from the ContainerView.
157     */
158    public void hide() {
159        super.dismiss();
160        mAnchorView.removeOnLayoutChangeListener(mLayoutChangeListener);
161        mViewAndroidDelegate.releaseAnchorView(mAnchorView);
162    }
163
164    /**
165     * Get desired popup window width by calculating the maximum text length from Autofill data.
166     * @param data Autofill suggestion data.
167     * @return The popup window width in DIP.
168     */
169    private float getDesiredWidth(ArrayList<AutofillSuggestion> data) {
170        if (mLabelViewPaint == null || mSublabelViewPaint == null) {
171            LayoutInflater inflater =
172                    (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
173            View layout = inflater.inflate(R.layout.autofill_text, null);
174            TextView labelView = (TextView) layout.findViewById(R.id.autofill_label);
175            mLabelViewPaint = labelView.getPaint();
176            TextView sublabelView = (TextView) layout.findViewById(R.id.autofill_sublabel);
177            mSublabelViewPaint = sublabelView.getPaint();
178        }
179
180        float maxTextWidth = 0;
181        Rect bounds = new Rect();
182        for (int i = 0; i < data.size(); ++i) {
183            bounds.setEmpty();
184            String label = data.get(i).mLabel;
185            if (!TextUtils.isEmpty(label)) {
186                mLabelViewPaint.getTextBounds(label, 0, label.length(), bounds);
187            }
188            float labelWidth = bounds.width();
189
190            bounds.setEmpty();
191            String sublabel = data.get(i).mSublabel;
192            if (!TextUtils.isEmpty(sublabel)) {
193                mSublabelViewPaint.getTextBounds(sublabel, 0, sublabel.length(), bounds);
194            }
195
196            float localMax = Math.max(labelWidth, bounds.width());
197            maxTextWidth = Math.max(maxTextWidth, localMax);
198        }
199        // Scale it down to make it unscaled by screen density.
200        maxTextWidth = maxTextWidth / mContext.getResources().getDisplayMetrics().density;
201        // Adding padding.
202        return maxTextWidth + TEXT_PADDING_DP;
203    }
204
205    @Override
206    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
207        mAutofillCallback.suggestionSelected(position);
208    }
209
210}
211