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