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