Home | History | Annotate | Download | only in webkit
      1 /*
      2  * Copyright (C) 2010 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 
     17 package android.webkit;
     18 
     19 import android.annotation.SystemApi;
     20 import android.content.Context;
     21 import android.content.res.Resources;
     22 import android.graphics.Point;
     23 import android.graphics.Rect;
     24 import android.text.Editable;
     25 import android.text.Selection;
     26 import android.text.Spannable;
     27 import android.text.TextWatcher;
     28 import android.view.ActionMode;
     29 import android.view.LayoutInflater;
     30 import android.view.Menu;
     31 import android.view.MenuItem;
     32 import android.view.View;
     33 import android.view.inputmethod.InputMethodManager;
     34 import android.widget.EditText;
     35 import android.widget.TextView;
     36 
     37 /**
     38  * @hide
     39  */
     40 @SystemApi
     41 public class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
     42         View.OnClickListener, WebView.FindListener {
     43     private View mCustomView;
     44     private EditText mEditText;
     45     private TextView mMatches;
     46     private WebView mWebView;
     47     private InputMethodManager mInput;
     48     private Resources mResources;
     49     private boolean mMatchesFound;
     50     private int mNumberOfMatches;
     51     private int mActiveMatchIndex;
     52     private ActionMode mActionMode;
     53 
     54     public FindActionModeCallback(Context context) {
     55         mCustomView = LayoutInflater.from(context).inflate(
     56                 com.android.internal.R.layout.webview_find, null);
     57         mEditText = (EditText) mCustomView.findViewById(
     58                 com.android.internal.R.id.edit);
     59         mEditText.setCustomSelectionActionModeCallback(new NoAction());
     60         mEditText.setOnClickListener(this);
     61         setText("");
     62         mMatches = (TextView) mCustomView.findViewById(
     63                 com.android.internal.R.id.matches);
     64         mInput = context.getSystemService(InputMethodManager.class);
     65         mResources = context.getResources();
     66     }
     67 
     68     public void finish() {
     69         mActionMode.finish();
     70     }
     71 
     72     /*
     73      * Place text in the text field so it can be searched for.  Need to press
     74      * the find next or find previous button to find all of the matches.
     75      */
     76     public void setText(String text) {
     77         mEditText.setText(text);
     78         Spannable span = (Spannable) mEditText.getText();
     79         int length = span.length();
     80         // Ideally, we would like to set the selection to the whole field,
     81         // but this brings up the Text selection CAB, which dismisses this
     82         // one.
     83         Selection.setSelection(span, length, length);
     84         // Necessary each time we set the text, so that this will watch
     85         // changes to it.
     86         span.setSpan(this, 0, length, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
     87         mMatchesFound = false;
     88     }
     89 
     90     /*
     91      * Set the WebView to search.  Must be non null.
     92      */
     93     public void setWebView(WebView webView) {
     94         if (null == webView) {
     95             throw new AssertionError("WebView supplied to "
     96                     + "FindActionModeCallback cannot be null");
     97         }
     98         mWebView = webView;
     99         mWebView.setFindDialogFindListener(this);
    100     }
    101 
    102     @Override
    103     public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
    104             boolean isDoneCounting) {
    105         if (isDoneCounting) {
    106             updateMatchCount(activeMatchOrdinal, numberOfMatches, numberOfMatches == 0);
    107         }
    108     }
    109 
    110     /*
    111      * Move the highlight to the next match.
    112      * @param next If true, find the next match further down in the document.
    113      *             If false, find the previous match, up in the document.
    114      */
    115     private void findNext(boolean next) {
    116         if (mWebView == null) {
    117             throw new AssertionError(
    118                     "No WebView for FindActionModeCallback::findNext");
    119         }
    120         if (!mMatchesFound) {
    121             findAll();
    122             return;
    123         }
    124         if (0 == mNumberOfMatches) {
    125             // There are no matches, so moving to the next match will not do
    126             // anything.
    127             return;
    128         }
    129         mWebView.findNext(next);
    130         updateMatchesString();
    131     }
    132 
    133     /*
    134      * Highlight all the instances of the string from mEditText in mWebView.
    135      */
    136     public void findAll() {
    137         if (mWebView == null) {
    138             throw new AssertionError(
    139                     "No WebView for FindActionModeCallback::findAll");
    140         }
    141         CharSequence find = mEditText.getText();
    142         if (0 == find.length()) {
    143             mWebView.clearMatches();
    144             mMatches.setVisibility(View.GONE);
    145             mMatchesFound = false;
    146             mWebView.findAll(null);
    147         } else {
    148             mMatchesFound = true;
    149             mMatches.setVisibility(View.INVISIBLE);
    150             mNumberOfMatches = 0;
    151             mWebView.findAllAsync(find.toString());
    152         }
    153     }
    154 
    155     public void showSoftInput() {
    156         if (mEditText.requestFocus()) {
    157             mInput.showSoftInput(mEditText, 0);
    158         }
    159     }
    160 
    161     public void updateMatchCount(int matchIndex, int matchCount, boolean isEmptyFind) {
    162         if (!isEmptyFind) {
    163             mNumberOfMatches = matchCount;
    164             mActiveMatchIndex = matchIndex;
    165             updateMatchesString();
    166         } else {
    167             mMatches.setVisibility(View.GONE);
    168             mNumberOfMatches = 0;
    169         }
    170     }
    171 
    172     /*
    173      * Update the string which tells the user how many matches were found, and
    174      * which match is currently highlighted.
    175      */
    176     private void updateMatchesString() {
    177         if (mNumberOfMatches == 0) {
    178             mMatches.setText(com.android.internal.R.string.no_matches);
    179         } else {
    180             mMatches.setText(mResources.getQuantityString(
    181                 com.android.internal.R.plurals.matches_found, mNumberOfMatches,
    182                 mActiveMatchIndex + 1, mNumberOfMatches));
    183         }
    184         mMatches.setVisibility(View.VISIBLE);
    185     }
    186 
    187     // OnClickListener implementation
    188 
    189     @Override
    190     public void onClick(View v) {
    191         findNext(true);
    192     }
    193 
    194     // ActionMode.Callback implementation
    195 
    196     @Override
    197     public boolean onCreateActionMode(ActionMode mode, Menu menu) {
    198         if (!mode.isUiFocusable()) {
    199             // If the action mode we're running in is not focusable the user
    200             // will not be able to type into the find on page field. This
    201             // should only come up when we're running in a dialog which is
    202             // already less than ideal; disable the option for now.
    203             return false;
    204         }
    205 
    206         mode.setCustomView(mCustomView);
    207         mode.getMenuInflater().inflate(com.android.internal.R.menu.webview_find,
    208                 menu);
    209         mActionMode = mode;
    210         Editable edit = mEditText.getText();
    211         Selection.setSelection(edit, edit.length());
    212         mMatches.setVisibility(View.GONE);
    213         mMatchesFound = false;
    214         mMatches.setText("0");
    215         mEditText.requestFocus();
    216         return true;
    217     }
    218 
    219     @Override
    220     public void onDestroyActionMode(ActionMode mode) {
    221         mActionMode = null;
    222         mWebView.notifyFindDialogDismissed();
    223         mWebView.setFindDialogFindListener(null);
    224         mInput.hideSoftInputFromWindow(mWebView.getWindowToken(), 0);
    225     }
    226 
    227     @Override
    228     public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
    229         return false;
    230     }
    231 
    232     @Override
    233     public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
    234         if (mWebView == null) {
    235             throw new AssertionError(
    236                     "No WebView for FindActionModeCallback::onActionItemClicked");
    237         }
    238         mInput.hideSoftInputFromWindow(mWebView.getWindowToken(), 0);
    239         switch(item.getItemId()) {
    240             case com.android.internal.R.id.find_prev:
    241                 findNext(false);
    242                 break;
    243             case com.android.internal.R.id.find_next:
    244                 findNext(true);
    245                 break;
    246             default:
    247                 return false;
    248         }
    249         return true;
    250     }
    251 
    252     // TextWatcher implementation
    253 
    254     @Override
    255     public void beforeTextChanged(CharSequence s,
    256                                   int start,
    257                                   int count,
    258                                   int after) {
    259         // Does nothing.  Needed to implement TextWatcher.
    260     }
    261 
    262     @Override
    263     public void onTextChanged(CharSequence s,
    264                               int start,
    265                               int before,
    266                               int count) {
    267         findAll();
    268     }
    269 
    270     @Override
    271     public void afterTextChanged(Editable s) {
    272         // Does nothing.  Needed to implement TextWatcher.
    273     }
    274 
    275     private Rect mGlobalVisibleRect = new Rect();
    276     private Point mGlobalVisibleOffset = new Point();
    277     public int getActionModeGlobalBottom() {
    278         if (mActionMode == null) {
    279             return 0;
    280         }
    281         View view = (View) mCustomView.getParent();
    282         if (view == null) {
    283             view = mCustomView;
    284         }
    285         view.getGlobalVisibleRect(mGlobalVisibleRect, mGlobalVisibleOffset);
    286         return mGlobalVisibleRect.bottom;
    287     }
    288 
    289     public static class NoAction implements ActionMode.Callback {
    290         @Override
    291         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
    292             return false;
    293         }
    294 
    295         @Override
    296         public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
    297             return false;
    298         }
    299 
    300         @Override
    301         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
    302             return false;
    303         }
    304 
    305         @Override
    306         public void onDestroyActionMode(ActionMode mode) {
    307         }
    308     }
    309 }
    310