Home | History | Annotate | Download | only in webkit
      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 {
     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 
     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 
     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 
    100     public void setText(CharSequence text) {
    101         mText = text;
    102         if (mFilter != null) {
    103             mFilter.filter(text, this);
    104         }
    105     }
    106 
    107     public void setAutoFillQueryId(int queryId) {
    108         mQueryId = queryId;
    109     }
    110 
    111     public void clearAdapter() {
    112         mAdapter = null;
    113         mFilter = null;
    114         if (mPopup != null) {
    115             mPopup.dismiss();
    116             mPopup.setAdapter(null);
    117         }
    118     }
    119 
    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 
    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
    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 
    205     public void setIsAutoFillProfileSet(boolean isAutoFillProfileSet) {
    206         mIsAutoFillProfileSet = isAutoFillProfileSet;
    207     }
    208 
    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
    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
    234     public void onDismiss() {
    235         mWebView.getWebView().removeView(mAnchor);
    236     }
    237 
    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