Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / ui / android / java / src / org / chromium / ui / autofill / AutofillPopup.java
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
5 package org.chromium.ui.autofill;
6
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;
17
18 import org.chromium.ui.R;
19 import org.chromium.ui.base.ViewAndroidDelegate;
20
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.HashSet;
24 import java.util.List;
25
26 /**
27  * The Autofill suggestion popup that lists relevant suggestions.
28  */
29 public class AutofillPopup extends ListPopupWindow implements AdapterView.OnItemClickListener {
30
31     /**
32      * Constants defining types of Autofill suggestion entries.
33      * Has to be kept in sync with enum in WebAutofillClient.h
34      *
35      * Not supported: MenuItemIDWarningMessage, MenuItemIDClearForm, and
36      * MenuItemIDAutofillOptions.
37      */
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;
42
43     private static final int TEXT_PADDING_DP = 30;
44
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;
57
58     /**
59      * An interface that can be injected to log field names selected by
60      * the autofill.
61      */
62     public interface AutofillLogger {
63         public void logSuggestionSelected(String fieldName);
64     }
65
66     private static AutofillLogger sAutofillLogger = null;
67
68     public static void setAutofillLogger(AutofillLogger autofillLogger) {
69         sAutofillLogger = autofillLogger;
70     }
71
72     /**
73      * An interface to handle the touch interaction with an AutofillPopup object.
74      */
75     public interface AutofillPopupDelegate {
76         /**
77          * Requests the controller to hide AutofillPopup.
78          */
79         public void requestHide();
80
81         /**
82          * Handles the selection of an Autofill suggestion from an AutofillPopup.
83          * @param listIndex The index of the selected Autofill suggestion.
84          */
85         public void suggestionSelected(int listIndex);
86     }
87
88     /**
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.
93      */
94     public AutofillPopup(Context context, ViewAndroidDelegate viewAndroidDelegate,
95             AutofillPopupDelegate autofillCallback) {
96         super(context, null, 0, R.style.AutofillPopupWindow);
97         mContext = context;
98         mViewAndroidDelegate = viewAndroidDelegate;
99         mAutofillCallback = autofillCallback;
100
101         setOnItemClickListener(this);
102
103         mAnchorView = mViewAndroidDelegate.acquireAnchorView();
104         mAnchorView.setId(R.id.autofill_popup_window);
105         mAnchorView.setTag(this);
106
107         mViewAndroidDelegate.setAnchorViewPosition(mAnchorView, mAnchorX, mAnchorY, mAnchorWidth,
108                 mAnchorHeight);
109
110         mLayoutChangeListener = new OnLayoutChangeListener() {
111             @Override
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();
115             }
116         };
117
118         mAnchorView.addOnLayoutChangeListener(mLayoutChangeListener);
119         setAnchorView(mAnchorView);
120     }
121
122     @Override
123     public void show() {
124         // An ugly hack to keep the popup from expanding on top of the keyboard.
125         setInputMethodMode(INPUT_METHOD_NEEDED);
126         super.show();
127         getListView().setDividerHeight(0);
128     }
129
130     /**
131      * Sets the location and the size of the anchor view that the AutofillPopup will use to attach
132      * itself.
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.
137      */
138     public void setAnchorRect(float x, float y, float width, float height) {
139         mAnchorWidth = width;
140         mAnchorHeight = height;
141         mAnchorX = x;
142         mAnchorY = y;
143         if (mAnchorView != null) {
144             mViewAndroidDelegate.setAnchorViewPosition(mAnchorView, mAnchorX, mAnchorY,
145                     mAnchorWidth, mAnchorHeight);
146         }
147     }
148
149     /**
150      * Sets the Autofill suggestions to display in the popup and shows the popup.
151      * @param suggestions Autofill suggestion data.
152      */
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());
165             }
166         }
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,
171                 mAnchorHeight);
172     }
173
174     /**
175      * Overrides the default dismiss behavior to request the controller to dismiss the view.
176      */
177     @Override
178     public void dismiss() {
179         mAutofillCallback.requestHide();
180     }
181
182     /**
183      * Hides the popup and removes the anchor view from the ContainerView.
184      */
185     public void hide() {
186         super.dismiss();
187         mAnchorView.removeOnLayoutChangeListener(mLayoutChangeListener);
188         mAnchorView.setTag(null);
189         mViewAndroidDelegate.releaseAnchorView(mAnchorView);
190     }
191
192     /**
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.
196      */
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();
206         }
207
208         float maxTextWidth = 0;
209         Rect bounds = new Rect();
210         for (int i = 0; i < data.size(); ++i) {
211             bounds.setEmpty();
212             String label = data.get(i).mLabel;
213             if (!TextUtils.isEmpty(label)) {
214                 mLabelViewPaint.getTextBounds(label, 0, label.length(), bounds);
215             }
216             float labelWidth = bounds.width();
217
218             bounds.setEmpty();
219             String sublabel = data.get(i).mSublabel;
220             if (!TextUtils.isEmpty(sublabel)) {
221                 mSublabelViewPaint.getTextBounds(sublabel, 0, sublabel.length(), bounds);
222             }
223
224             float localMax = Math.max(labelWidth, bounds.width());
225             maxTextWidth = Math.max(maxTextWidth, localMax);
226         }
227         // Scale it down to make it unscaled by screen density.
228         maxTextWidth = maxTextWidth / mContext.getResources().getDisplayMetrics().density;
229         // Adding padding.
230         return maxTextWidth + TEXT_PADDING_DP;
231     }
232
233     @Override
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);
241         }
242         mAutofillCallback.suggestionSelected(listIndex);
243     }
244
245 }