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