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