FindActionModeCallback.java revision 05919f2133f8af89ab69c3a39aa34c6a32e78f05
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 View mTitleBar;
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        setText("");
58        mMatches = (TextView) mCustomView.findViewById(
59                com.android.internal.R.id.matches);
60        mInput = (InputMethodManager)
61                context.getSystemService(Context.INPUT_METHOD_SERVICE);
62        mResources = context.getResources();
63    }
64
65    void setTitleBar(View v) { mTitleBar = v; }
66
67    void finish() {
68        mActionMode.finish();
69    }
70
71    /*
72     * Place text in the text field so it can be searched for.  Need to press
73     * the find next or find previous button to find all of the matches.
74     */
75    void setText(String text) {
76        mEditText.setText(text);
77        Spannable span = (Spannable) mEditText.getText();
78        int length = span.length();
79        // Ideally, we would like to set the selection to the whole field,
80        // but this brings up the Text selection CAB, which dismisses this
81        // one.
82        Selection.setSelection(span, length, length);
83        // Necessary each time we set the text, so that this will watch
84        // changes to it.
85        span.setSpan(this, 0, length, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
86        mMatchesFound = false;
87    }
88
89    /*
90     * Set the WebView to search.  Must be non null, and set before calling
91     * startActionMode.
92     */
93    void setWebView(WebView webView) {
94        if (null == webView) {
95            throw new AssertionError("WebView supplied to "
96                    + "FindActionModeCallback cannot be null");
97        }
98        mWebView = webView;
99    }
100
101    /*
102     * Move the highlight to the next match.
103     * @param next If true, find the next match further down in the document.
104     *             If false, find the previous match, up in the document.
105     */
106    private void findNext(boolean next) {
107        if (mWebView == null) {
108            throw new AssertionError(
109                    "No WebView for FindActionModeCallback::findNext");
110        }
111        mWebView.findNext(next);
112    }
113
114    /*
115     * Highlight all the instances of the string from mEditText in mWebView.
116     */
117    void findAll() {
118        if (mWebView == null) {
119            throw new AssertionError(
120                    "No WebView for FindActionModeCallback::findAll");
121        }
122        CharSequence find = mEditText.getText();
123        if (0 == find.length()) {
124            mWebView.clearMatches();
125            mMatches.setVisibility(View.GONE);
126            mMatchesFound = false;
127        } else {
128            mMatchesFound = true;
129            mMatches.setVisibility(View.VISIBLE);
130            mNumberOfMatches = mWebView.findAll(find.toString());
131            if (0 == mNumberOfMatches) {
132                mMatches.setText(mResources.getString(
133                        com.android.internal.R.string.no_matches));
134            } else {
135                updateMatchesString();
136            }
137        }
138    }
139
140    /*
141     * Update the string which tells the user how many matches were found, and
142     * which match is currently highlighted.
143     */
144    private void updateMatchesString() {
145        String template = mResources.getQuantityString(
146                com.android.internal.R.plurals.matches_found, mNumberOfMatches,
147                mWebView.findIndex() + 1, mNumberOfMatches);
148
149        mMatches.setText(template);
150    }
151
152    // OnLongClickListener implementation
153
154    @Override
155    public boolean onLongClick(View v) { return true; }
156
157    // ActionMode.Callback implementation
158
159    @Override
160    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
161        mode.setCustomView(mCustomView);
162        mode.getMenuInflater().inflate(com.android.internal.R.menu.webview_find,
163                menu);
164        mActionMode = mode;
165        Editable edit = mEditText.getText();
166        Selection.setSelection(edit, edit.length());
167        mMatches.setVisibility(View.GONE);
168        mMatchesFound = false;
169        mMatches.setText("0");
170        mEditText.requestFocus();
171        mInput.showSoftInput(mEditText, 0);
172        return true;
173    }
174
175    @Override
176    public void onDestroyActionMode(ActionMode mode) {
177        if (mTitleBar != null) mWebView.setEmbeddedTitleBar(mTitleBar);
178        mWebView.notifyFindDialogDismissed();
179        mInput.hideSoftInputFromWindow(mWebView.getWindowToken(), 0);
180    }
181
182    @Override
183    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
184        return false;
185    }
186
187    @Override
188    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
189        if (!mMatchesFound) {
190            findAll();
191            return true;
192        }
193        switch(item.getItemId()) {
194            case com.android.internal.R.id.find_prev:
195                findNext(false);
196                break;
197            case com.android.internal.R.id.find_next:
198                findNext(true);
199                break;
200            default:
201                return false;
202        }
203        updateMatchesString();
204        return true;
205    }
206
207    // TextWatcher implementation
208
209    @Override
210    public void beforeTextChanged(CharSequence s,
211                                  int start,
212                                  int count,
213                                  int after) {
214        // Does nothing.  Needed to implement TextWatcher.
215    }
216
217    @Override
218    public void onTextChanged(CharSequence s,
219                              int start,
220                              int before,
221                              int count) {
222        findAll();
223    }
224
225    @Override
226    public void afterTextChanged(Editable s) {
227        // Does nothing.  Needed to implement TextWatcher.
228    }
229
230}
231