324 lines
10 KiB
Java
324 lines
10 KiB
Java
/*
|
|
* Copyright (C) 2010 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package android.webkit;
|
|
|
|
import android.annotation.NonNull;
|
|
import android.annotation.SystemApi;
|
|
import android.content.Context;
|
|
import android.content.res.Resources;
|
|
import android.graphics.Point;
|
|
import android.graphics.Rect;
|
|
import android.text.Editable;
|
|
import android.text.Selection;
|
|
import android.text.Spannable;
|
|
import android.text.TextWatcher;
|
|
import android.util.PluralsMessageFormatter;
|
|
import android.view.ActionMode;
|
|
import android.view.LayoutInflater;
|
|
import android.view.Menu;
|
|
import android.view.MenuItem;
|
|
import android.view.View;
|
|
import android.view.inputmethod.InputMethodManager;
|
|
import android.widget.EditText;
|
|
import android.widget.TextView;
|
|
|
|
import com.android.internal.R;
|
|
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@SystemApi
|
|
public class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
|
|
View.OnClickListener, WebView.FindListener {
|
|
private View mCustomView;
|
|
private EditText mEditText;
|
|
private TextView mMatches;
|
|
private WebView mWebView;
|
|
private InputMethodManager mInput;
|
|
private Resources mResources;
|
|
private boolean mMatchesFound;
|
|
private int mNumberOfMatches;
|
|
private int mActiveMatchIndex;
|
|
private ActionMode mActionMode;
|
|
|
|
public FindActionModeCallback(Context context) {
|
|
mCustomView = LayoutInflater.from(context).inflate(
|
|
com.android.internal.R.layout.webview_find, null);
|
|
mEditText = (EditText) mCustomView.findViewById(
|
|
com.android.internal.R.id.edit);
|
|
mEditText.setCustomSelectionActionModeCallback(new NoAction());
|
|
mEditText.setOnClickListener(this);
|
|
setText("");
|
|
mMatches = (TextView) mCustomView.findViewById(
|
|
com.android.internal.R.id.matches);
|
|
mInput = context.getSystemService(InputMethodManager.class);
|
|
mResources = context.getResources();
|
|
}
|
|
|
|
public void finish() {
|
|
mActionMode.finish();
|
|
}
|
|
|
|
/**
|
|
* Place text in the text field so it can be searched for. Need to press
|
|
* the find next or find previous button to find all of the matches.
|
|
*/
|
|
public void setText(String text) {
|
|
mEditText.setText(text);
|
|
Spannable span = (Spannable) mEditText.getText();
|
|
int length = span.length();
|
|
// Ideally, we would like to set the selection to the whole field,
|
|
// but this brings up the Text selection CAB, which dismisses this
|
|
// one.
|
|
Selection.setSelection(span, length, length);
|
|
// Necessary each time we set the text, so that this will watch
|
|
// changes to it.
|
|
span.setSpan(this, 0, length, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
|
mMatchesFound = false;
|
|
}
|
|
|
|
/**
|
|
* Set the WebView to search.
|
|
*
|
|
* @param webView an implementation of WebView
|
|
*/
|
|
public void setWebView(@NonNull WebView webView) {
|
|
if (null == webView) {
|
|
throw new AssertionError("WebView supplied to "
|
|
+ "FindActionModeCallback cannot be null");
|
|
}
|
|
mWebView = webView;
|
|
mWebView.setFindDialogFindListener(this);
|
|
}
|
|
|
|
@Override
|
|
public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
|
|
boolean isDoneCounting) {
|
|
if (isDoneCounting) {
|
|
updateMatchCount(activeMatchOrdinal, numberOfMatches, numberOfMatches == 0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Move the highlight to the next match.
|
|
* @param next If {@code true}, find the next match further down in the document.
|
|
* If {@code false}, find the previous match, up in the document.
|
|
*/
|
|
private void findNext(boolean next) {
|
|
if (mWebView == null) {
|
|
throw new AssertionError(
|
|
"No WebView for FindActionModeCallback::findNext");
|
|
}
|
|
if (!mMatchesFound) {
|
|
findAll();
|
|
return;
|
|
}
|
|
if (0 == mNumberOfMatches) {
|
|
// There are no matches, so moving to the next match will not do
|
|
// anything.
|
|
return;
|
|
}
|
|
mWebView.findNext(next);
|
|
updateMatchesString();
|
|
}
|
|
|
|
/**
|
|
* Highlight all the instances of the string from mEditText in mWebView.
|
|
*/
|
|
public void findAll() {
|
|
if (mWebView == null) {
|
|
throw new AssertionError(
|
|
"No WebView for FindActionModeCallback::findAll");
|
|
}
|
|
CharSequence find = mEditText.getText();
|
|
if (0 == find.length()) {
|
|
mWebView.clearMatches();
|
|
mMatches.setVisibility(View.GONE);
|
|
mMatchesFound = false;
|
|
mWebView.findAll(null);
|
|
} else {
|
|
mMatchesFound = true;
|
|
mMatches.setVisibility(View.INVISIBLE);
|
|
mNumberOfMatches = 0;
|
|
mWebView.findAllAsync(find.toString());
|
|
}
|
|
}
|
|
|
|
public void showSoftInput() {
|
|
if (mEditText.requestFocus()) {
|
|
mInput.showSoftInput(mEditText, 0);
|
|
}
|
|
}
|
|
|
|
public void updateMatchCount(int matchIndex, int matchCount, boolean isEmptyFind) {
|
|
if (!isEmptyFind) {
|
|
mNumberOfMatches = matchCount;
|
|
mActiveMatchIndex = matchIndex;
|
|
updateMatchesString();
|
|
} else {
|
|
mMatches.setVisibility(View.GONE);
|
|
mNumberOfMatches = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update the string which tells the user how many matches were found, and
|
|
* which match is currently highlighted.
|
|
*/
|
|
private void updateMatchesString() {
|
|
if (mNumberOfMatches == 0) {
|
|
mMatches.setText(com.android.internal.R.string.no_matches);
|
|
} else {
|
|
Map<String, Object> arguments = new HashMap<>();
|
|
arguments.put("count", mActiveMatchIndex + 1);
|
|
arguments.put("total", mNumberOfMatches);
|
|
|
|
mMatches.setText(PluralsMessageFormatter.format(
|
|
mResources,
|
|
arguments,
|
|
R.string.matches_found));
|
|
}
|
|
mMatches.setVisibility(View.VISIBLE);
|
|
}
|
|
|
|
// OnClickListener implementation
|
|
|
|
@Override
|
|
public void onClick(View v) {
|
|
findNext(true);
|
|
}
|
|
|
|
// ActionMode.Callback implementation
|
|
|
|
@Override
|
|
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
|
if (!mode.isUiFocusable()) {
|
|
// If the action mode we're running in is not focusable the user
|
|
// will not be able to type into the find on page field. This
|
|
// should only come up when we're running in a dialog which is
|
|
// already less than ideal; disable the option for now.
|
|
return false;
|
|
}
|
|
|
|
mode.setCustomView(mCustomView);
|
|
mode.getMenuInflater().inflate(com.android.internal.R.menu.webview_find,
|
|
menu);
|
|
mActionMode = mode;
|
|
Editable edit = mEditText.getText();
|
|
Selection.setSelection(edit, edit.length());
|
|
mMatches.setVisibility(View.GONE);
|
|
mMatchesFound = false;
|
|
mMatches.setText("0");
|
|
mEditText.requestFocus();
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void onDestroyActionMode(ActionMode mode) {
|
|
mActionMode = null;
|
|
mWebView.notifyFindDialogDismissed();
|
|
mWebView.setFindDialogFindListener(null);
|
|
mInput.hideSoftInputFromWindow(mWebView.getWindowToken(), 0);
|
|
}
|
|
|
|
@Override
|
|
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
|
if (mWebView == null) {
|
|
throw new AssertionError(
|
|
"No WebView for FindActionModeCallback::onActionItemClicked");
|
|
}
|
|
mInput.hideSoftInputFromWindow(mWebView.getWindowToken(), 0);
|
|
switch(item.getItemId()) {
|
|
case com.android.internal.R.id.find_prev:
|
|
findNext(false);
|
|
break;
|
|
case com.android.internal.R.id.find_next:
|
|
findNext(true);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// TextWatcher implementation
|
|
|
|
@Override
|
|
public void beforeTextChanged(CharSequence s,
|
|
int start,
|
|
int count,
|
|
int after) {
|
|
// Does nothing. Needed to implement TextWatcher.
|
|
}
|
|
|
|
@Override
|
|
public void onTextChanged(CharSequence s,
|
|
int start,
|
|
int before,
|
|
int count) {
|
|
findAll();
|
|
}
|
|
|
|
@Override
|
|
public void afterTextChanged(Editable s) {
|
|
// Does nothing. Needed to implement TextWatcher.
|
|
}
|
|
|
|
private Rect mGlobalVisibleRect = new Rect();
|
|
private Point mGlobalVisibleOffset = new Point();
|
|
public int getActionModeGlobalBottom() {
|
|
if (mActionMode == null) {
|
|
return 0;
|
|
}
|
|
View view = (View) mCustomView.getParent();
|
|
if (view == null) {
|
|
view = mCustomView;
|
|
}
|
|
view.getGlobalVisibleRect(mGlobalVisibleRect, mGlobalVisibleOffset);
|
|
return mGlobalVisibleRect.bottom;
|
|
}
|
|
|
|
public static class NoAction implements ActionMode.Callback {
|
|
@Override
|
|
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public void onDestroyActionMode(ActionMode mode) {
|
|
}
|
|
}
|
|
}
|