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 17 package android.webkit; 18 19 import android.content.Context; 20 import android.content.res.Resources; 21 import android.graphics.Point; 22 import android.graphics.Rect; 23 import android.text.Editable; 24 import android.text.Selection; 25 import android.text.Spannable; 26 import android.text.TextWatcher; 27 import android.view.ActionMode; 28 import android.view.LayoutInflater; 29 import android.view.Menu; 30 import android.view.MenuItem; 31 import android.view.View; 32 import android.view.inputmethod.InputMethodManager; 33 import android.widget.EditText; 34 import android.widget.TextView; 35 36 class FindActionModeCallback implements ActionMode.Callback, TextWatcher, 37 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 FindActionModeCallback(Context context)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 mEditText.setCustomSelectionActionModeCallback(new NoAction()); 55 mEditText.setOnClickListener(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 finish()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 */ setText(String text)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 */ setWebView(WebViewClassic webView)90 void setWebView(WebViewClassic 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 */ findNext(boolean next)103 private void findNext(boolean next) { 104 if (mWebView == null) { 105 throw new AssertionError( 106 "No WebView for FindActionModeCallback::findNext"); 107 } 108 if (!mMatchesFound) { 109 findAll(); 110 return; 111 } 112 if (0 == mNumberOfMatches) { 113 // There are no matches, so moving to the next match will not do 114 // anything. 115 return; 116 } 117 mWebView.findNext(next); 118 updateMatchesString(); 119 } 120 121 /* 122 * Highlight all the instances of the string from mEditText in mWebView. 123 */ findAll()124 void findAll() { 125 if (mWebView == null) { 126 throw new AssertionError( 127 "No WebView for FindActionModeCallback::findAll"); 128 } 129 CharSequence find = mEditText.getText(); 130 if (0 == find.length()) { 131 mWebView.clearMatches(); 132 mMatches.setVisibility(View.GONE); 133 mMatchesFound = false; 134 mWebView.findAll(null); 135 } else { 136 mMatchesFound = true; 137 mMatches.setVisibility(View.INVISIBLE); 138 mNumberOfMatches = 0; 139 mWebView.findAllAsync(find.toString()); 140 } 141 } 142 showSoftInput()143 public void showSoftInput() { 144 mInput.startGettingWindowFocus(mEditText.getRootView()); 145 mInput.focusIn(mEditText); 146 mInput.showSoftInput(mEditText, 0); 147 } 148 updateMatchCount(int matchIndex, int matchCount, boolean isEmptyFind)149 public void updateMatchCount(int matchIndex, int matchCount, boolean isEmptyFind) { 150 if (!isEmptyFind) { 151 mNumberOfMatches = matchCount; 152 mActiveMatchIndex = matchIndex; 153 updateMatchesString(); 154 } else { 155 mMatches.setVisibility(View.INVISIBLE); 156 mNumberOfMatches = 0; 157 } 158 } 159 160 /* 161 * Update the string which tells the user how many matches were found, and 162 * which match is currently highlighted. 163 */ updateMatchesString()164 private void updateMatchesString() { 165 if (mNumberOfMatches == 0) { 166 mMatches.setText(com.android.internal.R.string.no_matches); 167 } else { 168 mMatches.setText(mResources.getQuantityString( 169 com.android.internal.R.plurals.matches_found, mNumberOfMatches, 170 mActiveMatchIndex + 1, mNumberOfMatches)); 171 } 172 mMatches.setVisibility(View.VISIBLE); 173 } 174 175 // OnClickListener implementation 176 177 @Override onClick(View v)178 public void onClick(View v) { 179 findNext(true); 180 } 181 182 // ActionMode.Callback implementation 183 184 @Override onCreateActionMode(ActionMode mode, Menu menu)185 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 186 if (!mode.isUiFocusable()) { 187 // If the action mode we're running in is not focusable the user 188 // will not be able to type into the find on page field. This 189 // should only come up when we're running in a dialog which is 190 // already less than ideal; disable the option for now. 191 return false; 192 } 193 194 mode.setCustomView(mCustomView); 195 mode.getMenuInflater().inflate(com.android.internal.R.menu.webview_find, 196 menu); 197 mActionMode = mode; 198 Editable edit = mEditText.getText(); 199 Selection.setSelection(edit, edit.length()); 200 mMatches.setVisibility(View.GONE); 201 mMatchesFound = false; 202 mMatches.setText("0"); 203 mEditText.requestFocus(); 204 return true; 205 } 206 207 @Override onDestroyActionMode(ActionMode mode)208 public void onDestroyActionMode(ActionMode mode) { 209 mActionMode = null; 210 mWebView.notifyFindDialogDismissed(); 211 mInput.hideSoftInputFromWindow(mWebView.getWebView().getWindowToken(), 0); 212 } 213 214 @Override onPrepareActionMode(ActionMode mode, Menu menu)215 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 216 return false; 217 } 218 219 @Override onActionItemClicked(ActionMode mode, MenuItem item)220 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 221 if (mWebView == null) { 222 throw new AssertionError( 223 "No WebView for FindActionModeCallback::onActionItemClicked"); 224 } 225 mInput.hideSoftInputFromWindow(mWebView.getWebView().getWindowToken(), 0); 226 switch(item.getItemId()) { 227 case com.android.internal.R.id.find_prev: 228 findNext(false); 229 break; 230 case com.android.internal.R.id.find_next: 231 findNext(true); 232 break; 233 default: 234 return false; 235 } 236 return true; 237 } 238 239 // TextWatcher implementation 240 241 @Override beforeTextChanged(CharSequence s, int start, int count, int after)242 public void beforeTextChanged(CharSequence s, 243 int start, 244 int count, 245 int after) { 246 // Does nothing. Needed to implement TextWatcher. 247 } 248 249 @Override onTextChanged(CharSequence s, int start, int before, int count)250 public void onTextChanged(CharSequence s, 251 int start, 252 int before, 253 int count) { 254 findAll(); 255 } 256 257 @Override afterTextChanged(Editable s)258 public void afterTextChanged(Editable s) { 259 // Does nothing. Needed to implement TextWatcher. 260 } 261 262 private Rect mGlobalVisibleRect = new Rect(); 263 private Point mGlobalVisibleOffset = new Point(); getActionModeGlobalBottom()264 public int getActionModeGlobalBottom() { 265 if (mActionMode == null) { 266 return 0; 267 } 268 View view = (View) mCustomView.getParent(); 269 if (view == null) { 270 view = mCustomView; 271 } 272 view.getGlobalVisibleRect(mGlobalVisibleRect, mGlobalVisibleOffset); 273 return mGlobalVisibleRect.bottom; 274 } 275 276 public static class NoAction implements ActionMode.Callback { 277 @Override onCreateActionMode(ActionMode mode, Menu menu)278 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 279 return false; 280 } 281 282 @Override onPrepareActionMode(ActionMode mode, Menu menu)283 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 284 return false; 285 } 286 287 @Override onActionItemClicked(ActionMode mode, MenuItem item)288 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 289 return false; 290 } 291 292 @Override onDestroyActionMode(ActionMode mode)293 public void onDestroyActionMode(ActionMode mode) { 294 } 295 } 296 } 297