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