AutofillPopup.java revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
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 org.chromium.ui.R; 19import org.chromium.ui.base.ViewAndroidDelegate; 20 21import java.util.ArrayList; 22import java.util.Arrays; 23import java.util.HashSet; 24import java.util.List; 25 26/** 27 * The Autofill suggestion popup that lists relevant suggestions. 28 */ 29public 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 to handle the touch interaction with an AutofillPopup object. 60 */ 61 public interface AutofillPopupDelegate { 62 /** 63 * Requests the controller to hide AutofillPopup. 64 */ 65 public void requestHide(); 66 67 /** 68 * Handles the selection of an Autofill suggestion from an AutofillPopup. 69 * @param listIndex The index of the selected Autofill suggestion. 70 */ 71 public void suggestionSelected(int listIndex); 72 } 73 74 /** 75 * Creates an AutofillWindow with specified parameters. 76 * @param context Application context. 77 * @param viewAndroidDelegate View delegate used to add and remove views. 78 * @param autofillCallback A object that handles the calls to the native AutofillPopupView. 79 */ 80 public AutofillPopup(Context context, ViewAndroidDelegate viewAndroidDelegate, 81 AutofillPopupDelegate autofillCallback) { 82 super(context, null, 0, R.style.AutofillPopupWindow); 83 mContext = context; 84 mViewAndroidDelegate = viewAndroidDelegate; 85 mAutofillCallback = autofillCallback; 86 87 setOnItemClickListener(this); 88 89 mAnchorView = mViewAndroidDelegate.acquireAnchorView(); 90 mAnchorView.setId(R.id.autofill_popup_window); 91 mAnchorView.setTag(this); 92 93 mViewAndroidDelegate.setAnchorViewPosition(mAnchorView, mAnchorX, mAnchorY, mAnchorWidth, 94 mAnchorHeight); 95 96 mLayoutChangeListener = new OnLayoutChangeListener() { 97 @Override 98 public void onLayoutChange(View v, int left, int top, int right, int bottom, 99 int oldLeft, int oldTop, int oldRight, int oldBottom) { 100 if (v == mAnchorView) AutofillPopup.this.show(); 101 } 102 }; 103 104 mAnchorView.addOnLayoutChangeListener(mLayoutChangeListener); 105 setAnchorView(mAnchorView); 106 } 107 108 @Override 109 public void show() { 110 // An ugly hack to keep the popup from expanding on top of the keyboard. 111 setInputMethodMode(INPUT_METHOD_NEEDED); 112 super.show(); 113 getListView().setDividerHeight(0); 114 } 115 116 /** 117 * Sets the location and the size of the anchor view that the AutofillPopup will use to attach 118 * itself. 119 * @param x X coordinate of the top left corner of the anchor view. 120 * @param y Y coordinate of the top left corner of the anchor view. 121 * @param width The width of the anchor view. 122 * @param height The height of the anchor view. 123 */ 124 public void setAnchorRect(float x, float y, float width, float height) { 125 mAnchorWidth = width; 126 mAnchorHeight = height; 127 mAnchorX = x; 128 mAnchorY = y; 129 if (mAnchorView != null) { 130 mViewAndroidDelegate.setAnchorViewPosition(mAnchorView, mAnchorX, mAnchorY, 131 mAnchorWidth, mAnchorHeight); 132 } 133 } 134 135 /** 136 * Sets the Autofill suggestions to display in the popup and shows the popup. 137 * @param suggestions Autofill suggestion data. 138 */ 139 public void show(AutofillSuggestion[] suggestions) { 140 mSuggestions = new ArrayList<AutofillSuggestion>(Arrays.asList(suggestions)); 141 // Remove the AutofillSuggestions with IDs that are not supported by Android 142 ArrayList<AutofillSuggestion> cleanedData = new ArrayList<AutofillSuggestion>(); 143 HashSet<Integer> separators = new HashSet<Integer>(); 144 for (int i = 0; i < suggestions.length; i++) { 145 int itemId = suggestions[i].mUniqueId; 146 if (itemId > 0 || itemId == ITEM_ID_AUTOCOMPLETE_ENTRY || 147 itemId == ITEM_ID_PASSWORD_ENTRY || itemId == ITEM_ID_DATA_LIST_ENTRY) { 148 cleanedData.add(suggestions[i]); 149 } else if (itemId == ITEM_ID_SEPARATOR_ENTRY) { 150 separators.add(cleanedData.size()); 151 } 152 } 153 setAdapter(new AutofillListAdapter(mContext, cleanedData, separators)); 154 // Once the mAnchorRect is resized and placed correctly, it will show the Autofill popup. 155 mAnchorWidth = Math.max(getDesiredWidth(cleanedData), mAnchorWidth); 156 mViewAndroidDelegate.setAnchorViewPosition(mAnchorView, mAnchorX, mAnchorY, mAnchorWidth, 157 mAnchorHeight); 158 } 159 160 /** 161 * Overrides the default dismiss behavior to request the controller to dismiss the view. 162 */ 163 @Override 164 public void dismiss() { 165 mAutofillCallback.requestHide(); 166 } 167 168 /** 169 * Hides the popup and removes the anchor view from the ContainerView. 170 */ 171 public void hide() { 172 super.dismiss(); 173 mAnchorView.removeOnLayoutChangeListener(mLayoutChangeListener); 174 mAnchorView.setTag(null); 175 mViewAndroidDelegate.releaseAnchorView(mAnchorView); 176 } 177 178 /** 179 * Get desired popup window width by calculating the maximum text length from Autofill data. 180 * @param data Autofill suggestion data. 181 * @return The popup window width in DIP. 182 */ 183 private float getDesiredWidth(ArrayList<AutofillSuggestion> data) { 184 if (mLabelViewPaint == null || mSublabelViewPaint == null) { 185 LayoutInflater inflater = 186 (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 187 View layout = inflater.inflate(R.layout.autofill_text, null); 188 TextView labelView = (TextView) layout.findViewById(R.id.autofill_label); 189 mLabelViewPaint = labelView.getPaint(); 190 TextView sublabelView = (TextView) layout.findViewById(R.id.autofill_sublabel); 191 mSublabelViewPaint = sublabelView.getPaint(); 192 } 193 194 float maxTextWidth = 0; 195 Rect bounds = new Rect(); 196 for (int i = 0; i < data.size(); ++i) { 197 bounds.setEmpty(); 198 String label = data.get(i).mLabel; 199 if (!TextUtils.isEmpty(label)) { 200 mLabelViewPaint.getTextBounds(label, 0, label.length(), bounds); 201 } 202 float labelWidth = bounds.width(); 203 204 bounds.setEmpty(); 205 String sublabel = data.get(i).mSublabel; 206 if (!TextUtils.isEmpty(sublabel)) { 207 mSublabelViewPaint.getTextBounds(sublabel, 0, sublabel.length(), bounds); 208 } 209 210 float localMax = Math.max(labelWidth, bounds.width()); 211 maxTextWidth = Math.max(maxTextWidth, localMax); 212 } 213 // Scale it down to make it unscaled by screen density. 214 maxTextWidth = maxTextWidth / mContext.getResources().getDisplayMetrics().density; 215 // Adding padding. 216 return maxTextWidth + TEXT_PADDING_DP; 217 } 218 219 @Override 220 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 221 AutofillListAdapter adapter = (AutofillListAdapter) parent.getAdapter(); 222 int listIndex = mSuggestions.indexOf(adapter.getItem(position)); 223 assert listIndex > -1; 224 mAutofillCallback.suggestionSelected(listIndex); 225 } 226 227} 228