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