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