AutoCompletePopup.java revision 5d84368a7577c5d53ce885cd2d095cc5ba40d924
1/* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16package android.webkit; 17 18import android.content.Context; 19import android.os.Handler; 20import android.os.Message; 21import android.text.Editable; 22import android.view.KeyEvent; 23import android.view.View; 24import android.widget.AbsoluteLayout; 25import android.widget.AdapterView; 26import android.widget.AdapterView.OnItemClickListener; 27import android.widget.Filter; 28import android.widget.Filterable; 29import android.widget.ListAdapter; 30import android.widget.ListPopupWindow; 31 32class AutoCompletePopup implements OnItemClickListener, Filter.FilterListener { 33 private static class AnchorView extends View { 34 AnchorView(Context context) { 35 super(context); 36 setFocusable(false); 37 } 38 } 39 private static final int AUTOFILL_FORM = 100; 40 private boolean mIsAutoFillProfileSet; 41 private Handler mHandler; 42 private int mQueryId; 43 private ListPopupWindow mPopup; 44 private Filter mFilter; 45 private CharSequence mText; 46 private ListAdapter mAdapter; 47 private View mAnchor; 48 private WebViewClassic.WebViewInputConnection mInputConnection; 49 private WebViewClassic mWebView; 50 51 public AutoCompletePopup(Context context, 52 WebViewClassic webView, 53 WebViewClassic.WebViewInputConnection inputConnection) { 54 mInputConnection = inputConnection; 55 mWebView = webView; 56 mPopup = new ListPopupWindow(context); 57 mAnchor = new AnchorView(context); 58 mWebView.getWebView().addView(mAnchor); 59 mPopup.setOnItemClickListener(this); 60 mPopup.setAnchorView(mAnchor); 61 mPopup.setPromptPosition(ListPopupWindow.POSITION_PROMPT_BELOW); 62 mHandler = new Handler() { 63 @Override 64 public void handleMessage(Message msg) { 65 switch (msg.what) { 66 case AUTOFILL_FORM: 67 mWebView.autoFillForm(mQueryId); 68 break; 69 } 70 } 71 }; 72 } 73 74 public boolean onKeyPreIme(int keyCode, KeyEvent event) { 75 if (keyCode == KeyEvent.KEYCODE_BACK && mPopup.isShowing()) { 76 // special case for the back key, we do not even try to send it 77 // to the drop down list but instead, consume it immediately 78 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 79 KeyEvent.DispatcherState state = mAnchor.getKeyDispatcherState(); 80 if (state != null) { 81 state.startTracking(event, this); 82 } 83 return true; 84 } else if (event.getAction() == KeyEvent.ACTION_UP) { 85 KeyEvent.DispatcherState state = mAnchor.getKeyDispatcherState(); 86 if (state != null) { 87 state.handleUpEvent(event); 88 } 89 if (event.isTracking() && !event.isCanceled()) { 90 mPopup.dismiss(); 91 return true; 92 } 93 } 94 } 95 if (mPopup.isShowing()) { 96 return mPopup.onKeyPreIme(keyCode, event); 97 } 98 return false; 99 } 100 101 public void setText(CharSequence text) { 102 mText = text; 103 if (mFilter != null) { 104 mFilter.filter(text, this); 105 } 106 } 107 108 public void setAutoFillQueryId(int queryId) { 109 mQueryId = queryId; 110 } 111 112 public void clearAdapter() { 113 mAdapter = null; 114 mFilter = null; 115 mPopup.dismiss(); 116 mPopup.setAdapter(null); 117 } 118 119 public <T extends ListAdapter & Filterable> void setAdapter(T adapter) { 120 mPopup.setAdapter(adapter); 121 mAdapter = adapter; 122 if (adapter != null) { 123 mFilter = adapter.getFilter(); 124 mFilter.filter(mText, this); 125 } else { 126 mFilter = null; 127 } 128 resetRect(); 129 } 130 131 public void resetRect() { 132 int left = mWebView.contentToViewX(mWebView.mEditTextContentBounds.left); 133 int right = mWebView.contentToViewX(mWebView.mEditTextContentBounds.right); 134 int width = right - left; 135 mPopup.setWidth(width); 136 137 int bottom = mWebView.contentToViewY(mWebView.mEditTextContentBounds.bottom); 138 int top = mWebView.contentToViewY(mWebView.mEditTextContentBounds.top); 139 int height = bottom - top; 140 141 AbsoluteLayout.LayoutParams lp = 142 (AbsoluteLayout.LayoutParams) mAnchor.getLayoutParams(); 143 boolean needsUpdate = false; 144 if (null == lp) { 145 lp = new AbsoluteLayout.LayoutParams(width, height, left, top); 146 } else { 147 if ((lp.x != left) || (lp.y != top) || (lp.width != width) 148 || (lp.height != height)) { 149 needsUpdate = true; 150 lp.x = left; 151 lp.y = top; 152 lp.width = width; 153 lp.height = height; 154 } 155 } 156 if (needsUpdate) { 157 mAnchor.setLayoutParams(lp); 158 } 159 if (mPopup.isShowing()) { 160 mPopup.show(); // update its position 161 } 162 } 163 164 // AdapterView.OnItemClickListener implementation 165 @Override 166 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 167 if (id == 0 && position == 0 && mInputConnection.getIsAutoFillable()) { 168 mText = ""; 169 pushTextToInputConnection(); 170 // Blank out the text box while we wait for WebCore to fill the form. 171 if (mIsAutoFillProfileSet) { 172 // Call a webview method to tell WebCore to autofill the form. 173 mWebView.autoFillForm(mQueryId); 174 } else { 175 // There is no autofill profile setup yet and the user has 176 // elected to try and set one up. Call through to the 177 // embedder to action that. 178 mWebView.getWebChromeClient().setupAutoFill( 179 mHandler.obtainMessage(AUTOFILL_FORM)); 180 } 181 } else { 182 Object selectedItem; 183 if (position < 0) { 184 selectedItem = mPopup.getSelectedItem(); 185 } else { 186 selectedItem = mAdapter.getItem(position); 187 } 188 if (selectedItem != null) { 189 setText(mFilter.convertResultToString(selectedItem)); 190 pushTextToInputConnection(); 191 } 192 } 193 mPopup.dismiss(); 194 } 195 196 public void setIsAutoFillProfileSet(boolean isAutoFillProfileSet) { 197 mIsAutoFillProfileSet = isAutoFillProfileSet; 198 } 199 200 private void pushTextToInputConnection() { 201 Editable oldText = mInputConnection.getEditable(); 202 mInputConnection.setSelection(0, oldText.length()); 203 mInputConnection.replaceSelection(mText); 204 mInputConnection.setSelection(mText.length(), mText.length()); 205 } 206 207 @Override 208 public void onFilterComplete(int count) { 209 boolean showDropDown = (count > 0) && 210 (mInputConnection.getIsAutoFillable() || mText.length() > 0); 211 if (showDropDown) { 212 if (!mPopup.isShowing()) { 213 // Make sure the list does not obscure the IME when shown for the first time. 214 mPopup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NEEDED); 215 } 216 mPopup.show(); 217 mPopup.getListView().setOverScrollMode(View.OVER_SCROLL_ALWAYS); 218 } else { 219 mPopup.dismiss(); 220 } 221 } 222} 223 224