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