blob: e011d51c51fd22e6e507fe54b216ce87130fbd54 [file] [log] [blame]
Leon Scrogginsfe026bd2010-08-24 14:16:09 -04001/*
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
17package android.webkit;
18
Nate Fischer3442c742017-09-08 17:02:00 -070019import android.annotation.NonNull;
Ignacio Solla451e3382014-11-10 10:35:54 +000020import android.annotation.SystemApi;
Leon Scrogginsfe026bd2010-08-24 14:16:09 -040021import android.content.Context;
22import android.content.res.Resources;
John Reckd6ac7272011-11-08 15:00:17 -080023import android.graphics.Point;
24import android.graphics.Rect;
Leon Scrogginsfe026bd2010-08-24 14:16:09 -040025import android.text.Editable;
26import android.text.Selection;
27import android.text.Spannable;
28import android.text.TextWatcher;
Leon Scrogginsfe026bd2010-08-24 14:16:09 -040029import android.view.ActionMode;
30import android.view.LayoutInflater;
31import android.view.Menu;
Leon Scrogginsfe026bd2010-08-24 14:16:09 -040032import android.view.MenuItem;
33import android.view.View;
34import android.view.inputmethod.InputMethodManager;
John Reckfffce6f2011-10-06 20:37:01 -070035import android.widget.EditText;
36import android.widget.TextView;
Leon Scrogginsfe026bd2010-08-24 14:16:09 -040037
Ben Murdoch52c9f7f2013-01-18 00:50:37 +000038/**
39 * @hide
40 */
Ignacio Solla451e3382014-11-10 10:35:54 +000041@SystemApi
Ben Murdoch52c9f7f2013-01-18 00:50:37 +000042public class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
43 View.OnClickListener, WebView.FindListener {
Leon Scrogginsfe026bd2010-08-24 14:16:09 -040044 private View mCustomView;
45 private EditText mEditText;
46 private TextView mMatches;
Ben Murdoch52c9f7f2013-01-18 00:50:37 +000047 private WebView mWebView;
Leon Scrogginsfe026bd2010-08-24 14:16:09 -040048 private InputMethodManager mInput;
49 private Resources mResources;
50 private boolean mMatchesFound;
51 private int mNumberOfMatches;
Victoria Lease47d0ee92012-02-13 15:47:48 -080052 private int mActiveMatchIndex;
Leon Scroggins05919f22010-09-14 17:22:36 -040053 private ActionMode mActionMode;
Leon Scrogginsfe026bd2010-08-24 14:16:09 -040054
Ben Murdoch52c9f7f2013-01-18 00:50:37 +000055 public FindActionModeCallback(Context context) {
Leon Scrogginsfe026bd2010-08-24 14:16:09 -040056 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);
George Mount7e9f62e2012-05-23 16:55:46 -070060 mEditText.setCustomSelectionActionModeCallback(new NoAction());
Leon Scroggins7014b122011-01-11 15:17:34 -050061 mEditText.setOnClickListener(this);
Leon Scrogginsfe026bd2010-08-24 14:16:09 -040062 setText("");
63 mMatches = (TextView) mCustomView.findViewById(
64 com.android.internal.R.id.matches);
Yohei Yukawa777ef952015-11-25 20:32:24 -080065 mInput = context.getSystemService(InputMethodManager.class);
Leon Scrogginsfe026bd2010-08-24 14:16:09 -040066 mResources = context.getResources();
67 }
68
Ben Murdoch52c9f7f2013-01-18 00:50:37 +000069 public void finish() {
Leon Scroggins05919f22010-09-14 17:22:36 -040070 mActionMode.finish();
71 }
72
Nate Fischer5cca7bd2017-09-26 18:51:49 -070073 /**
Leon Scrogginsfe026bd2010-08-24 14:16:09 -040074 * 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 */
Ben Murdoch52c9f7f2013-01-18 00:50:37 +000077 public void setText(String text) {
Leon Scrogginsfe026bd2010-08-24 14:16:09 -040078 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
Nate Fischer3442c742017-09-08 17:02:00 -070091 /**
92 * Set the WebView to search.
93 *
94 * @param webView an implementation of WebView
Leon Scrogginsfe026bd2010-08-24 14:16:09 -040095 */
Nate Fischer3442c742017-09-08 17:02:00 -070096 public void setWebView(@NonNull WebView webView) {
Leon Scrogginsfe026bd2010-08-24 14:16:09 -040097 if (null == webView) {
98 throw new AssertionError("WebView supplied to "
99 + "FindActionModeCallback cannot be null");
100 }
101 mWebView = webView;
Ben Murdoch52c9f7f2013-01-18 00:50:37 +0000102 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 }
Leon Scrogginsfe026bd2010-08-24 14:16:09 -0400111 }
112
Nate Fischer5cca7bd2017-09-26 18:51:49 -0700113 /**
Leon Scrogginsfe026bd2010-08-24 14:16:09 -0400114 * Move the highlight to the next match.
Nate Fischer0a6140d2017-09-05 12:37:49 -0700115 * @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.
Leon Scrogginsfe026bd2010-08-24 14:16:09 -0400117 */
118 private void findNext(boolean next) {
119 if (mWebView == null) {
120 throw new AssertionError(
121 "No WebView for FindActionModeCallback::findNext");
122 }
Leon Scroggins7014b122011-01-11 15:17:34 -0500123 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 }
Leon Scrogginsfe026bd2010-08-24 14:16:09 -0400132 mWebView.findNext(next);
Leon Scroggins7014b122011-01-11 15:17:34 -0500133 updateMatchesString();
Leon Scrogginsfe026bd2010-08-24 14:16:09 -0400134 }
135
Nate Fischer5cca7bd2017-09-26 18:51:49 -0700136 /**
Leon Scrogginsfe026bd2010-08-24 14:16:09 -0400137 * Highlight all the instances of the string from mEditText in mWebView.
138 */
Ben Murdoch52c9f7f2013-01-18 00:50:37 +0000139 public void findAll() {
Leon Scrogginsfe026bd2010-08-24 14:16:09 -0400140 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;
Victoria Leaseabeb6a72012-03-05 16:29:12 -0800149 mWebView.findAll(null);
Leon Scrogginsfe026bd2010-08-24 14:16:09 -0400150 } else {
151 mMatchesFound = true;
Victoria Lease47d0ee92012-02-13 15:47:48 -0800152 mMatches.setVisibility(View.INVISIBLE);
153 mNumberOfMatches = 0;
Victoria Leaseabeb6a72012-03-05 16:29:12 -0800154 mWebView.findAllAsync(find.toString());
Leon Scrogginsfe026bd2010-08-24 14:16:09 -0400155 }
156 }
157
Leon Scroggins571354f2011-01-04 10:12:41 -0500158 public void showSoftInput() {
Yohei Yukawa5f059652015-05-14 22:16:41 -0700159 if (mEditText.requestFocus()) {
160 mInput.showSoftInput(mEditText, 0);
161 }
Leon Scroggins571354f2011-01-04 10:12:41 -0500162 }
163
Victoria Lease0b8413b2012-03-26 13:04:10 -0700164 public void updateMatchCount(int matchIndex, int matchCount, boolean isEmptyFind) {
165 if (!isEmptyFind) {
Victoria Lease47d0ee92012-02-13 15:47:48 -0800166 mNumberOfMatches = matchCount;
167 mActiveMatchIndex = matchIndex;
168 updateMatchesString();
169 } else {
Stefan Wysocki1c15b8d2012-09-18 14:11:20 +0200170 mMatches.setVisibility(View.GONE);
Victoria Lease47d0ee92012-02-13 15:47:48 -0800171 mNumberOfMatches = 0;
172 }
173 }
174
Nate Fischer5cca7bd2017-09-26 18:51:49 -0700175 /**
Leon Scrogginsfe026bd2010-08-24 14:16:09 -0400176 * Update the string which tells the user how many matches were found, and
177 * which match is currently highlighted.
178 */
179 private void updateMatchesString() {
Victoria Lease47d0ee92012-02-13 15:47:48 -0800180 if (mNumberOfMatches == 0) {
181 mMatches.setText(com.android.internal.R.string.no_matches);
182 } else {
183 mMatches.setText(mResources.getQuantityString(
Leon Scrogginsfe026bd2010-08-24 14:16:09 -0400184 com.android.internal.R.plurals.matches_found, mNumberOfMatches,
Victoria Lease47d0ee92012-02-13 15:47:48 -0800185 mActiveMatchIndex + 1, mNumberOfMatches));
186 }
187 mMatches.setVisibility(View.VISIBLE);
Leon Scrogginsfe026bd2010-08-24 14:16:09 -0400188 }
189
Leon Scroggins7014b122011-01-11 15:17:34 -0500190 // OnClickListener implementation
191
192 @Override
193 public void onClick(View v) {
194 findNext(true);
195 }
196
Leon Scrogginsfe026bd2010-08-24 14:16:09 -0400197 // ActionMode.Callback implementation
198
199 @Override
200 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
Adam Powellf8419a02011-10-03 12:08:54 -0700201 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
Leon Scrogginsfe026bd2010-08-24 14:16:09 -0400209 mode.setCustomView(mCustomView);
210 mode.getMenuInflater().inflate(com.android.internal.R.menu.webview_find,
211 menu);
Leon Scroggins05919f22010-09-14 17:22:36 -0400212 mActionMode = mode;
Leon Scrogginsfe026bd2010-08-24 14:16:09 -0400213 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();
Leon Scrogginsfe026bd2010-08-24 14:16:09 -0400219 return true;
220 }
221
222 @Override
223 public void onDestroyActionMode(ActionMode mode) {
John Reckfffce6f2011-10-06 20:37:01 -0700224 mActionMode = null;
Leon Scrogginsfe026bd2010-08-24 14:16:09 -0400225 mWebView.notifyFindDialogDismissed();
Ben Murdoch52c9f7f2013-01-18 00:50:37 +0000226 mWebView.setFindDialogFindListener(null);
227 mInput.hideSoftInputFromWindow(mWebView.getWindowToken(), 0);
Leon Scrogginsfe026bd2010-08-24 14:16:09 -0400228 }
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) {
Leon Scroggins261f42d2010-12-09 13:46:28 -0500237 if (mWebView == null) {
238 throw new AssertionError(
239 "No WebView for FindActionModeCallback::onActionItemClicked");
240 }
Ben Murdoch52c9f7f2013-01-18 00:50:37 +0000241 mInput.hideSoftInputFromWindow(mWebView.getWindowToken(), 0);
Leon Scrogginsfe026bd2010-08-24 14:16:09 -0400242 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 }
Leon Scrogginsfe026bd2010-08-24 14:16:09 -0400252 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
John Reckd6ac7272011-11-08 15:00:17 -0800278 private Rect mGlobalVisibleRect = new Rect();
279 private Point mGlobalVisibleOffset = new Point();
280 public int getActionModeGlobalBottom() {
John Reckfffce6f2011-10-06 20:37:01 -0700281 if (mActionMode == null) {
282 return 0;
283 }
John Reckd6ac7272011-11-08 15:00:17 -0800284 View view = (View) mCustomView.getParent();
285 if (view == null) {
286 view = mCustomView;
287 }
288 view.getGlobalVisibleRect(mGlobalVisibleRect, mGlobalVisibleOffset);
289 return mGlobalVisibleRect.bottom;
John Reckfffce6f2011-10-06 20:37:01 -0700290 }
291
George Mount7e9f62e2012-05-23 16:55:46 -0700292 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 }
Leon Scrogginsfe026bd2010-08-24 14:16:09 -0400312}