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