OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.chrome.browser.widget.findinpage; |
| 6 |
| 7 import android.annotation.SuppressLint; |
| 8 import android.content.Context; |
| 9 import android.graphics.Rect; |
| 10 import android.os.Handler; |
| 11 import android.os.Vibrator; |
| 12 import android.provider.Settings; |
| 13 import android.support.v4.view.accessibility.AccessibilityEventCompat; |
| 14 import android.text.Editable; |
| 15 import android.text.InputType; |
| 16 import android.text.TextWatcher; |
| 17 import android.util.AttributeSet; |
| 18 import android.view.ActionMode; |
| 19 import android.view.Gravity; |
| 20 import android.view.KeyEvent; |
| 21 import android.view.View; |
| 22 import android.widget.LinearLayout; |
| 23 import android.widget.TextView; |
| 24 |
| 25 import com.google.android.apps.chrome.R; |
| 26 |
| 27 import org.chromium.base.VisibleForTesting; |
| 28 import org.chromium.chrome.browser.ChromeWebContentsDelegateAndroid; |
| 29 import org.chromium.chrome.browser.EmptyTabObserver; |
| 30 import org.chromium.chrome.browser.FindMatchRectsDetails; |
| 31 import org.chromium.chrome.browser.FindNotificationDetails; |
| 32 import org.chromium.chrome.browser.Tab; |
| 33 import org.chromium.chrome.browser.TabObserver; |
| 34 import org.chromium.chrome.browser.findinpage.FindInPageBridge; |
| 35 import org.chromium.chrome.browser.tabmodel.EmptyTabModelObserver; |
| 36 import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver; |
| 37 import org.chromium.chrome.browser.tabmodel.TabModel; |
| 38 import org.chromium.chrome.browser.tabmodel.TabModel.TabSelectionType; |
| 39 import org.chromium.chrome.browser.tabmodel.TabModelObserver; |
| 40 import org.chromium.chrome.browser.tabmodel.TabModelSelector; |
| 41 import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver; |
| 42 import org.chromium.chrome.browser.widget.TintedImageButton; |
| 43 import org.chromium.chrome.browser.widget.VerticallyFixedEditText; |
| 44 import org.chromium.ui.UiUtils; |
| 45 |
| 46 /** A toolbar providing find in page functionality. */ |
| 47 public class FindToolbar extends LinearLayout |
| 48 implements ChromeWebContentsDelegateAndroid.FindResultListener, |
| 49 ChromeWebContentsDelegateAndroid.FindMatchRectsListener { |
| 50 private static final long ACCESSIBLE_ANNOUNCEMENT_DELAY_MILLIS = 500; |
| 51 |
| 52 // Toolbar UI |
| 53 private TextView mFindStatus; |
| 54 protected FindQuery mFindQuery; |
| 55 protected TintedImageButton mCloseFindButton; |
| 56 protected TintedImageButton mFindPrevButton; |
| 57 protected TintedImageButton mFindNextButton; |
| 58 |
| 59 private FindResultBar mResultBar = null; |
| 60 |
| 61 protected TabModelSelector mTabModelSelector; |
| 62 private final TabModelSelectorObserver mTabModelSelectorObserver; |
| 63 private final TabModelObserver mTabModelObserver; |
| 64 private Tab mCurrentTab; |
| 65 private final TabObserver mTabObserver; |
| 66 private FindInPageBridge mFindInPageBridge; |
| 67 private FindToolbarObserver mObserver; |
| 68 |
| 69 /** Most recently entered search text (globally, in non-incognito tabs). */ |
| 70 private String mLastUserSearch = ""; |
| 71 |
| 72 /** Whether toolbar text is being set automatically (not typed by user). */ |
| 73 private boolean mSettingFindTextProgrammatically = false; |
| 74 |
| 75 /** Whether the search key should trigger a new search. */ |
| 76 private boolean mSearchKeyShouldTriggerSearch = false; |
| 77 |
| 78 private boolean mActive = false; |
| 79 |
| 80 private Handler mHandler = new Handler(); |
| 81 private Runnable mAccessibleAnnouncementRunnable; |
| 82 private boolean mAccessibilityDidActivateResult; |
| 83 |
| 84 /** Subclasses EditText in order to intercept BACK key presses. */ |
| 85 @SuppressLint("Instantiatable") |
| 86 static class FindQuery extends VerticallyFixedEditText { |
| 87 private FindToolbar mFindToolbar; |
| 88 |
| 89 public FindQuery(Context context, AttributeSet attrs) { |
| 90 super(context, attrs); |
| 91 } |
| 92 |
| 93 void setFindToolbar(FindToolbar findToolbar) { |
| 94 mFindToolbar = findToolbar; |
| 95 } |
| 96 |
| 97 @Override |
| 98 public boolean onKeyPreIme(int keyCode, KeyEvent event) { |
| 99 if (keyCode == KeyEvent.KEYCODE_BACK) { |
| 100 if (event.getAction() == KeyEvent.ACTION_DOWN |
| 101 && event.getRepeatCount() == 0) { |
| 102 // Tell the framework to start tracking this event. |
| 103 getKeyDispatcherState().startTracking(event, this); |
| 104 return true; |
| 105 } else if (event.getAction() == KeyEvent.ACTION_UP) { |
| 106 getKeyDispatcherState().handleUpEvent(event); |
| 107 if (event.isTracking() && !event.isCanceled()) { |
| 108 mFindToolbar.deactivate(); |
| 109 return true; |
| 110 } |
| 111 } |
| 112 } |
| 113 return super.onKeyPreIme(keyCode, event); |
| 114 } |
| 115 |
| 116 @Override |
| 117 public boolean onKeyDown(int keyCode, KeyEvent event) { |
| 118 if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE
_F3 |
| 119 || (keyCode == KeyEvent.KEYCODE_G && event.isCtrlPressed()))
{ |
| 120 mFindToolbar.hideKeyboardAndStartFinding(!event.isShiftPressed()
); |
| 121 return true; |
| 122 } |
| 123 return super.onKeyDown(keyCode, event); |
| 124 } |
| 125 } |
| 126 |
| 127 public FindToolbar(Context context, AttributeSet attrs) { |
| 128 super(context, attrs); |
| 129 |
| 130 mTabObserver = new EmptyTabObserver() { |
| 131 @Override |
| 132 public void onPageLoadStarted(Tab tab) { |
| 133 deactivate(); |
| 134 } |
| 135 |
| 136 @Override |
| 137 public void onContentChanged(Tab tab) { |
| 138 deactivate(); |
| 139 } |
| 140 |
| 141 @Override |
| 142 public void onClosingStateChanged(Tab tab, boolean closing) { |
| 143 if (closing) deactivate(); |
| 144 } |
| 145 }; |
| 146 |
| 147 mTabModelSelectorObserver = new EmptyTabModelSelectorObserver() { |
| 148 @Override |
| 149 public void onTabModelSelected(TabModel newModel, TabModel oldModel)
{ |
| 150 deactivate(); |
| 151 updateVisualsForTabModel(newModel.isIncognito()); |
| 152 } |
| 153 }; |
| 154 |
| 155 mTabModelObserver = new EmptyTabModelObserver() { |
| 156 @Override |
| 157 public void didSelectTab(Tab tab, TabSelectionType type, int lastId)
{ |
| 158 deactivate(); |
| 159 } |
| 160 }; |
| 161 } |
| 162 |
| 163 @Override |
| 164 public void onFinishInflate() { |
| 165 setOrientation(HORIZONTAL); |
| 166 setGravity(Gravity.CENTER_VERTICAL); |
| 167 |
| 168 mFindQuery = (FindQuery) findViewById(R.id.find_query); |
| 169 mFindQuery.setFindToolbar(this); |
| 170 mFindQuery.setInputType(InputType.TYPE_TEXT_VARIATION_FILTER); |
| 171 mFindQuery.setSelectAllOnFocus(true); |
| 172 mFindQuery.setOnFocusChangeListener(new View.OnFocusChangeListener() { |
| 173 @Override |
| 174 public void onFocusChange(View v, boolean hasFocus) { |
| 175 mAccessibilityDidActivateResult = false; |
| 176 if (!hasFocus) { |
| 177 if (mFindQuery.getText().length() > 0) { |
| 178 mSearchKeyShouldTriggerSearch = true; |
| 179 } |
| 180 UiUtils.hideKeyboard(mFindQuery); |
| 181 } |
| 182 } |
| 183 }); |
| 184 mFindQuery.addTextChangedListener(new TextWatcher() { |
| 185 @Override |
| 186 public void onTextChanged(CharSequence s, int start, int before, int
count) { |
| 187 mAccessibilityDidActivateResult = false; |
| 188 setPrevNextEnabled(s.length() > 0); |
| 189 |
| 190 if (mSettingFindTextProgrammatically) return; |
| 191 |
| 192 // If we're called during onRestoreInstanceState() the current |
| 193 // view won't have been set yet. TODO(husky): Find a better fix. |
| 194 assert mCurrentTab != null; |
| 195 assert mCurrentTab.getContentViewCore() != null; |
| 196 if (mCurrentTab.getContentViewCore() == null) return; |
| 197 |
| 198 if (s.length() > 0) { |
| 199 // Don't clearResults() as that would cause flicker. |
| 200 // Just wait until onFindResultReceived updates it. |
| 201 mSearchKeyShouldTriggerSearch = false; |
| 202 mFindInPageBridge.startFinding(s.toString(), true, false); |
| 203 } else { |
| 204 clearResults(); |
| 205 mFindInPageBridge.stopFinding(); |
| 206 } |
| 207 |
| 208 if (!mCurrentTab.isIncognito()) { |
| 209 mLastUserSearch = s.toString(); |
| 210 } |
| 211 } |
| 212 |
| 213 @Override |
| 214 public void beforeTextChanged(CharSequence s, |
| 215 int start, int count, int after) { |
| 216 } |
| 217 |
| 218 @Override |
| 219 public void afterTextChanged(Editable s) { |
| 220 } |
| 221 }); |
| 222 mFindQuery.setOnEditorActionListener(new TextView.OnEditorActionListener
() { |
| 223 @Override |
| 224 public boolean onEditorAction(TextView v, int actionId, KeyEvent eve
nt) { |
| 225 if (event != null && event.getAction() == KeyEvent.ACTION_UP) re
turn false; |
| 226 |
| 227 // Only trigger a new find if the text was set programmatically. |
| 228 // Otherwise just revisit the current active match. |
| 229 if (mSearchKeyShouldTriggerSearch) { |
| 230 mSearchKeyShouldTriggerSearch = false; |
| 231 hideKeyboardAndStartFinding(true); |
| 232 } else { |
| 233 UiUtils.hideKeyboard(mFindQuery); |
| 234 mFindInPageBridge.activateFindInPageResultForAccessibility()
; |
| 235 mAccessibilityDidActivateResult = true; |
| 236 } |
| 237 return true; |
| 238 } |
| 239 }); |
| 240 |
| 241 mFindStatus = (TextView) findViewById(R.id.find_status); |
| 242 |
| 243 mFindPrevButton = (TintedImageButton) findViewById(R.id.find_prev_button
); |
| 244 mFindPrevButton.setOnClickListener(new OnClickListener() { |
| 245 @Override |
| 246 public void onClick(View v) { |
| 247 hideKeyboardAndStartFinding(false); |
| 248 } |
| 249 }); |
| 250 |
| 251 mFindNextButton = (TintedImageButton) findViewById(R.id.find_next_button
); |
| 252 mFindNextButton.setOnClickListener(new OnClickListener() { |
| 253 @Override |
| 254 public void onClick(View v) { |
| 255 hideKeyboardAndStartFinding(true); |
| 256 } |
| 257 }); |
| 258 |
| 259 setPrevNextEnabled(false); |
| 260 |
| 261 mCloseFindButton = (TintedImageButton) findViewById(R.id.close_find_butt
on); |
| 262 mCloseFindButton.setOnClickListener(new OnClickListener() { |
| 263 @Override |
| 264 public void onClick(View v) { |
| 265 deactivate(); |
| 266 } |
| 267 }); |
| 268 } |
| 269 |
| 270 // Overriden by subclasses. |
| 271 protected void findResultSelected(Rect rect) { |
| 272 } |
| 273 |
| 274 private void hideKeyboardAndStartFinding(boolean forward) { |
| 275 final String findQuery = mFindQuery.getText().toString(); |
| 276 if (findQuery.length() == 0) return; |
| 277 |
| 278 UiUtils.hideKeyboard(mFindQuery); |
| 279 mFindInPageBridge.startFinding(findQuery, forward, false); |
| 280 mFindInPageBridge.activateFindInPageResultForAccessibility(); |
| 281 mAccessibilityDidActivateResult = true; |
| 282 } |
| 283 |
| 284 private boolean mShowKeyboardOnceWindowIsFocused = false; |
| 285 |
| 286 @Override |
| 287 public void onWindowFocusChanged(boolean hasFocus) { |
| 288 super.onWindowFocusChanged(hasFocus); |
| 289 |
| 290 if (mShowKeyboardOnceWindowIsFocused) { |
| 291 mShowKeyboardOnceWindowIsFocused = false; |
| 292 // See showKeyboard() for explanation. |
| 293 // By this point we've already waited till the window regains focus |
| 294 // from the options menu, but we still need to use postDelayed with |
| 295 // a zero wait time to delay until all the side-effects are complete |
| 296 // (e.g. becoming the target of the Input Method). |
| 297 mHandler.postDelayed(new Runnable() { |
| 298 @Override |
| 299 public void run() { |
| 300 showKeyboard(); |
| 301 |
| 302 // This is also a great time to set accessibility focus to t
he query box - |
| 303 // this also fails if we don't wait until the window regains
focus. |
| 304 // Sending a HOVER_ENTER event before the ACCESSIBILITY_FOCU
SED event |
| 305 // is a widely-used hack to force TalkBack to move accessibi
lity focus |
| 306 // to a view, which is discouraged in general but reasonable
in this case. |
| 307 mFindQuery.sendAccessibilityEvent( |
| 308 AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER); |
| 309 mFindQuery.sendAccessibilityEvent( |
| 310 AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOC
USED); |
| 311 } |
| 312 }, 0); |
| 313 } |
| 314 } |
| 315 |
| 316 @Override |
| 317 public void onFindMatchRects(FindMatchRectsDetails matchRects) { |
| 318 if (mResultBar == null) return; |
| 319 if (mFindQuery.getText().length() > 0) { |
| 320 mResultBar.setMatchRects(matchRects.version, matchRects.rects, match
Rects.activeRect); |
| 321 } else { |
| 322 // Since we don't issue a request for an empty string we never get a
'no rects' response |
| 323 // in that case. This could cause us to display stale state if the u
ser is deleting the |
| 324 // search string. If the response for the last character comes in af
ter we've issued a |
| 325 // clearReslts in TextChangedListener that response will be accepted
and we will end up |
| 326 // showing stale results for an empty query. |
| 327 // Sending an empty string message seems a bit wasteful, so instead
we simply ignore all |
| 328 // results that come in if the query is empty. |
| 329 mResultBar.clearMatchRects(); |
| 330 } |
| 331 } |
| 332 |
| 333 @Override |
| 334 public void onFindResult(FindNotificationDetails result) { |
| 335 if (mResultBar != null) mResultBar.mWaitingForActivateAck = false; |
| 336 |
| 337 if ((result.activeMatchOrdinal == -1 || result.numberOfMatches == 1) |
| 338 && !result.finalUpdate) { |
| 339 // Wait until activeMatchOrdinal has been determined (is no longer |
| 340 // -1) before showing counts. Additionally, to reduce flicker, |
| 341 // ignore short-lived interim notifications with numberOfMatches set |
| 342 // to 1, which are sent as soon as something has been found (see bug |
| 343 // 894389 and FindBarController::UpdateFindBarForCurrentResult). |
| 344 // Instead wait until the scoping effort starts returning real |
| 345 // match counts (or the search actually finishes with 1 result). |
| 346 // This also protects against receiving bogus rendererSelectionRects |
| 347 // at the start (see below for why we can't filter them out). |
| 348 return; |
| 349 } |
| 350 |
| 351 if (result.finalUpdate) { |
| 352 if (result.numberOfMatches > 0) { |
| 353 // TODO(johnme): Don't wait till end of find, stream rects live! |
| 354 mFindInPageBridge.requestFindMatchRects( |
| 355 mResultBar != null ? mResultBar.mRectsVersion : -1); |
| 356 } else { |
| 357 clearResults(); |
| 358 } |
| 359 |
| 360 findResultSelected(result.rendererSelectionRect); |
| 361 } |
| 362 |
| 363 // Even though we wait above until activeMatchOrdinal is no longer -1, |
| 364 // it's possible for it to still be -1 (unknown) in the final find |
| 365 // notification. This happens very rarely, e.g. if the m_activeMatch |
| 366 // found by WebFrameImpl::find has been removed from the DOM by the time |
| 367 // WebFrameImpl::scopeStringMatches tries to find the ordinal of the |
| 368 // active match (while counting the matches), as in b/4147049. In such |
| 369 // cases it looks less broken to show 0 instead of -1 (as desktop does). |
| 370 Context context = getContext(); |
| 371 String text = context.getResources().getString( |
| 372 R.string.find_in_page_count, |
| 373 Math.max(result.activeMatchOrdinal, 0), |
| 374 result.numberOfMatches); |
| 375 setStatus(text, result.numberOfMatches == 0); |
| 376 |
| 377 // The accessible version will be something like "Result 1 of 9". |
| 378 String accessibleText = getAccessibleStatusText( |
| 379 Math.max(result.activeMatchOrdinal, 0), |
| 380 result.numberOfMatches); |
| 381 mFindStatus.setContentDescription(accessibleText); |
| 382 announceStatusForAccessibility(accessibleText); |
| 383 |
| 384 // Vibrate when no results are found, unless you're just deleting chars. |
| 385 if (result.numberOfMatches == 0 && result.finalUpdate |
| 386 && !mFindInPageBridge.getPreviousFindText().startsWith( |
| 387 mFindQuery.getText().toString())) { |
| 388 final boolean hapticFeedbackEnabled = Settings.System.getInt( |
| 389 context.getContentResolver(), |
| 390 Settings.System.HAPTIC_FEEDBACK_ENABLED, 1) == 1; |
| 391 if (hapticFeedbackEnabled) { |
| 392 Vibrator v = (Vibrator) context.getSystemService( |
| 393 Context.VIBRATOR_SERVICE); |
| 394 final long noResultsVibrateDurationMs = 50; |
| 395 v.vibrate(noResultsVibrateDurationMs); |
| 396 } |
| 397 } |
| 398 } |
| 399 |
| 400 private String getAccessibleStatusText(int activeMatchOrdinal, int numberOfM
atches) { |
| 401 Context context = getContext(); |
| 402 return (numberOfMatches > 0) |
| 403 ? context.getResources().getString( |
| 404 R.string.accessible_find_in_page_count, |
| 405 activeMatchOrdinal, |
| 406 numberOfMatches) |
| 407 : context.getResources().getString(R.string.accessible_find_in_p
age_no_results); |
| 408 } |
| 409 |
| 410 private void announceStatusForAccessibility(final String announcementText) { |
| 411 // Don't announce if the user has already activated a result by pressing
Enter/Search |
| 412 // or clicking on the Next/Previous buttons. |
| 413 if (mAccessibilityDidActivateResult) return; |
| 414 |
| 415 // Delay the announcement briefly, and if any additional announcements c
ome in, |
| 416 // have them preempt the previous queued one. That makes for a better us
er experience |
| 417 // than speaking instantly as you're typing and constantly interrupting
itself. |
| 418 |
| 419 if (mAccessibleAnnouncementRunnable != null) { |
| 420 mHandler.removeCallbacks(mAccessibleAnnouncementRunnable); |
| 421 } |
| 422 |
| 423 mAccessibleAnnouncementRunnable = new Runnable() { |
| 424 @Override |
| 425 public void run() { |
| 426 mFindQuery.announceForAccessibility(announcementText); |
| 427 } |
| 428 }; |
| 429 mHandler.postDelayed(mAccessibleAnnouncementRunnable, |
| 430 ACCESSIBLE_ANNOUNCEMENT_DELAY_MILLIS); |
| 431 } |
| 432 |
| 433 /** The find toolbar's container must provide access to its TabModel. */ |
| 434 public void setTabModelSelector(TabModelSelector modelSelector) { |
| 435 mTabModelSelector = modelSelector; |
| 436 updateVisualsForTabModel(modelSelector != null && modelSelector.isIncogn
itoSelected()); |
| 437 } |
| 438 |
| 439 /** |
| 440 * Handles updating any visual elements of the find toolbar based on changes
to the tab model. |
| 441 * @param isIncognito Whether the current tab model is incognito or not. |
| 442 */ |
| 443 protected void updateVisualsForTabModel(boolean isIncognito) { |
| 444 } |
| 445 |
| 446 /** |
| 447 * Sets a custom ActionMode.Callback instance to the FindQuery. This lets u
s |
| 448 * get notified when the user tries to do copy, paste, etc. on the FindQuery
. |
| 449 * @param callback The ActionMode.Callback instance to be notified when sele
ction ActionMode |
| 450 * is triggered. |
| 451 */ |
| 452 public void setActionModeCallbackForTextEdit(ActionMode.Callback callback) { |
| 453 mFindQuery.setCustomSelectionActionModeCallback(callback); |
| 454 } |
| 455 |
| 456 /** |
| 457 * Sets the observer to be notified of changes to the find toolbar. |
| 458 */ |
| 459 protected void setObserver(FindToolbarObserver observer) { |
| 460 mObserver = observer; |
| 461 } |
| 462 |
| 463 /** |
| 464 * Checks to see if a ContentViewCore is available to hook into. |
| 465 */ |
| 466 protected boolean isViewAvailable() { |
| 467 Tab currentTab = mTabModelSelector.getCurrentTab(); |
| 468 return currentTab != null && currentTab.getContentViewCore() != null; |
| 469 } |
| 470 |
| 471 /** |
| 472 * Initializes the find toolbar. Should be called just after the find toolba
r is shown. |
| 473 * If the toolbar is already showing, this just focuses the toolbar. |
| 474 */ |
| 475 public void activate() { |
| 476 if (!isViewAvailable()) return; |
| 477 if (mActive) { |
| 478 requestQueryFocus(); |
| 479 return; |
| 480 } |
| 481 |
| 482 mTabModelSelector.addObserver(mTabModelSelectorObserver); |
| 483 for (TabModel model : mTabModelSelector.getModels()) { |
| 484 model.addObserver(mTabModelObserver); |
| 485 } |
| 486 mCurrentTab = mTabModelSelector.getCurrentTab(); |
| 487 mCurrentTab.addObserver(mTabObserver); |
| 488 mFindInPageBridge = new FindInPageBridge(mCurrentTab.getWebContents()); |
| 489 mCurrentTab.getChromeWebContentsDelegateAndroid().setFindResultListener(
this); |
| 490 mCurrentTab.getChromeWebContentsDelegateAndroid().setFindMatchRectsListe
ner(this); |
| 491 initializeFindText(); |
| 492 mFindQuery.requestFocus(); |
| 493 // The keyboard doesn't show itself automatically. |
| 494 showKeyboard(); |
| 495 // Always show the bar to make the FindToolbar more distinct from the Om
nibox. |
| 496 setResultsBarVisibility(true); |
| 497 mActive = true; |
| 498 updateVisualsForTabModel(mTabModelSelector.isIncognitoSelected()); |
| 499 |
| 500 // Let everyone know that we've just updated. |
| 501 if (mObserver != null) mObserver.onFindToolbarShown(); |
| 502 } |
| 503 |
| 504 /** Call this just before closing the find toolbar. */ |
| 505 public void deactivate() { |
| 506 if (!mActive) return; |
| 507 |
| 508 if (mObserver != null) mObserver.onFindToolbarHidden(); |
| 509 |
| 510 setResultsBarVisibility(false); |
| 511 |
| 512 mTabModelSelector.removeObserver(mTabModelSelectorObserver); |
| 513 for (TabModel model : mTabModelSelector.getModels()) { |
| 514 model.removeObserver(mTabModelObserver); |
| 515 } |
| 516 |
| 517 mCurrentTab.getChromeWebContentsDelegateAndroid().setFindResultListener(
null); |
| 518 mCurrentTab.getChromeWebContentsDelegateAndroid().setFindMatchRectsListe
ner(null); |
| 519 mCurrentTab.removeObserver(mTabObserver); |
| 520 |
| 521 UiUtils.hideKeyboard(mFindQuery); |
| 522 if (mFindQuery.getText().length() > 0) { |
| 523 clearResults(); |
| 524 mFindInPageBridge.stopFinding(); |
| 525 } |
| 526 |
| 527 mFindInPageBridge.destroy(); |
| 528 mActive = false; |
| 529 } |
| 530 |
| 531 /** |
| 532 * Requests focus for the query input field and shows the keyboard. |
| 533 */ |
| 534 public void requestQueryFocus() { |
| 535 mFindQuery.requestFocus(); |
| 536 showKeyboard(); |
| 537 } |
| 538 |
| 539 /** Called by the tablet-specific implementation when the hide animation is
about to begin. */ |
| 540 protected void onHideAnimationStart() { |
| 541 // We do this because hiding the bar after the animation ends doesn't lo
ok good. |
| 542 setResultsBarVisibility(false); |
| 543 } |
| 544 |
| 545 @VisibleForTesting |
| 546 public FindResultBar getFindResultBar() { |
| 547 return mResultBar; |
| 548 } |
| 549 |
| 550 /** |
| 551 * Returns whether an animation to show/hide the FindToolbar is currently ru
nning. |
| 552 */ |
| 553 @VisibleForTesting |
| 554 public boolean isAnimating() { |
| 555 return false; |
| 556 } |
| 557 |
| 558 /** |
| 559 * Restores the last text searched in this tab, or the global last search. |
| 560 */ |
| 561 private void initializeFindText() { |
| 562 mSettingFindTextProgrammatically = true; |
| 563 String findText = null; |
| 564 if (mSettingFindTextProgrammatically) { |
| 565 findText = mFindInPageBridge.getPreviousFindText(); |
| 566 if (findText.isEmpty() && !mCurrentTab.isIncognito()) { |
| 567 findText = mLastUserSearch; |
| 568 } |
| 569 mSearchKeyShouldTriggerSearch = true; |
| 570 } else { |
| 571 mSearchKeyShouldTriggerSearch = false; |
| 572 } |
| 573 mFindQuery.setText(findText); |
| 574 mSettingFindTextProgrammatically = false; |
| 575 } |
| 576 |
| 577 /** Clears the result displays (except in-page match highlighting). */ |
| 578 protected void clearResults() { |
| 579 setStatus("", false); |
| 580 if (mResultBar != null) { |
| 581 mResultBar.clearMatchRects(); |
| 582 } |
| 583 } |
| 584 |
| 585 private void setResultsBarVisibility(boolean visibility) { |
| 586 if (visibility && mResultBar == null && mCurrentTab != null |
| 587 && mCurrentTab.getContentViewCore() != null) { |
| 588 mResultBar = new FindResultBar(getContext(), mCurrentTab, mFindInPag
eBridge); |
| 589 } else if (!visibility) { |
| 590 if (mResultBar != null) { |
| 591 mResultBar.dismiss(); |
| 592 mResultBar = null; |
| 593 } |
| 594 } |
| 595 } |
| 596 |
| 597 private void setStatus(String text, boolean failed) { |
| 598 mFindStatus.setText(text); |
| 599 mFindStatus.setContentDescription(null); |
| 600 boolean incognito = mTabModelSelector != null && mTabModelSelector.isInc
ognitoSelected(); |
| 601 mFindStatus.setTextColor(getStatusColor(failed, incognito)); |
| 602 } |
| 603 |
| 604 /** |
| 605 * @param failed Whether or not the find query had any matching results. |
| 606 * @param incognito Whether or not the current tab is incognito. |
| 607 * @return The color of the status text. |
| 608 */ |
| 609 protected int getStatusColor(boolean failed, boolean incognito) { |
| 610 int colorResourceId = failed ? R.color.find_in_page_failed_results_statu
s_color |
| 611 : R.color.find_in_page_results_status_color; |
| 612 return getContext().getResources().getColor(colorResourceId); |
| 613 } |
| 614 |
| 615 protected void setPrevNextEnabled(boolean enable) { |
| 616 mFindPrevButton.setEnabled(enable); |
| 617 mFindNextButton.setEnabled(enable); |
| 618 } |
| 619 |
| 620 private void showKeyboard() { |
| 621 if (!mFindQuery.hasWindowFocus()) { |
| 622 // HACK: showKeyboard() is normally called from activate() which is |
| 623 // triggered by an options menu item. Unfortunately, because the |
| 624 // options menu is still focused at this point, that means our |
| 625 // window doesn't actually have focus when this first gets called, |
| 626 // and hence it isn't the target of the Input Method, and in |
| 627 // practice that means the soft keyboard never shows up (whatever |
| 628 // flags you pass). So as a workaround we postpone asking for the |
| 629 // keyboard to be shown until just after the window gets refocused. |
| 630 // See onWindowFocusChanged(boolean hasFocus). |
| 631 mShowKeyboardOnceWindowIsFocused = true; |
| 632 return; |
| 633 } |
| 634 UiUtils.showKeyboard(mFindQuery); |
| 635 } |
| 636 } |
OLD | NEW |