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.annotation.SuppressLint; 8 import android.content.Context; 9 import android.util.Log; 10 import android.view.View; 11 import android.widget.AdapterView; 12 import android.widget.ListPopupWindow; 13 import android.widget.PopupWindow; 14 15 import org.chromium.base.ApiCompatibilityUtils; 16 import org.chromium.ui.DropdownAdapter; 17 import org.chromium.ui.DropdownItem; 18 import org.chromium.ui.DropdownPopupWindow; 19 import org.chromium.ui.base.ViewAndroidDelegate; 20 21 import java.lang.reflect.Method; 22 import java.util.ArrayList; 23 import java.util.Arrays; 24 import java.util.HashSet; 25 import java.util.List; 26 27 /** 28 * The Autofill suggestion popup that lists relevant suggestions. 29 */ 30 public class AutofillPopup extends DropdownPopupWindow implements AdapterView.OnItemClickListener, 31 PopupWindow.OnDismissListener { 32 33 /** 34 * The constant used to specify a separator in a list of Autofill suggestions. 35 * Has to be kept in sync with enum in WebAutofillClient.h 36 */ 37 private static final int ITEM_ID_SEPARATOR_ENTRY = -3; 38 39 private final Context mContext; 40 private final AutofillPopupDelegate mAutofillCallback; 41 private List<AutofillSuggestion> mSuggestions; 42 43 44 /** 45 * An interface to handle the touch interaction with an AutofillPopup object. 46 */ 47 public interface AutofillPopupDelegate { 48 /** 49 * Informs the controller the AutofillPopup was hidden. 50 */ dismissed()51 public void dismissed(); 52 53 /** 54 * Handles the selection of an Autofill suggestion from an AutofillPopup. 55 * @param listIndex The index of the selected Autofill suggestion. 56 */ suggestionSelected(int listIndex)57 public void suggestionSelected(int listIndex); 58 } 59 60 /** 61 * Creates an AutofillWindow with specified parameters. 62 * @param context Application context. 63 * @param viewAndroidDelegate View delegate used to add and remove views. 64 * @param autofillCallback A object that handles the calls to the native AutofillPopupView. 65 */ AutofillPopup(Context context, ViewAndroidDelegate viewAndroidDelegate, AutofillPopupDelegate autofillCallback)66 public AutofillPopup(Context context, ViewAndroidDelegate viewAndroidDelegate, 67 AutofillPopupDelegate autofillCallback) { 68 super(context, viewAndroidDelegate); 69 mContext = context; 70 mAutofillCallback = autofillCallback; 71 72 setOnItemClickListener(this); 73 setOnDismissListener(this); 74 } 75 76 /** 77 * Filters the Autofill suggestions to the ones that we support and shows the popup. 78 * @param suggestions Autofill suggestion data. 79 */ 80 @SuppressLint("InlinedApi") filterAndShow(AutofillSuggestion[] suggestions, boolean isRtl)81 public void filterAndShow(AutofillSuggestion[] suggestions, boolean isRtl) { 82 mSuggestions = new ArrayList<AutofillSuggestion>(Arrays.asList(suggestions)); 83 // Remove the AutofillSuggestions with IDs that are not supported by Android 84 ArrayList<DropdownItem> cleanedData = new ArrayList<DropdownItem>(); 85 HashSet<Integer> separators = new HashSet<Integer>(); 86 for (int i = 0; i < suggestions.length; i++) { 87 int itemId = suggestions[i].getSuggestionId(); 88 if (itemId == ITEM_ID_SEPARATOR_ENTRY) { 89 separators.add(cleanedData.size()); 90 } else { 91 cleanedData.add(suggestions[i]); 92 } 93 } 94 95 setAdapter(new DropdownAdapter(mContext, cleanedData, separators)); 96 show(); 97 ApiCompatibilityUtils.setLayoutDirection(getListView(), 98 isRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR); 99 100 // HACK: The ListPopupWindow's mPopup automatically dismisses on an outside tap. There's 101 // no way to override it or prevent it, except reaching into ListPopupWindow's hidden 102 // API. This allows the C++ controller to completely control showing/hiding the popup. 103 // See http://crbug.com/400601 104 try { 105 Method setForceIgnoreOutsideTouch = ListPopupWindow.class.getMethod( 106 "setForceIgnoreOutsideTouch", new Class[] { boolean.class }); 107 setForceIgnoreOutsideTouch.invoke(this, new Object[] { true }); 108 } catch (Exception e) { 109 Log.e("AutofillPopup", 110 "ListPopupWindow.setForceIgnoreOutsideTouch not found", 111 e); 112 } 113 } 114 115 /** 116 * Hides the popup. 117 */ hide()118 public void hide() { 119 dismiss(); 120 } 121 122 @Override onItemClick(AdapterView<?> parent, View view, int position, long id)123 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 124 DropdownAdapter adapter = (DropdownAdapter) parent.getAdapter(); 125 int listIndex = mSuggestions.indexOf(adapter.getItem(position)); 126 assert listIndex > -1; 127 mAutofillCallback.suggestionSelected(listIndex); 128 } 129 130 @Override onDismiss()131 public void onDismiss() { 132 mAutofillCallback.dismissed(); 133 } 134 } 135