| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package android.webkit; |
| |
| import android.content.Context; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.text.Editable; |
| import android.view.KeyEvent; |
| import android.view.View; |
| import android.widget.AbsoluteLayout; |
| import android.widget.AdapterView; |
| import android.widget.AdapterView.OnItemClickListener; |
| import android.widget.Filter; |
| import android.widget.Filterable; |
| import android.widget.ListAdapter; |
| import android.widget.ListPopupWindow; |
| import android.widget.PopupWindow.OnDismissListener; |
| |
| class AutoCompletePopup implements OnItemClickListener, Filter.FilterListener, |
| OnDismissListener{ |
| private static class AnchorView extends View { |
| AnchorView(Context context) { |
| super(context); |
| setFocusable(false); |
| setVisibility(INVISIBLE); |
| } |
| } |
| private static final int AUTOFILL_FORM = 100; |
| private boolean mIsAutoFillProfileSet; |
| private Handler mHandler; |
| private int mQueryId; |
| private ListPopupWindow mPopup; |
| private Filter mFilter; |
| private CharSequence mText; |
| private ListAdapter mAdapter; |
| private View mAnchor; |
| private WebViewClassic.WebViewInputConnection mInputConnection; |
| private WebViewClassic mWebView; |
| |
| public AutoCompletePopup(WebViewClassic webView, |
| WebViewClassic.WebViewInputConnection inputConnection) { |
| mInputConnection = inputConnection; |
| mWebView = webView; |
| mHandler = new Handler() { |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case AUTOFILL_FORM: |
| mWebView.autoFillForm(mQueryId); |
| break; |
| } |
| } |
| }; |
| } |
| |
| public boolean onKeyPreIme(int keyCode, KeyEvent event) { |
| if (mPopup == null) { |
| return false; |
| } |
| if (keyCode == KeyEvent.KEYCODE_BACK && mPopup.isShowing()) { |
| // special case for the back key, we do not even try to send it |
| // to the drop down list but instead, consume it immediately |
| if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { |
| KeyEvent.DispatcherState state = mAnchor.getKeyDispatcherState(); |
| if (state != null) { |
| state.startTracking(event, this); |
| } |
| return true; |
| } else if (event.getAction() == KeyEvent.ACTION_UP) { |
| KeyEvent.DispatcherState state = mAnchor.getKeyDispatcherState(); |
| if (state != null) { |
| state.handleUpEvent(event); |
| } |
| if (event.isTracking() && !event.isCanceled()) { |
| mPopup.dismiss(); |
| return true; |
| } |
| } |
| } |
| if (mPopup.isShowing()) { |
| return mPopup.onKeyPreIme(keyCode, event); |
| } |
| return false; |
| } |
| |
| public void setText(CharSequence text) { |
| mText = text; |
| if (mFilter != null) { |
| mFilter.filter(text, this); |
| } |
| } |
| |
| public void setAutoFillQueryId(int queryId) { |
| mQueryId = queryId; |
| } |
| |
| public void clearAdapter() { |
| mAdapter = null; |
| mFilter = null; |
| if (mPopup != null) { |
| mPopup.dismiss(); |
| mPopup.setAdapter(null); |
| } |
| } |
| |
| public <T extends ListAdapter & Filterable> void setAdapter(T adapter) { |
| ensurePopup(); |
| mPopup.setAdapter(adapter); |
| mAdapter = adapter; |
| if (adapter != null) { |
| mFilter = adapter.getFilter(); |
| mFilter.filter(mText, this); |
| } else { |
| mFilter = null; |
| } |
| resetRect(); |
| } |
| |
| public void resetRect() { |
| ensurePopup(); |
| int left = mWebView.contentToViewX(mWebView.mEditTextContentBounds.left); |
| int right = mWebView.contentToViewX(mWebView.mEditTextContentBounds.right); |
| int width = right - left; |
| mPopup.setWidth(width); |
| |
| int bottom = mWebView.contentToViewY(mWebView.mEditTextContentBounds.bottom); |
| int top = mWebView.contentToViewY(mWebView.mEditTextContentBounds.top); |
| int height = bottom - top; |
| |
| AbsoluteLayout.LayoutParams lp = |
| (AbsoluteLayout.LayoutParams) mAnchor.getLayoutParams(); |
| boolean needsUpdate = false; |
| if (null == lp) { |
| lp = new AbsoluteLayout.LayoutParams(width, height, left, top); |
| } else { |
| if ((lp.x != left) || (lp.y != top) || (lp.width != width) |
| || (lp.height != height)) { |
| needsUpdate = true; |
| lp.x = left; |
| lp.y = top; |
| lp.width = width; |
| lp.height = height; |
| } |
| } |
| if (needsUpdate) { |
| mAnchor.setLayoutParams(lp); |
| } |
| if (mPopup.isShowing()) { |
| mPopup.show(); // update its position |
| } |
| } |
| |
| // AdapterView.OnItemClickListener implementation |
| @Override |
| public void onItemClick(AdapterView<?> parent, View view, int position, long id) { |
| if (mPopup == null) { |
| return; |
| } |
| if (id == 0 && position == 0 && mInputConnection.getIsAutoFillable()) { |
| mText = ""; |
| pushTextToInputConnection(); |
| // Blank out the text box while we wait for WebCore to fill the form. |
| if (mIsAutoFillProfileSet) { |
| // Call a webview method to tell WebCore to autofill the form. |
| mWebView.autoFillForm(mQueryId); |
| } else { |
| // There is no autofill profile setup yet and the user has |
| // elected to try and set one up. Call through to the |
| // embedder to action that. |
| WebChromeClient webChromeClient = mWebView.getWebChromeClient(); |
| if (webChromeClient != null) { |
| webChromeClient.setupAutoFill( |
| mHandler.obtainMessage(AUTOFILL_FORM)); |
| } |
| } |
| } else { |
| Object selectedItem; |
| if (position < 0) { |
| selectedItem = mPopup.getSelectedItem(); |
| } else { |
| selectedItem = mAdapter.getItem(position); |
| } |
| if (selectedItem != null) { |
| setText(mFilter.convertResultToString(selectedItem)); |
| pushTextToInputConnection(); |
| } |
| } |
| mPopup.dismiss(); |
| } |
| |
| public void setIsAutoFillProfileSet(boolean isAutoFillProfileSet) { |
| mIsAutoFillProfileSet = isAutoFillProfileSet; |
| } |
| |
| private void pushTextToInputConnection() { |
| Editable oldText = mInputConnection.getEditable(); |
| mInputConnection.setSelection(0, oldText.length()); |
| mInputConnection.replaceSelection(mText); |
| mInputConnection.setSelection(mText.length(), mText.length()); |
| } |
| |
| @Override |
| public void onFilterComplete(int count) { |
| ensurePopup(); |
| boolean showDropDown = (count > 0) && |
| (mInputConnection.getIsAutoFillable() || mText.length() > 0); |
| if (showDropDown) { |
| if (!mPopup.isShowing()) { |
| // Make sure the list does not obscure the IME when shown for the first time. |
| mPopup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NEEDED); |
| } |
| mPopup.show(); |
| mPopup.getListView().setOverScrollMode(View.OVER_SCROLL_ALWAYS); |
| } else { |
| mPopup.dismiss(); |
| } |
| } |
| |
| @Override |
| public void onDismiss() { |
| mWebView.getWebView().removeView(mAnchor); |
| } |
| |
| private void ensurePopup() { |
| if (mPopup == null) { |
| mPopup = new ListPopupWindow(mWebView.getContext()); |
| mAnchor = new AnchorView(mWebView.getContext()); |
| mWebView.getWebView().addView(mAnchor); |
| mPopup.setOnItemClickListener(this); |
| mPopup.setAnchorView(mAnchor); |
| mPopup.setPromptPosition(ListPopupWindow.POSITION_PROMPT_BELOW); |
| } else if (mWebView.getWebView().indexOfChild(mAnchor) < 0) { |
| mWebView.getWebView().addView(mAnchor); |
| } |
| } |
| } |
| |