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