FindActionModeCallback.java revision 73254be0ddfc075521a63a069c6c30d200dca557
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.text.Editable;
22import android.text.Selection;
23import android.text.Spannable;
24import android.text.TextWatcher;
25import android.webkit.WebView;
26import android.widget.EditText;
27import android.widget.TextView;
28import android.view.ActionMode;
29import android.view.LayoutInflater;
30import android.view.Menu;
31import android.view.MenuInflater;
32import android.view.MenuItem;
33import android.view.View;
34import android.view.inputmethod.InputMethodManager;
35
36class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
37        View.OnLongClickListener {
38    private View mCustomView;
39    private EditText mEditText;
40    private TextView mMatches;
41    private WebView mWebView;
42    private InputMethodManager mInput;
43    private Resources mResources;
44    private boolean mMatchesFound;
45    private int mNumberOfMatches;
46    private ActionMode mActionMode;
47
48    FindActionModeCallback(Context context) {
49        mCustomView = LayoutInflater.from(context).inflate(
50                com.android.internal.R.layout.webview_find, null);
51        mEditText = (EditText) mCustomView.findViewById(
52                com.android.internal.R.id.edit);
53        // Override long click so that select ActionMode is not opened, which
54        // would exit find ActionMode.
55        mEditText.setOnLongClickListener(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
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     */
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     */
90    void setWebView(WebView 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     */
103    private void findNext(boolean next) {
104        if (mWebView == null) {
105            throw new AssertionError(
106                    "No WebView for FindActionModeCallback::findNext");
107        }
108        mWebView.findNext(next);
109    }
110
111    /*
112     * Highlight all the instances of the string from mEditText in mWebView.
113     */
114    void findAll() {
115        if (mWebView == null) {
116            throw new AssertionError(
117                    "No WebView for FindActionModeCallback::findAll");
118        }
119        CharSequence find = mEditText.getText();
120        if (0 == find.length()) {
121            mWebView.clearMatches();
122            mMatches.setVisibility(View.GONE);
123            mMatchesFound = false;
124        } else {
125            mMatchesFound = true;
126            mMatches.setVisibility(View.VISIBLE);
127            mNumberOfMatches = mWebView.findAll(find.toString());
128            if (0 == mNumberOfMatches) {
129                mMatches.setText(mResources.getString(
130                        com.android.internal.R.string.no_matches));
131            } else {
132                updateMatchesString();
133            }
134        }
135    }
136
137    public void showSoftInput() {
138        mInput.startGettingWindowFocus(mEditText.getRootView());
139        mInput.focusIn(mEditText);
140        mInput.showSoftInput(mEditText, 0);
141    }
142
143    /*
144     * Update the string which tells the user how many matches were found, and
145     * which match is currently highlighted.
146     * Not to be called when mNumberOfMatches is 0.
147     */
148    private void updateMatchesString() {
149        String template = mResources.getQuantityString(
150                com.android.internal.R.plurals.matches_found, mNumberOfMatches,
151                mWebView.findIndex() + 1, mNumberOfMatches);
152
153        mMatches.setText(template);
154    }
155
156    // OnLongClickListener implementation
157
158    @Override
159    public boolean onLongClick(View v) { return true; }
160
161    // ActionMode.Callback implementation
162
163    @Override
164    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
165        mode.setCustomView(mCustomView);
166        mode.getMenuInflater().inflate(com.android.internal.R.menu.webview_find,
167                menu);
168        mActionMode = mode;
169        Editable edit = mEditText.getText();
170        Selection.setSelection(edit, edit.length());
171        mMatches.setVisibility(View.GONE);
172        mMatchesFound = false;
173        mMatches.setText("0");
174        mEditText.requestFocus();
175        return true;
176    }
177
178    @Override
179    public void onDestroyActionMode(ActionMode mode) {
180        mWebView.notifyFindDialogDismissed();
181        mInput.hideSoftInputFromWindow(mWebView.getWindowToken(), 0);
182    }
183
184    @Override
185    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
186        return false;
187    }
188
189    @Override
190    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
191        if (mWebView == null) {
192            throw new AssertionError(
193                    "No WebView for FindActionModeCallback::onActionItemClicked");
194        }
195        mInput.hideSoftInputFromWindow(mWebView.getWindowToken(), 0);
196        if (!mMatchesFound) {
197            findAll();
198            return true;
199        }
200        if (0 == mNumberOfMatches) {
201            // There are no matches, so moving to the next match will not do
202            // anything.
203            return true;
204        }
205        switch(item.getItemId()) {
206            case com.android.internal.R.id.find_prev:
207                findNext(false);
208                break;
209            case com.android.internal.R.id.find_next:
210                findNext(true);
211                break;
212            default:
213                return false;
214        }
215        updateMatchesString();
216        return true;
217    }
218
219    // TextWatcher implementation
220
221    @Override
222    public void beforeTextChanged(CharSequence s,
223                                  int start,
224                                  int count,
225                                  int after) {
226        // Does nothing.  Needed to implement TextWatcher.
227    }
228
229    @Override
230    public void onTextChanged(CharSequence s,
231                              int start,
232                              int before,
233                              int count) {
234        findAll();
235    }
236
237    @Override
238    public void afterTextChanged(Editable s) {
239        // Does nothing.  Needed to implement TextWatcher.
240    }
241
242}
243