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 */ 16 package android.webkit; 17 18 import android.content.Context; 19 import android.os.Handler; 20 import android.os.Message; 21 import android.text.Editable; 22 import android.view.KeyEvent; 23 import android.view.View; 24 import android.widget.AbsoluteLayout; 25 import android.widget.AdapterView; 26 import android.widget.AdapterView.OnItemClickListener; 27 import android.widget.Filter; 28 import android.widget.Filterable; 29 import android.widget.ListAdapter; 30 import android.widget.ListPopupWindow; 31 import android.widget.PopupWindow.OnDismissListener; 32 33 class AutoCompletePopup implements OnItemClickListener, Filter.FilterListener, 34 OnDismissListener{ 35 private static class AnchorView extends View { AnchorView(Context context)36 AnchorView(Context context) { 37 super(context); 38 setFocusable(false); 39 setVisibility(INVISIBLE); 40 } 41 } 42 private static final int AUTOFILL_FORM = 100; 43 private boolean mIsAutoFillProfileSet; 44 private Handler mHandler; 45 private int mQueryId; 46 private ListPopupWindow mPopup; 47 private Filter mFilter; 48 private CharSequence mText; 49 private ListAdapter mAdapter; 50 private View mAnchor; 51 private WebViewClassic.WebViewInputConnection mInputConnection; 52 private WebViewClassic mWebView; 53 AutoCompletePopup(WebViewClassic webView, WebViewClassic.WebViewInputConnection inputConnection)54 public AutoCompletePopup(WebViewClassic webView, 55 WebViewClassic.WebViewInputConnection inputConnection) { 56 mInputConnection = inputConnection; 57 mWebView = webView; 58 mHandler = new Handler() { 59 @Override 60 public void handleMessage(Message msg) { 61 switch (msg.what) { 62 case AUTOFILL_FORM: 63 mWebView.autoFillForm(mQueryId); 64 break; 65 } 66 } 67 }; 68 } 69 onKeyPreIme(int keyCode, KeyEvent event)70 public boolean onKeyPreIme(int keyCode, KeyEvent event) { 71 if (mPopup == null) { 72 return false; 73 } 74 if (keyCode == KeyEvent.KEYCODE_BACK && mPopup.isShowing()) { 75 // special case for the back key, we do not even try to send it 76 // to the drop down list but instead, consume it immediately 77 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 78 KeyEvent.DispatcherState state = mAnchor.getKeyDispatcherState(); 79 if (state != null) { 80 state.startTracking(event, this); 81 } 82 return true; 83 } else if (event.getAction() == KeyEvent.ACTION_UP) { 84 KeyEvent.DispatcherState state = mAnchor.getKeyDispatcherState(); 85 if (state != null) { 86 state.handleUpEvent(event); 87 } 88 if (event.isTracking() && !event.isCanceled()) { 89 mPopup.dismiss(); 90 return true; 91 } 92 } 93 } 94 if (mPopup.isShowing()) { 95 return mPopup.onKeyPreIme(keyCode, event); 96 } 97 return false; 98 } 99 setText(CharSequence text)100 public void setText(CharSequence text) { 101 mText = text; 102 if (mFilter != null) { 103 mFilter.filter(text, this); 104 } 105 } 106 setAutoFillQueryId(int queryId)107 public void setAutoFillQueryId(int queryId) { 108 mQueryId = queryId; 109 } 110 clearAdapter()111 public void clearAdapter() { 112 mAdapter = null; 113 mFilter = null; 114 if (mPopup != null) { 115 mPopup.dismiss(); 116 mPopup.setAdapter(null); 117 } 118 } 119 setAdapter(T adapter)120 public <T extends ListAdapter & Filterable> void setAdapter(T adapter) { 121 ensurePopup(); 122 mPopup.setAdapter(adapter); 123 mAdapter = adapter; 124 if (adapter != null) { 125 mFilter = adapter.getFilter(); 126 mFilter.filter(mText, this); 127 } else { 128 mFilter = null; 129 } 130 resetRect(); 131 } 132 resetRect()133 public void resetRect() { 134 ensurePopup(); 135 int left = mWebView.contentToViewX(mWebView.mEditTextContentBounds.left); 136 int right = mWebView.contentToViewX(mWebView.mEditTextContentBounds.right); 137 int width = right - left; 138 mPopup.setWidth(width); 139 140 int bottom = mWebView.contentToViewY(mWebView.mEditTextContentBounds.bottom); 141 int top = mWebView.contentToViewY(mWebView.mEditTextContentBounds.top); 142 int height = bottom - top; 143 144 AbsoluteLayout.LayoutParams lp = 145 (AbsoluteLayout.LayoutParams) mAnchor.getLayoutParams(); 146 boolean needsUpdate = false; 147 if (null == lp) { 148 lp = new AbsoluteLayout.LayoutParams(width, height, left, top); 149 } else { 150 if ((lp.x != left) || (lp.y != top) || (lp.width != width) 151 || (lp.height != height)) { 152 needsUpdate = true; 153 lp.x = left; 154 lp.y = top; 155 lp.width = width; 156 lp.height = height; 157 } 158 } 159 if (needsUpdate) { 160 mAnchor.setLayoutParams(lp); 161 } 162 if (mPopup.isShowing()) { 163 mPopup.show(); // update its position 164 } 165 } 166 167 // AdapterView.OnItemClickListener implementation 168 @Override onItemClick(AdapterView<?> parent, View view, int position, long id)169 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 170 if (mPopup == null) { 171 return; 172 } 173 if (id == 0 && position == 0 && mInputConnection.getIsAutoFillable()) { 174 mText = ""; 175 pushTextToInputConnection(); 176 // Blank out the text box while we wait for WebCore to fill the form. 177 if (mIsAutoFillProfileSet) { 178 // Call a webview method to tell WebCore to autofill the form. 179 mWebView.autoFillForm(mQueryId); 180 } else { 181 // There is no autofill profile setup yet and the user has 182 // elected to try and set one up. Call through to the 183 // embedder to action that. 184 WebChromeClient webChromeClient = mWebView.getWebChromeClient(); 185 if (webChromeClient != null) { 186 webChromeClient.setupAutoFill( 187 mHandler.obtainMessage(AUTOFILL_FORM)); 188 } 189 } 190 } else { 191 Object selectedItem; 192 if (position < 0) { 193 selectedItem = mPopup.getSelectedItem(); 194 } else { 195 selectedItem = mAdapter.getItem(position); 196 } 197 if (selectedItem != null) { 198 setText(mFilter.convertResultToString(selectedItem)); 199 pushTextToInputConnection(); 200 } 201 } 202 mPopup.dismiss(); 203 } 204 setIsAutoFillProfileSet(boolean isAutoFillProfileSet)205 public void setIsAutoFillProfileSet(boolean isAutoFillProfileSet) { 206 mIsAutoFillProfileSet = isAutoFillProfileSet; 207 } 208 pushTextToInputConnection()209 private void pushTextToInputConnection() { 210 Editable oldText = mInputConnection.getEditable(); 211 mInputConnection.setSelection(0, oldText.length()); 212 mInputConnection.replaceSelection(mText); 213 mInputConnection.setSelection(mText.length(), mText.length()); 214 } 215 216 @Override onFilterComplete(int count)217 public void onFilterComplete(int count) { 218 ensurePopup(); 219 boolean showDropDown = (count > 0) && 220 (mInputConnection.getIsAutoFillable() || mText.length() > 0); 221 if (showDropDown) { 222 if (!mPopup.isShowing()) { 223 // Make sure the list does not obscure the IME when shown for the first time. 224 mPopup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NEEDED); 225 } 226 mPopup.show(); 227 mPopup.getListView().setOverScrollMode(View.OVER_SCROLL_ALWAYS); 228 } else { 229 mPopup.dismiss(); 230 } 231 } 232 233 @Override onDismiss()234 public void onDismiss() { 235 mWebView.getWebView().removeView(mAnchor); 236 } 237 ensurePopup()238 private void ensurePopup() { 239 if (mPopup == null) { 240 mPopup = new ListPopupWindow(mWebView.getContext()); 241 mAnchor = new AnchorView(mWebView.getContext()); 242 mWebView.getWebView().addView(mAnchor); 243 mPopup.setOnItemClickListener(this); 244 mPopup.setAnchorView(mAnchor); 245 mPopup.setPromptPosition(ListPopupWindow.POSITION_PROMPT_BELOW); 246 } else if (mWebView.getWebView().indexOfChild(mAnchor) < 0) { 247 mWebView.getWebView().addView(mAnchor); 248 } 249 } 250 } 251 252