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.omnibox; |
| 6 |
| 7 import static org.chromium.chrome.browser.toolbar.ToolbarPhone.URL_FOCUS_CHANGE_
ANIMATION_DURATION_MS; |
| 8 |
| 9 import android.animation.Animator; |
| 10 import android.animation.AnimatorListenerAdapter; |
| 11 import android.animation.AnimatorSet; |
| 12 import android.animation.ObjectAnimator; |
| 13 import android.annotation.SuppressLint; |
| 14 import android.app.Activity; |
| 15 import android.content.ClipData; |
| 16 import android.content.ClipboardManager; |
| 17 import android.content.ContentResolver; |
| 18 import android.content.Context; |
| 19 import android.content.Intent; |
| 20 import android.content.res.ColorStateList; |
| 21 import android.graphics.Color; |
| 22 import android.graphics.PorterDuff; |
| 23 import android.graphics.Rect; |
| 24 import android.graphics.drawable.ColorDrawable; |
| 25 import android.graphics.drawable.Drawable; |
| 26 import android.net.Uri; |
| 27 import android.os.Parcelable; |
| 28 import android.os.SystemClock; |
| 29 import android.provider.Settings; |
| 30 import android.speech.RecognizerIntent; |
| 31 import android.text.Editable; |
| 32 import android.text.InputType; |
| 33 import android.text.Selection; |
| 34 import android.text.TextUtils; |
| 35 import android.text.TextWatcher; |
| 36 import android.util.AttributeSet; |
| 37 import android.util.Log; |
| 38 import android.util.Pair; |
| 39 import android.util.SparseArray; |
| 40 import android.view.ActionMode; |
| 41 import android.view.KeyEvent; |
| 42 import android.view.LayoutInflater; |
| 43 import android.view.Menu; |
| 44 import android.view.MenuItem; |
| 45 import android.view.MotionEvent; |
| 46 import android.view.View; |
| 47 import android.view.View.OnClickListener; |
| 48 import android.view.ViewGroup; |
| 49 import android.view.ViewStub; |
| 50 import android.view.inputmethod.BaseInputConnection; |
| 51 import android.widget.FrameLayout; |
| 52 import android.widget.ImageButton; |
| 53 import android.widget.ImageView; |
| 54 import android.widget.ListView; |
| 55 |
| 56 import com.google.android.apps.chrome.R; |
| 57 |
| 58 import org.chromium.base.ApiCompatibilityUtils; |
| 59 import org.chromium.base.CollectionUtil; |
| 60 import org.chromium.base.CommandLine; |
| 61 import org.chromium.base.VisibleForTesting; |
| 62 import org.chromium.base.metrics.RecordHistogram; |
| 63 import org.chromium.base.metrics.RecordUserAction; |
| 64 import org.chromium.chrome.browser.ChromeSwitches; |
| 65 import org.chromium.chrome.browser.ContextualMenuBar; |
| 66 import org.chromium.chrome.browser.ContextualMenuBar.ActionBarDelegate; |
| 67 import org.chromium.chrome.browser.CustomSelectionActionModeCallback; |
| 68 import org.chromium.chrome.browser.Tab; |
| 69 import org.chromium.chrome.browser.WebsiteSettingsPopup; |
| 70 import org.chromium.chrome.browser.WindowDelegate; |
| 71 import org.chromium.chrome.browser.appmenu.AppMenuButtonHelper; |
| 72 import org.chromium.chrome.browser.document.BrandColorUtils; |
| 73 import org.chromium.chrome.browser.dom_distiller.DomDistillerServiceFactory; |
| 74 import org.chromium.chrome.browser.dom_distiller.DomDistillerTabUtils; |
| 75 import org.chromium.chrome.browser.ntp.NativePageFactory; |
| 76 import org.chromium.chrome.browser.ntp.NewTabPage; |
| 77 import org.chromium.chrome.browser.ntp.NewTabPage.FakeboxDelegate; |
| 78 import org.chromium.chrome.browser.ntp.NewTabPageUma; |
| 79 import org.chromium.chrome.browser.omnibox.AutocompleteController.OnSuggestionsR
eceivedListener; |
| 80 import org.chromium.chrome.browser.omnibox.OmniboxResultsAdapter.OmniboxResultIt
em; |
| 81 import org.chromium.chrome.browser.omnibox.OmniboxResultsAdapter.OmniboxSuggesti
onDelegate; |
| 82 import org.chromium.chrome.browser.omnibox.OmniboxSuggestion.Type; |
| 83 import org.chromium.chrome.browser.omnibox.VoiceSuggestionProvider.VoiceResult; |
| 84 import org.chromium.chrome.browser.omnibox.geo.GeolocationHeader; |
| 85 import org.chromium.chrome.browser.preferences.privacy.PrivacyPreferencesManager
; |
| 86 import org.chromium.chrome.browser.profiles.Profile; |
| 87 import org.chromium.chrome.browser.search_engines.TemplateUrlService; |
| 88 import org.chromium.chrome.browser.ssl.ConnectionSecurityHelperSecurityLevel; |
| 89 import org.chromium.chrome.browser.tab.BackgroundContentViewHelper; |
| 90 import org.chromium.chrome.browser.tab.ChromeTab; |
| 91 import org.chromium.chrome.browser.toolbar.ToolbarDataProvider; |
| 92 import org.chromium.chrome.browser.util.FeatureUtilities; |
| 93 import org.chromium.chrome.browser.util.KeyNavigationUtil; |
| 94 import org.chromium.chrome.browser.util.ViewUtils; |
| 95 import org.chromium.chrome.browser.widget.TintedImageButton; |
| 96 import org.chromium.components.dom_distiller.core.DomDistillerService; |
| 97 import org.chromium.components.dom_distiller.core.DomDistillerUrlUtils; |
| 98 import org.chromium.content.browser.ContentViewCore; |
| 99 import org.chromium.content.browser.accessibility.BrowserAccessibilityManager; |
| 100 import org.chromium.content_public.browser.LoadUrlParams; |
| 101 import org.chromium.content_public.browser.WebContents; |
| 102 import org.chromium.ui.UiUtils; |
| 103 import org.chromium.ui.base.DeviceFormFactor; |
| 104 import org.chromium.ui.base.PageTransition; |
| 105 import org.chromium.ui.base.WindowAndroid; |
| 106 import org.chromium.ui.interpolators.BakedBezierInterpolator; |
| 107 |
| 108 import java.util.ArrayList; |
| 109 import java.util.HashSet; |
| 110 import java.util.List; |
| 111 |
| 112 /** |
| 113 * This class represents the location bar where the user types in URLs and |
| 114 * search terms. |
| 115 */ |
| 116 public class LocationBarLayout extends FrameLayout implements OnClickListener, |
| 117 OnSuggestionsReceivedListener, LocationBar, FakeboxDelegate, |
| 118 WindowAndroid.IntentCallback { |
| 119 |
| 120 // Delay triggering the omnibox results upon key press to allow the location
bar to repaint |
| 121 // with the new characters. |
| 122 private static final long OMNIBOX_SUGGESTION_START_DELAY_MS = 30; |
| 123 |
| 124 private static final int OMNIBOX_CONTAINER_BACKGROUND_FADE_MS = 250; |
| 125 |
| 126 // The minimum confidence threshold that will result in navigating directly
to a voice search |
| 127 // response (as opposed to treating it like a typed string in the Omnibox). |
| 128 private static final float VOICE_SEARCH_CONFIDENCE_NAVIGATE_THRESHOLD = 0.9f
; |
| 129 |
| 130 private static final int CONTENT_OVERLAY_COLOR = Color.argb(166, 0, 0, 0); |
| 131 private static final int OMNIBOX_RESULTS_BG_COLOR = Color.rgb(245, 245, 246)
; |
| 132 private static final int OMNIBOX_INCOGNITO_RESULTS_BG_COLOR = Color.rgb(50,
50, 50); |
| 133 |
| 134 /** |
| 135 * URI schemes that ContentView can handle. |
| 136 * |
| 137 * Copied from UrlUtilities.java. UrlUtilities uses a URI to check for sche
mes, which |
| 138 * is more strict than Uri and causes the path stripping to fail. |
| 139 * |
| 140 * The following additions have been made: "chrome", "ftp". |
| 141 */ |
| 142 private static final HashSet<String> ACCEPTED_SCHEMES = CollectionUtil.newHa
shSet( |
| 143 "about", "data", "file", "ftp", "http", "https", "inline", "javascri
pt", "chrome"); |
| 144 private static final HashSet<String> UNSUPPORTED_SCHEMES_TO_SPLIT = |
| 145 CollectionUtil.newHashSet("file", "javascript", "data"); |
| 146 |
| 147 protected ImageView mNavigationButton; |
| 148 protected ImageButton mSecurityButton; |
| 149 protected TintedImageButton mDeleteButton; |
| 150 protected TintedImageButton mMicButton; |
| 151 protected UrlBar mUrlBar; |
| 152 protected UrlContainer mUrlContainer; |
| 153 private ContextualMenuBar mContextualMenuBar = null; |
| 154 |
| 155 private AutocompleteController mAutocomplete; |
| 156 |
| 157 private ToolbarDataProvider mToolbarDataProvider; |
| 158 private UrlFocusChangeListener mUrlFocusChangeListener; |
| 159 |
| 160 private boolean mNativeInitialized; |
| 161 |
| 162 private final List<Runnable> mDeferredNativeRunnables = new ArrayList<Runnab
le>(); |
| 163 |
| 164 // The type of the navigation button currently showing. |
| 165 private NavigationButtonType mNavigationButtonType; |
| 166 |
| 167 // The type of the security icon currently active. |
| 168 private int mSecurityIconType; |
| 169 |
| 170 private final OmniboxResultsAdapter mSuggestionListAdapter; |
| 171 private OmniboxSuggestionsList mSuggestionList; |
| 172 |
| 173 private final List<OmniboxResultItem> mSuggestionItems; |
| 174 |
| 175 /** |
| 176 * The text shown in the URL bar (user text + inline autocomplete) after the
most recent set of |
| 177 * omnibox suggestions was received. When the user presses enter in the omni
box, this value is |
| 178 * compared to the URL bar text to determine whether the first suggestion is
still valid. |
| 179 */ |
| 180 private String mUrlTextAfterSuggestionsReceived; |
| 181 |
| 182 // Set to true when the URL bar text is modified programmatically. Initially
set |
| 183 // to true until the old state has been loaded. |
| 184 private boolean mIgnoreURLBarModification = true; |
| 185 private boolean mIgnoreOmniboxItemSelection = true; |
| 186 |
| 187 private boolean mLastUrlEditWasDelete = false; |
| 188 |
| 189 // True if we are showing the search query instead of the url. |
| 190 private boolean mQueryInTheOmnibox = false; |
| 191 |
| 192 private String mOriginalUrl = ""; |
| 193 |
| 194 private WindowAndroid mWindowAndroid; |
| 195 private WindowDelegate mWindowDelegate; |
| 196 |
| 197 private Runnable mRequestSuggestions; |
| 198 |
| 199 private ViewGroup mOmniboxResultsContainer; |
| 200 private ObjectAnimator mFadeInOmniboxBackgroundAnimator; |
| 201 private ObjectAnimator mFadeOutOmniboxBackgroundAnimator; |
| 202 private Animator mOmniboxBackgroundAnimator; |
| 203 |
| 204 private boolean mSuggestionsShown; |
| 205 private boolean mUrlHasFocus; |
| 206 private boolean mUrlFocusedFromFakebox; |
| 207 private boolean mHasRecordedUrlFocusSource; |
| 208 |
| 209 // Set to true when the user has started typing new input in the omnibox, se
t to false |
| 210 // when the omnibox loses focus or becomes empty. |
| 211 private boolean mHasStartedNewOmniboxEditSession; |
| 212 // The timestamp (using SystemClock.elapsedRealtime()) at the point when the
user started |
| 213 // modifying the omnibox with new input. |
| 214 private long mNewOmniboxEditSessionTimestamp = -1; |
| 215 |
| 216 private boolean mSecurityButtonShown; |
| 217 |
| 218 private AnimatorSet mLocationBarIconActiveAnimator; |
| 219 private AnimatorSet mSecurityButtonShowAnimator; |
| 220 private AnimatorSet mNavigationIconShowAnimator; |
| 221 |
| 222 private TextWatcher mTextWatcher; |
| 223 |
| 224 private OmniboxPrerender mOmniboxPrerender; |
| 225 |
| 226 private View mFocusedTabView; |
| 227 private int mFocusedTabImportantForAccessibilityState; |
| 228 private BrowserAccessibilityManager mFocusedTabAccessibilityManager; |
| 229 |
| 230 // True if we are showing original url for preview page. This is will be tru
e when there is a |
| 231 // background page loaded in background content view. |
| 232 private boolean mShowingOriginalUrlForPreview; |
| 233 |
| 234 private boolean mSuggestionModalShown; |
| 235 private boolean mUseDarkColors; |
| 236 |
| 237 // True if the user has just selected a suggestion from the suggestion list.
This suppresses |
| 238 // the recording of the dismissal of the suggestion list. (The list is only
considered to have |
| 239 // been dismissed if the user didn't choose one of the suggestions shown.) T
his signal is used |
| 240 // instead of a parameter to hideSuggestions because that method is often ca
lled from multiple |
| 241 // code paths in a not necessarily obvious or even deterministic order. |
| 242 private boolean mSuggestionSelectionInProgress; |
| 243 |
| 244 private CustomSelectionActionModeCallback mDefaultActionModeCallbackForTextE
dit; |
| 245 |
| 246 /** |
| 247 * Listener for receiving the messages related with interacting with the omn
ibox during startup. |
| 248 */ |
| 249 public interface OmniboxLivenessListener { |
| 250 /** |
| 251 * Called after the first draw when the omnibox can receive touch events
. |
| 252 */ |
| 253 void onOmniboxInteractive(); |
| 254 |
| 255 /** |
| 256 * Called when the native libraries are loaded and listeners with native
components |
| 257 * have been initialized. |
| 258 */ |
| 259 void onOmniboxFullyFunctional(); |
| 260 |
| 261 /** |
| 262 * Called when the omnibox is focused. |
| 263 */ |
| 264 void onOmniboxFocused(); |
| 265 } |
| 266 |
| 267 /** |
| 268 * Class to handle text changes in the URL bar, ensuring that the appropriat
e |
| 269 * buttons are displayed as the text changes, and requesting suggestions. |
| 270 */ |
| 271 private final class UrlBarTextWatcher implements TextWatcher { |
| 272 @Override |
| 273 public void afterTextChanged(final Editable editableText) { |
| 274 updateDeleteButtonVisibility(); |
| 275 updateNavigationButton(); |
| 276 |
| 277 if (mIgnoreURLBarModification) return; |
| 278 |
| 279 if (!mHasStartedNewOmniboxEditSession && mNativeInitialized) { |
| 280 RecordUserAction.record("MobileFirstEditInOmnibox"); |
| 281 mAutocomplete.resetSession(); |
| 282 mHasStartedNewOmniboxEditSession = true; |
| 283 mNewOmniboxEditSessionTimestamp = SystemClock.elapsedRealtime(); |
| 284 } |
| 285 |
| 286 if (!isInTouchMode() && mSuggestionList != null) { |
| 287 mSuggestionList.setSelection(0); |
| 288 } |
| 289 |
| 290 final String textWithoutAutocomplete = mUrlBar.getTextWithoutAutocom
plete(); |
| 291 |
| 292 stopAutocomplete(false); |
| 293 if (TextUtils.isEmpty(textWithoutAutocomplete)) { |
| 294 hideSuggestions(); |
| 295 startZeroSuggest(); |
| 296 } else { |
| 297 assert mRequestSuggestions == null : "Multiple omnibox requests
in flight."; |
| 298 mRequestSuggestions = new Runnable() { |
| 299 @Override |
| 300 public void run() { |
| 301 boolean preventAutocomplete = !shouldAutocomplete() |
| 302 || (editableText != null && Selection.getSelecti
onEnd(editableText) |
| 303 != editableText.length()); |
| 304 mRequestSuggestions = null; |
| 305 if (getCurrentTab() == null) return; |
| 306 mAutocomplete.start( |
| 307 getCurrentTab().getProfile(), |
| 308 getCurrentTab().getUrl(), |
| 309 textWithoutAutocomplete, preventAutocomplete); |
| 310 } |
| 311 }; |
| 312 if (mNativeInitialized) { |
| 313 postDelayed(mRequestSuggestions, OMNIBOX_SUGGESTION_START_DE
LAY_MS); |
| 314 } else { |
| 315 mDeferredNativeRunnables.add(mRequestSuggestions); |
| 316 } |
| 317 } |
| 318 |
| 319 // Occasionally, was seeing the selection in the URL not being clear
ed during |
| 320 // very rapid editing. This is here to hopefully force a selection
reset during |
| 321 // deletes. |
| 322 if (mLastUrlEditWasDelete) mUrlBar.setSelection(mUrlBar.getSelection
Start()); |
| 323 } |
| 324 |
| 325 @Override |
| 326 public void beforeTextChanged(CharSequence s, int start, int count, int
after) { |
| 327 cancelPendingAutocompleteStart(); |
| 328 } |
| 329 |
| 330 @Override |
| 331 public void onTextChanged(CharSequence s, int start, int before, int cou
nt) { |
| 332 // We need to determine whether the text change was triggered by a d
elete (so we |
| 333 // don't autocomplete of the entered text in that case). With soft-k
eyboard, there |
| 334 // is no way to know that the delete button was pressed, so we track
text removal |
| 335 // changes. |
| 336 mLastUrlEditWasDelete = (count == 0); |
| 337 } |
| 338 } |
| 339 |
| 340 /** |
| 341 * Class to handle input from a hardware keyboard when the focus is on the U
RL bar. In |
| 342 * particular, handle navigating the suggestions list from the keyboard. |
| 343 */ |
| 344 private final class UrlBarKeyListener implements OnKeyListener { |
| 345 @Override |
| 346 public boolean onKey(View v, int keyCode, KeyEvent event) { |
| 347 if (KeyNavigationUtil.isGoDown(event) |
| 348 && mSuggestionList != null |
| 349 && mSuggestionList.isShown()) { |
| 350 int suggestionCount = mSuggestionListAdapter.getCount(); |
| 351 if (mSuggestionList.getSelectedItemPosition() < suggestionCount
- 1) { |
| 352 if (suggestionCount > 0) mIgnoreOmniboxItemSelection = false
; |
| 353 } else { |
| 354 // Do not pass down events when the last item is already sel
ected as it will |
| 355 // dismiss the suggestion list. |
| 356 return true; |
| 357 } |
| 358 |
| 359 if (mSuggestionList.getSelectedItemPosition() |
| 360 == ListView.INVALID_POSITION) { |
| 361 // When clearing the selection after a text change, state is
not reset |
| 362 // correctly so hitting down again will cause it to start fr
om the previous |
| 363 // selection point. We still have to send the key down event
to let the list |
| 364 // view items take focus, but then we select the first item
explicitly. |
| 365 boolean result = mSuggestionList.onKeyDown(keyCode, event); |
| 366 mSuggestionList.setSelection(0); |
| 367 return result; |
| 368 } else { |
| 369 return mSuggestionList.onKeyDown(keyCode, event); |
| 370 } |
| 371 } else if (KeyNavigationUtil.isGoUp(event) |
| 372 && mSuggestionList != null |
| 373 && mSuggestionList.isShown()) { |
| 374 if (mSuggestionList.getSelectedItemPosition() != 0 |
| 375 && mSuggestionListAdapter.getCount() > 0) { |
| 376 mIgnoreOmniboxItemSelection = false; |
| 377 } |
| 378 return mSuggestionList.onKeyDown(keyCode, event); |
| 379 } else if (KeyNavigationUtil.isGoRight(event) |
| 380 && mSuggestionList != null |
| 381 && mSuggestionList.isShown() |
| 382 && mSuggestionList.getSelectedItemPosition() |
| 383 != ListView.INVALID_POSITION) { |
| 384 OmniboxResultItem selectedItem = |
| 385 (OmniboxResultItem) mSuggestionListAdapter.getItem( |
| 386 mSuggestionList.getSelectedItemPosition()); |
| 387 // Set the UrlBar text to empty, so that it will trigger a text
change when we |
| 388 // set the text to the suggestion again. |
| 389 setUrlBarText(null, null, ""); |
| 390 mUrlBar.setText(selectedItem.getSuggestion().getFillIntoEdit()); |
| 391 mSuggestionList.setSelection(0); |
| 392 mUrlBar.setSelection(mUrlBar.getText().length()); |
| 393 return true; |
| 394 } else if (KeyNavigationUtil.isEnter(event) |
| 395 && LocationBarLayout.this.getVisibility() == VISIBLE) { |
| 396 UiUtils.hideKeyboard(mUrlBar); |
| 397 mSuggestionSelectionInProgress = true; |
| 398 final String urlText = mUrlBar.getQueryText(); |
| 399 if (mNativeInitialized) { |
| 400 findMatchAndLoadUrl(urlText); |
| 401 } else { |
| 402 mDeferredNativeRunnables.add(new Runnable() { |
| 403 @Override |
| 404 public void run() { |
| 405 findMatchAndLoadUrl(urlText); |
| 406 } |
| 407 }); |
| 408 } |
| 409 return true; |
| 410 } |
| 411 |
| 412 return false; |
| 413 } |
| 414 |
| 415 private void findMatchAndLoadUrl(String urlText) { |
| 416 int suggestionMatchPosition; |
| 417 OmniboxSuggestion suggestionMatch; |
| 418 |
| 419 if (mSuggestionList != null |
| 420 && mSuggestionList.isShown() |
| 421 && mSuggestionList.getSelectedItemPosition() |
| 422 != ListView.INVALID_POSITION) { |
| 423 // Bluetooth keyboard case: the user highlighted a suggestion wi
th the arrow |
| 424 // keys, then pressed enter. |
| 425 suggestionMatchPosition = mSuggestionList.getSelectedItemPositio
n(); |
| 426 OmniboxResultItem selectedItem = |
| 427 (OmniboxResultItem) mSuggestionListAdapter.getItem(sugge
stionMatchPosition); |
| 428 suggestionMatch = selectedItem.getSuggestion(); |
| 429 } else if (!mSuggestionItems.isEmpty() |
| 430 && urlText.equals(mUrlTextAfterSuggestionsReceived)) { |
| 431 // Common case: the user typed something, received suggestions,
then pressed enter. |
| 432 suggestionMatch = mSuggestionItems.get(0).getSuggestion(); |
| 433 suggestionMatchPosition = 0; |
| 434 } else { |
| 435 // Less common case: there are no valid omnibox suggestions. Thi
s can happen if the |
| 436 // user tapped the URL bar to dismiss the suggestions, then pres
sed enter. This can |
| 437 // also happen if the user presses enter before any suggestions
have been received |
| 438 // from the autocomplete controller. |
| 439 suggestionMatch = mAutocomplete.classify(urlText); |
| 440 suggestionMatchPosition = 0; |
| 441 |
| 442 // If urlText couldn't be classified, bail. |
| 443 if (suggestionMatch == null) return; |
| 444 } |
| 445 |
| 446 String suggestionMatchUrl = updateSuggestionUrlIfNeeded(suggestionMa
tch, |
| 447 suggestionMatchPosition); |
| 448 |
| 449 // It's important to use the page transition from the suggestion or
we might end |
| 450 // up saving generated URLs as typed URLs, which would then pollute
the subsequent |
| 451 // omnibox results. |
| 452 loadUrlFromOmniboxMatch(suggestionMatchUrl, suggestionMatch.getTrans
ition(), |
| 453 suggestionMatchPosition, suggestionMatch.getType()); |
| 454 } |
| 455 } |
| 456 |
| 457 /** |
| 458 * Specifies the types of buttons shown to signify different types of naviga
tion elements. |
| 459 */ |
| 460 protected enum NavigationButtonType { |
| 461 PAGE, |
| 462 MAGNIFIER, |
| 463 EMPTY, |
| 464 } |
| 465 |
| 466 /** |
| 467 * @param outRect Populated with a {@link Rect} that represents the {@link T
ab} specific content |
| 468 * of this {@link LocationBar}. |
| 469 */ |
| 470 public void getContentRect(Rect outRect) { |
| 471 outRect.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRi
ght(), |
| 472 getHeight() - getPaddingBottom()); |
| 473 } |
| 474 |
| 475 /** |
| 476 * A widget for showing a list of omnibox suggestions. |
| 477 */ |
| 478 @VisibleForTesting |
| 479 public class OmniboxSuggestionsList extends ListView { |
| 480 private final int mSuggestionHeight; |
| 481 private final int mSuggestionAnswerHeight; |
| 482 private final View mAnchorView; |
| 483 |
| 484 private final int[] mTempPosition = new int[2]; |
| 485 private final Rect mTempRect = new Rect(); |
| 486 |
| 487 private final int mBackgroundVerticalPadding; |
| 488 |
| 489 private float mMaxRequiredWidth; |
| 490 private float mMaxMatchContentsWidth; |
| 491 |
| 492 /** |
| 493 * Constructs a new list designed for containing omnibox suggestions. |
| 494 * @param context Context used for contained views. |
| 495 */ |
| 496 public OmniboxSuggestionsList(Context context) { |
| 497 super(context, null, android.R.attr.dropDownListViewStyle); |
| 498 setDivider(null); |
| 499 setFocusable(true); |
| 500 setFocusableInTouchMode(true); |
| 501 |
| 502 mSuggestionHeight = context.getResources().getDimensionPixelOffset( |
| 503 R.dimen.omnibox_suggestion_height); |
| 504 mSuggestionAnswerHeight = context.getResources().getDimensionPixelOf
fset( |
| 505 R.dimen.omnibox_suggestion_answer_height); |
| 506 |
| 507 int paddingTop = context.getResources().getDimensionPixelOffset( |
| 508 R.dimen.omnibox_suggestion_list_padding_top); |
| 509 int paddingBottom = context.getResources().getDimensionPixelOffset( |
| 510 R.dimen.omnibox_suggestion_list_padding_bottom); |
| 511 ApiCompatibilityUtils.setPaddingRelative(this, 0, paddingTop, 0, pad
dingBottom); |
| 512 |
| 513 Drawable background = getSuggestionPopupBackground(); |
| 514 setBackground(background); |
| 515 background.getPadding(mTempRect); |
| 516 |
| 517 mBackgroundVerticalPadding = |
| 518 mTempRect.top + mTempRect.bottom + getPaddingTop() + getPadd
ingBottom(); |
| 519 |
| 520 mAnchorView = LocationBarLayout.this.getRootView().findViewById(R.id
.toolbar); |
| 521 } |
| 522 |
| 523 private void show() { |
| 524 updateLayoutParams(); |
| 525 if (getVisibility() != VISIBLE) { |
| 526 mIgnoreOmniboxItemSelection = true; // Reset to default value. |
| 527 setVisibility(VISIBLE); |
| 528 if (getSelectedItemPosition() != 0) setSelection(0); |
| 529 } |
| 530 updateSuggestionsLayoutDirection(mUrlBar.getUrlDirection()); |
| 531 } |
| 532 |
| 533 /** |
| 534 * Invalidates all of the suggestion views in the list. Only applicable
when this |
| 535 * is visible. |
| 536 */ |
| 537 public void invalidateSuggestionViews() { |
| 538 if (!isShown()) return; |
| 539 ListView suggestionsList = mSuggestionList; |
| 540 for (int i = 0; i < suggestionsList.getChildCount(); i++) { |
| 541 if (suggestionsList.getChildAt(i) instanceof SuggestionView) { |
| 542 suggestionsList.getChildAt(i).postInvalidateOnAnimation(); |
| 543 } |
| 544 } |
| 545 } |
| 546 |
| 547 /** |
| 548 * Updates the maximum widths required to render the suggestions. |
| 549 * This is needed for infinite suggestions where we try to vertically al
ign the leading |
| 550 * ellipsis. |
| 551 */ |
| 552 public void resetMaxTextWidths() { |
| 553 mMaxRequiredWidth = 0; |
| 554 mMaxMatchContentsWidth = 0; |
| 555 } |
| 556 |
| 557 /** |
| 558 * Updates the max text width values for the suggestions. |
| 559 * @param requiredWidth a new required width. |
| 560 * @param matchContentsWidth a new match contents width. |
| 561 */ |
| 562 public void updateMaxTextWidths(float requiredWidth, float matchContents
Width) { |
| 563 mMaxRequiredWidth = Math.max(mMaxRequiredWidth, requiredWidth); |
| 564 mMaxMatchContentsWidth = Math.max(mMaxMatchContentsWidth, matchConte
ntsWidth); |
| 565 } |
| 566 |
| 567 /** |
| 568 * @return max required width for the suggestions. |
| 569 */ |
| 570 public float getMaxRequiredWidth() { |
| 571 return mMaxRequiredWidth; |
| 572 } |
| 573 |
| 574 /** |
| 575 * @return max match contents width for the suggestions. |
| 576 */ |
| 577 public float getMaxMatchContentsWidth() { |
| 578 return mMaxMatchContentsWidth; |
| 579 } |
| 580 |
| 581 private void updateLayoutParams() { |
| 582 boolean updateLayout = false; |
| 583 FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) g
etLayoutParams(); |
| 584 if (layoutParams == null) { |
| 585 layoutParams = new FrameLayout.LayoutParams(0, 0); |
| 586 setLayoutParams(layoutParams); |
| 587 } |
| 588 |
| 589 // Compare the relative positions of the anchor view to the list par
ent view to |
| 590 // determine the offset to apply to the suggestions list. By using
layout positioning, |
| 591 // this avoids issues where getLocationInWindow can be inaccurate on
certain devices. |
| 592 View contentView = |
| 593 LocationBarLayout.this.getRootView().findViewById(android.R.
id.content); |
| 594 ViewUtils.getRelativeLayoutPosition(contentView, mAnchorView, mTempP
osition); |
| 595 int anchorX = mTempPosition[0]; |
| 596 int anchorY = mTempPosition[1]; |
| 597 |
| 598 ViewUtils.getRelativeLayoutPosition(contentView, (View) getParent(),
mTempPosition); |
| 599 int parentY = mTempPosition[1]; |
| 600 |
| 601 int anchorBottomRelativeToContent = anchorY + mAnchorView.getMeasure
dHeight(); |
| 602 int desiredTopMargin = anchorBottomRelativeToContent - parentY; |
| 603 if (layoutParams.topMargin != desiredTopMargin) { |
| 604 layoutParams.topMargin = desiredTopMargin; |
| 605 updateLayout = true; |
| 606 } |
| 607 |
| 608 int contentLeft = contentView.getLeft(); |
| 609 int anchorLeftRelativeToContent = anchorX - contentLeft; |
| 610 if (layoutParams.leftMargin != anchorLeftRelativeToContent) { |
| 611 layoutParams.leftMargin = anchorLeftRelativeToContent; |
| 612 updateLayout = true; |
| 613 } |
| 614 |
| 615 getWindowDelegate().getWindowVisibleDisplayFrame(mTempRect); |
| 616 int decorHeight = getWindowDelegate().getDecorViewHeight(); |
| 617 int availableViewportHeight = Math.min(mTempRect.height(), decorHeig
ht); |
| 618 int availableListHeight = availableViewportHeight - anchorBottomRela
tiveToContent; |
| 619 int desiredHeight = Math.min(availableListHeight, getIdealHeight()); |
| 620 if (layoutParams.height != desiredHeight) { |
| 621 layoutParams.height = desiredHeight; |
| 622 updateLayout = true; |
| 623 } |
| 624 |
| 625 int desiredWidth = getDesiredWidth(); |
| 626 if (layoutParams.width != desiredWidth) { |
| 627 layoutParams.width = desiredWidth; |
| 628 updateLayout = true; |
| 629 } |
| 630 |
| 631 if (updateLayout) requestLayout(); |
| 632 } |
| 633 |
| 634 private int getIdealHeight() { |
| 635 int idealListSize = mBackgroundVerticalPadding; |
| 636 for (int i = 0; i < mSuggestionItems.size(); i++) { |
| 637 OmniboxResultItem item = mSuggestionItems.get(i); |
| 638 if (!TextUtils.isEmpty(item.getSuggestion().getAnswerContents())
) { |
| 639 idealListSize += mSuggestionAnswerHeight; |
| 640 } else { |
| 641 idealListSize += mSuggestionHeight; |
| 642 } |
| 643 } |
| 644 return idealListSize; |
| 645 } |
| 646 |
| 647 private int getDesiredWidth() { |
| 648 return mAnchorView.getWidth(); |
| 649 } |
| 650 |
| 651 @Override |
| 652 public void onWindowFocusChanged(boolean hasWindowFocus) { |
| 653 super.onWindowFocusChanged(hasWindowFocus); |
| 654 if (!hasWindowFocus && !mSuggestionModalShown) hideSuggestions(); |
| 655 } |
| 656 |
| 657 @Override |
| 658 protected void layoutChildren() { |
| 659 super.layoutChildren(); |
| 660 // In ICS, the selected view is not marked as selected despite calli
ng setSelection(0), |
| 661 // so we bootstrap this after the children have been laid out. |
| 662 if (!isInTouchMode() && getSelectedView() != null) { |
| 663 getSelectedView().setSelected(true); |
| 664 } |
| 665 } |
| 666 } |
| 667 |
| 668 public LocationBarLayout(Context context, AttributeSet attrs) { |
| 669 super(context, attrs); |
| 670 |
| 671 LayoutInflater.from(context).inflate(R.layout.location_bar, this, true); |
| 672 mNavigationButton = (ImageView) findViewById(R.id.navigation_button); |
| 673 assert mNavigationButton != null : "Missing navigation type view."; |
| 674 mNavigationButtonType = DeviceFormFactor.isTablet(context) |
| 675 ? NavigationButtonType.PAGE : NavigationButtonType.EMPTY; |
| 676 |
| 677 mSecurityButton = (ImageButton) findViewById(R.id.security_button); |
| 678 mSecurityIconType = ConnectionSecurityHelperSecurityLevel.NONE; |
| 679 |
| 680 mDeleteButton = (TintedImageButton) findViewById(R.id.delete_button); |
| 681 |
| 682 mUrlBar = (UrlBar) findViewById(R.id.url_bar); |
| 683 // The HTC Sense IME will attempt to autocomplete words in the Omnibox w
hen Prediction is |
| 684 // enabled. We want to disable this feature and rely on the Omnibox's i
mplementation. |
| 685 // Their IME does not respect ~TYPE_TEXT_FLAG_AUTO_COMPLETE nor any of t
he other InputType |
| 686 // options I tried, but setting the filter variation prevents it. Sadly
, it also removes |
| 687 // the .com button, but the prediction was buggy as it would autocomplet
e words even when |
| 688 // typing at the beginning of the omnibox text when other content was pr
esent (messing up |
| 689 // what was previously there). See bug: http://b/issue?id=6200071 |
| 690 String defaultIme = Settings.Secure.getString(getContext().getContentRes
olver(), |
| 691 Settings.Secure.DEFAULT_INPUT_METHOD); |
| 692 if (defaultIme != null && defaultIme.contains("com.htc.android.htcime"))
{ |
| 693 mUrlBar.setInputType(mUrlBar.getInputType() | InputType.TYPE_TEXT_VA
RIATION_FILTER); |
| 694 } |
| 695 mUrlBar.setDelegate(this); |
| 696 |
| 697 mUrlContainer = (UrlContainer) findViewById(R.id.url_container); |
| 698 |
| 699 mSuggestionItems = new ArrayList<OmniboxResultItem>(); |
| 700 mSuggestionListAdapter = new OmniboxResultsAdapter(getContext(), this, m
SuggestionItems); |
| 701 |
| 702 mMicButton = (TintedImageButton) findViewById(R.id.mic_button); |
| 703 } |
| 704 |
| 705 @Override |
| 706 protected void onFinishInflate() { |
| 707 super.onFinishInflate(); |
| 708 |
| 709 mUrlBar.setCursorVisible(false); |
| 710 mNavigationButton.setVisibility(VISIBLE); |
| 711 mSecurityButton.setVisibility(INVISIBLE); |
| 712 |
| 713 setLayoutTransition(null); |
| 714 |
| 715 AnimatorListenerAdapter iconChangeAnimatorListener = new AnimatorListene
rAdapter() { |
| 716 @Override |
| 717 public void onAnimationEnd(Animator animation) { |
| 718 if (animation == mSecurityButtonShowAnimator) { |
| 719 mNavigationButton.setVisibility(INVISIBLE); |
| 720 } else if (animation == mNavigationIconShowAnimator) { |
| 721 mSecurityButton.setVisibility(INVISIBLE); |
| 722 } |
| 723 } |
| 724 |
| 725 @Override |
| 726 public void onAnimationStart(Animator animation) { |
| 727 if (animation == mSecurityButtonShowAnimator) { |
| 728 mSecurityButton.setVisibility(VISIBLE); |
| 729 } else if (animation == mNavigationIconShowAnimator) { |
| 730 mNavigationButton.setVisibility(VISIBLE); |
| 731 } |
| 732 } |
| 733 }; |
| 734 |
| 735 mSecurityButtonShowAnimator = new AnimatorSet(); |
| 736 mSecurityButtonShowAnimator.playTogether( |
| 737 ObjectAnimator.ofFloat(mNavigationButton, ALPHA, 0), |
| 738 ObjectAnimator.ofFloat(mSecurityButton, ALPHA, 1)); |
| 739 mSecurityButtonShowAnimator.setDuration(URL_FOCUS_CHANGE_ANIMATION_DURAT
ION_MS); |
| 740 mSecurityButtonShowAnimator.addListener(iconChangeAnimatorListener); |
| 741 |
| 742 mNavigationIconShowAnimator = new AnimatorSet(); |
| 743 mNavigationIconShowAnimator.playTogether( |
| 744 ObjectAnimator.ofFloat(mNavigationButton, ALPHA, 1), |
| 745 ObjectAnimator.ofFloat(mSecurityButton, ALPHA, 0)); |
| 746 mNavigationIconShowAnimator.setDuration(URL_FOCUS_CHANGE_ANIMATION_DURAT
ION_MS); |
| 747 mNavigationIconShowAnimator.addListener(iconChangeAnimatorListener); |
| 748 |
| 749 mUrlBar.setOnKeyListener(new UrlBarKeyListener()); |
| 750 |
| 751 mTextWatcher = new UrlBarTextWatcher(); |
| 752 mUrlBar.setLocationBarTextWatcher(mTextWatcher); |
| 753 |
| 754 // mLocationBar's direction is tied to this UrlBar's text direction. Ico
ns inside the |
| 755 // location bar, e.g. lock, refresh, X, should be reversed if UrlBar's t
ext is RTL. |
| 756 mUrlBar.setUrlDirectionListener(new UrlBar.UrlDirectionListener() { |
| 757 @Override |
| 758 public void onUrlDirectionChanged(int layoutDirection) { |
| 759 ApiCompatibilityUtils.setLayoutDirection(LocationBarLayout.this,
layoutDirection); |
| 760 updateSuggestionsLayoutDirection(layoutDirection); |
| 761 } |
| 762 }); |
| 763 |
| 764 mUrlBar.setSelectAllOnFocus(true); |
| 765 } |
| 766 |
| 767 @Override |
| 768 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
| 769 updateLayoutParams(); |
| 770 super.onMeasure(widthMeasureSpec, heightMeasureSpec); |
| 771 } |
| 772 |
| 773 private void updateSuggestionsLayoutDirection(int layoutDirection) { |
| 774 if (mSuggestionList != null && mSuggestionList.isShown()) { |
| 775 ListView listView = mSuggestionList; |
| 776 ApiCompatibilityUtils.setLayoutDirection(listView, layoutDirection); |
| 777 for (int i = 0; i < listView.getChildCount(); i++) { |
| 778 ApiCompatibilityUtils.setLayoutDirection(listView.getChildAt(i), |
| 779 layoutDirection); |
| 780 } |
| 781 } |
| 782 } |
| 783 |
| 784 @Override |
| 785 public void initializeControls(WindowDelegate windowDelegate, |
| 786 ActionBarDelegate actionBarDelegate, WindowAndroid windowAndroid) { |
| 787 mWindowDelegate = windowDelegate; |
| 788 |
| 789 mContextualMenuBar = new ContextualMenuBar(getContext(), actionBarDelega
te); |
| 790 mContextualMenuBar.setCustomSelectionActionModeCallback( |
| 791 new CustomSelectionActionModeCallback() { |
| 792 @Override |
| 793 public boolean onPrepareActionMode(ActionMode mode, Menu men
u) { |
| 794 super.onPrepareActionMode(mode, menu); |
| 795 mode.getMenuInflater().inflate(R.menu.textselectionmenu,
menu); |
| 796 return true; |
| 797 } |
| 798 |
| 799 @Override |
| 800 public boolean onActionItemClicked(ActionMode mode, MenuItem
item) { |
| 801 if (item.getItemId() == R.id.copy_url) { |
| 802 ClipboardManager clipboard = |
| 803 (ClipboardManager) getContext().getSystemSer
vice( |
| 804 Context.CLIPBOARD_SERVICE); |
| 805 ClipData clip = ClipData.newPlainText("url", mOrigin
alUrl); |
| 806 clipboard.setPrimaryClip(clip); |
| 807 mode.finish(); |
| 808 return true; |
| 809 } else { |
| 810 return super.onActionItemClicked(mode, item); |
| 811 } |
| 812 } |
| 813 }); |
| 814 |
| 815 mWindowAndroid = windowAndroid; |
| 816 mMicButton.setOnClickListener(this); |
| 817 } |
| 818 |
| 819 /** |
| 820 * @return The WindowDelegate for the LocationBar. This should be used for a
ll Window related |
| 821 * state queries. |
| 822 */ |
| 823 protected WindowDelegate getWindowDelegate() { |
| 824 return mWindowDelegate; |
| 825 } |
| 826 |
| 827 /** |
| 828 * Handles native dependent initialization for this class. |
| 829 */ |
| 830 @Override |
| 831 public void onNativeLibraryReady() { |
| 832 mNativeInitialized = true; |
| 833 |
| 834 mSecurityButton.setOnClickListener(this); |
| 835 mNavigationButton.setOnClickListener(this); |
| 836 updateMicButtonState(); |
| 837 mDeleteButton.setOnClickListener(this); |
| 838 |
| 839 mAutocomplete = new AutocompleteController(this); |
| 840 |
| 841 mOmniboxPrerender = new OmniboxPrerender(); |
| 842 |
| 843 for (Runnable deferredRunnable : mDeferredNativeRunnables) { |
| 844 post(deferredRunnable); |
| 845 } |
| 846 mDeferredNativeRunnables.clear(); |
| 847 |
| 848 mUrlBar.onOmniboxFullyFunctional(); |
| 849 |
| 850 updateCustomSelectionActionModeCallback(); |
| 851 updateVisualsForState(); |
| 852 } |
| 853 |
| 854 /** |
| 855 * @return Whether or not to animate icon changes. |
| 856 */ |
| 857 protected boolean shouldAnimateIconChanges() { |
| 858 return mUrlHasFocus; |
| 859 } |
| 860 |
| 861 /** |
| 862 * Sets the autocomplete controller for the location bar. |
| 863 * |
| 864 * @param controller The controller that will handle autocomplete/omnibox su
ggestions. |
| 865 * @note Only used for testing. |
| 866 */ |
| 867 @VisibleForTesting |
| 868 public void setAutocompleteController(AutocompleteController controller) { |
| 869 mAutocomplete = controller; |
| 870 } |
| 871 |
| 872 /** |
| 873 * Updates the profile used for generating autocomplete suggestions. |
| 874 * @param profile The profile to be used. |
| 875 */ |
| 876 @Override |
| 877 public void setAutocompleteProfile(Profile profile) { |
| 878 // This will only be called once at least one tab exists, and the tab mo
del is told to |
| 879 // update its state. During Chrome initialization the tab model update h
appens after the |
| 880 // call to onNativeLibraryReady, so this assert will not fire. |
| 881 assert mNativeInitialized : |
| 882 "Setting Autocomplete Profile before native side initialized"; |
| 883 mAutocomplete.setProfile(profile); |
| 884 mOmniboxPrerender.initializeForProfile(profile); |
| 885 } |
| 886 |
| 887 private void changeLocationBarIcon(boolean showSecurityButton) { |
| 888 if (mLocationBarIconActiveAnimator != null && mLocationBarIconActiveAnim
ator.isRunning()) { |
| 889 mLocationBarIconActiveAnimator.cancel(); |
| 890 } |
| 891 View viewToBeShown = showSecurityButton ? mSecurityButton : mNavigationB
utton; |
| 892 if (viewToBeShown.getVisibility() == VISIBLE && viewToBeShown.getAlpha()
== 1) { |
| 893 return; |
| 894 } |
| 895 if (showSecurityButton) { |
| 896 mLocationBarIconActiveAnimator = mSecurityButtonShowAnimator; |
| 897 } else { |
| 898 mLocationBarIconActiveAnimator = mNavigationIconShowAnimator; |
| 899 } |
| 900 if (shouldAnimateIconChanges()) { |
| 901 mLocationBarIconActiveAnimator.setDuration(URL_FOCUS_CHANGE_ANIMATIO
N_DURATION_MS); |
| 902 } else { |
| 903 mLocationBarIconActiveAnimator.setDuration(0); |
| 904 } |
| 905 mLocationBarIconActiveAnimator.start(); |
| 906 } |
| 907 |
| 908 @Override |
| 909 public void onUrlPreFocusChanged(boolean gainFocus) { |
| 910 if (mToolbarDataProvider == null || mToolbarDataProvider.getTab() == nul
l) return; |
| 911 |
| 912 if (!mQueryInTheOmnibox |
| 913 && FeatureUtilities.isDocumentMode(getContext()) |
| 914 && !TextUtils.isEmpty(mUrlBar.getText())) { |
| 915 mUrlBar.setUrl(mToolbarDataProvider.getTab().getUrl(), null); |
| 916 } |
| 917 } |
| 918 |
| 919 @Override |
| 920 public void setUrlBarFocus(boolean shouldBeFocused) { |
| 921 if (shouldBeFocused) { |
| 922 mUrlBar.requestFocus(); |
| 923 } else { |
| 924 mUrlBar.clearFocus(); |
| 925 } |
| 926 } |
| 927 |
| 928 @Override |
| 929 public long getFirstUrlBarFocusTime() { |
| 930 return mUrlBar.getFirstFocusTime(); |
| 931 } |
| 932 |
| 933 /** |
| 934 * Triggered when the URL input field has gained or lost focus. |
| 935 * @param hasFocus Whether the URL field has gained focus. |
| 936 */ |
| 937 public void onUrlFocusChange(boolean hasFocus) { |
| 938 mUrlHasFocus = hasFocus; |
| 939 updateFocusSource(hasFocus); |
| 940 updateDeleteButtonVisibility(); |
| 941 Tab currentTab = getCurrentTab(); |
| 942 if (hasFocus) { |
| 943 mUrlBar.deEmphasizeUrl(); |
| 944 } else { |
| 945 hideSuggestions(); |
| 946 |
| 947 // Focus change caused by a close-tab may result in an invalid curre
nt tab. |
| 948 if (currentTab != null) { |
| 949 setUrlToPageUrl(); |
| 950 emphasizeUrl(); |
| 951 } |
| 952 } |
| 953 |
| 954 if (getToolbarDataProvider().isUsingBrandColor()) { |
| 955 updateVisualsForState(); |
| 956 if (mUrlHasFocus) mUrlBar.selectAll(); |
| 957 } |
| 958 |
| 959 if (mUrlFocusChangeListener != null) mUrlFocusChangeListener.onUrlFocusC
hange(hasFocus); |
| 960 changeLocationBarIcon( |
| 961 (!DeviceFormFactor.isTablet(getContext()) || !hasFocus) && isSec
urityButtonShown()); |
| 962 mUrlBar.setCursorVisible(hasFocus); |
| 963 if (mQueryInTheOmnibox) mUrlBar.setSelection(mUrlBar.getSelectionEnd()); |
| 964 |
| 965 updateOmniboxResultsContainer(); |
| 966 if (hasFocus) updateOmniboxResultsContainerBackground(true); |
| 967 |
| 968 if (hasFocus && currentTab != null && !currentTab.isIncognito()) { |
| 969 if (mNativeInitialized |
| 970 && TemplateUrlService.getInstance().isDefaultSearchEngineGoo
gle()) { |
| 971 GeolocationHeader.primeLocationForGeoHeader(getContext()); |
| 972 } else { |
| 973 mDeferredNativeRunnables.add(new Runnable() { |
| 974 @Override |
| 975 public void run() { |
| 976 if (TemplateUrlService.getInstance().isDefaultSearchEngi
neGoogle()) { |
| 977 GeolocationHeader.primeLocationForGeoHeader(getConte
xt()); |
| 978 } |
| 979 } |
| 980 }); |
| 981 } |
| 982 } |
| 983 |
| 984 if (mNativeInitialized) { |
| 985 startZeroSuggest(); |
| 986 } else { |
| 987 mDeferredNativeRunnables.add(new Runnable() { |
| 988 @Override |
| 989 public void run() { |
| 990 if (TextUtils.isEmpty(mUrlBar.getQueryText())) { |
| 991 startZeroSuggest(); |
| 992 } |
| 993 } |
| 994 }); |
| 995 } |
| 996 |
| 997 // Add and remove text watcher from the URL bar with focus, so that it's |
| 998 // not called when we modify the displayed information on focus. |
| 999 if (hasFocus) { |
| 1000 mUrlBar.addTextChangedListener(mTextWatcher); |
| 1001 } else { |
| 1002 mUrlBar.removeTextChangedListener(mTextWatcher); |
| 1003 } |
| 1004 |
| 1005 if (!hasFocus) { |
| 1006 mHasStartedNewOmniboxEditSession = false; |
| 1007 mNewOmniboxEditSessionTimestamp = -1; |
| 1008 } |
| 1009 } |
| 1010 |
| 1011 /** |
| 1012 * Make a zero suggest request if native is loaded, the URL bar has focus, a
nd the |
| 1013 * current tab is not incognito. |
| 1014 */ |
| 1015 private void startZeroSuggest() { |
| 1016 // Reset "edited" state in the omnibox if zero suggest is triggered -- n
ew edits |
| 1017 // now count as a new session. |
| 1018 mHasStartedNewOmniboxEditSession = false; |
| 1019 mNewOmniboxEditSessionTimestamp = -1; |
| 1020 Tab currentTab = getCurrentTab(); |
| 1021 if (mNativeInitialized |
| 1022 && mUrlHasFocus |
| 1023 && currentTab != null |
| 1024 && !currentTab.isIncognito()) { |
| 1025 mAutocomplete.startZeroSuggest(currentTab.getProfile(), mUrlBar.getQ
ueryText(), |
| 1026 currentTab.getUrl(), mQueryInTheOmnibox, mUrlFocusedFromFake
box); |
| 1027 } |
| 1028 } |
| 1029 |
| 1030 @Override |
| 1031 public void setDefaultTextEditActionModeCallback(CustomSelectionActionModeCa
llback callback) { |
| 1032 mDefaultActionModeCallbackForTextEdit = callback; |
| 1033 } |
| 1034 |
| 1035 /** |
| 1036 * If query in the omnibox, sets UrlBar's ActionModeCallback to show copy ur
l button. Else, |
| 1037 * it is set to the default one. |
| 1038 */ |
| 1039 private void updateCustomSelectionActionModeCallback() { |
| 1040 if (mQueryInTheOmnibox) { |
| 1041 mUrlBar.setCustomSelectionActionModeCallback( |
| 1042 mContextualMenuBar.getCustomSelectionActionModeCallback()); |
| 1043 } else { |
| 1044 mUrlBar.setCustomSelectionActionModeCallback(mDefaultActionModeCallb
ackForTextEdit); |
| 1045 } |
| 1046 } |
| 1047 |
| 1048 @Override |
| 1049 public void requestUrlFocusFromFakebox(String pastedText) { |
| 1050 mUrlFocusedFromFakebox = true; |
| 1051 mUrlBar.requestFocus(); |
| 1052 |
| 1053 if (pastedText != null) { |
| 1054 // This must be happen after requestUrlFocus(), which changes the se
lection. |
| 1055 mUrlBar.setUrl(pastedText, null); |
| 1056 mUrlBar.setSelection(mUrlBar.getText().length()); |
| 1057 } |
| 1058 } |
| 1059 |
| 1060 /** |
| 1061 * Sets the toolbar that owns this LocationBar. |
| 1062 */ |
| 1063 @Override |
| 1064 public void setToolbarDataProvider(ToolbarDataProvider toolbarDataProvider)
{ |
| 1065 mToolbarDataProvider = toolbarDataProvider; |
| 1066 |
| 1067 mUrlBar.setOnFocusChangeListener(new View.OnFocusChangeListener() { |
| 1068 @Override |
| 1069 public void onFocusChange(View v, final boolean hasFocus) { |
| 1070 onUrlFocusChange(hasFocus); |
| 1071 } |
| 1072 }); |
| 1073 } |
| 1074 |
| 1075 @Override |
| 1076 public void setMenuButtonHelper(AppMenuButtonHelper helper) { } |
| 1077 |
| 1078 @Override |
| 1079 public View getMenuAnchor() { |
| 1080 return null; |
| 1081 } |
| 1082 |
| 1083 /** |
| 1084 * Sets the URL focus change listner that will be notified when the URL gain
s or loses focus. |
| 1085 * @param listener The listener to be registered. |
| 1086 */ |
| 1087 @Override |
| 1088 public void setUrlFocusChangeListener(UrlFocusChangeListener listener) { |
| 1089 mUrlFocusChangeListener = listener; |
| 1090 } |
| 1091 |
| 1092 /** |
| 1093 * @return The toolbar data provider. |
| 1094 */ |
| 1095 @VisibleForTesting |
| 1096 protected ToolbarDataProvider getToolbarDataProvider() { |
| 1097 return mToolbarDataProvider; |
| 1098 } |
| 1099 |
| 1100 private static NavigationButtonType suggestionTypeToNavigationButtonType( |
| 1101 OmniboxSuggestion.Type suggestionType) { |
| 1102 switch (suggestionType) { |
| 1103 case NAVSUGGEST: |
| 1104 case HISTORY_URL: |
| 1105 case URL_WHAT_YOU_TYPED: |
| 1106 case HISTORY_TITLE: |
| 1107 case HISTORY_BODY: |
| 1108 case HISTORY_KEYWORD: |
| 1109 return NavigationButtonType.PAGE; |
| 1110 case SEARCH_WHAT_YOU_TYPED: |
| 1111 case SEARCH_HISTORY: |
| 1112 case SEARCH_SUGGEST: |
| 1113 case SEARCH_SUGGEST_ENTITY: |
| 1114 case SEARCH_SUGGEST_TAIL: |
| 1115 case SEARCH_SUGGEST_PERSONALIZED: |
| 1116 case SEARCH_SUGGEST_PROFILE: |
| 1117 case VOICE_SUGGEST: |
| 1118 case SEARCH_OTHER_ENGINE: |
| 1119 case OPEN_HISTORY_PAGE: |
| 1120 return NavigationButtonType.MAGNIFIER; |
| 1121 default: |
| 1122 assert false; |
| 1123 return NavigationButtonType.MAGNIFIER; |
| 1124 } |
| 1125 } |
| 1126 |
| 1127 // Updates the navigation button based on the URL string |
| 1128 private void updateNavigationButton() { |
| 1129 boolean isTablet = DeviceFormFactor.isTablet(getContext()); |
| 1130 NavigationButtonType type = NavigationButtonType.EMPTY; |
| 1131 if (isTablet && !mSuggestionItems.isEmpty()) { |
| 1132 // If there are suggestions showing, show the icon for the default s
uggestion. |
| 1133 type = suggestionTypeToNavigationButtonType( |
| 1134 mSuggestionItems.get(0).getSuggestion().getType()); |
| 1135 } else if (mQueryInTheOmnibox) { |
| 1136 type = NavigationButtonType.MAGNIFIER; |
| 1137 } else if (isTablet) { |
| 1138 type = NavigationButtonType.PAGE; |
| 1139 } |
| 1140 |
| 1141 if (type != mNavigationButtonType) setNavigationButtonType(type); |
| 1142 } |
| 1143 |
| 1144 /** |
| 1145 * @return Whether the query is shown in the omnibox instead of the url. |
| 1146 */ |
| 1147 public boolean showingQueryInTheOmnibox() { |
| 1148 return mQueryInTheOmnibox; |
| 1149 } |
| 1150 |
| 1151 /** |
| 1152 * @return Whether original url is shown for preview page. |
| 1153 */ |
| 1154 @Override |
| 1155 public boolean showingOriginalUrlForPreview() { |
| 1156 return mShowingOriginalUrlForPreview; |
| 1157 } |
| 1158 |
| 1159 private int getSecurityLevel() { |
| 1160 if (getCurrentTab() == null) return ConnectionSecurityHelperSecurityLeve
l.NONE; |
| 1161 return getCurrentTab().getSecurityLevel(); |
| 1162 } |
| 1163 |
| 1164 /** |
| 1165 * Determines the icon that should be displayed for the current security lev
el. |
| 1166 * @param securityLevel The security level for which the resource will be re
turned. |
| 1167 * @param usingLightTheme Whether light themed security assets should be use
d. |
| 1168 * @return The resource ID of the icon that should be displayed, 0 if no ico
n should show. |
| 1169 */ |
| 1170 public static int getSecurityIconResource(int securityLevel, boolean usingLi
ghtTheme) { |
| 1171 switch (securityLevel) { |
| 1172 case ConnectionSecurityHelperSecurityLevel.NONE: |
| 1173 return 0; |
| 1174 case ConnectionSecurityHelperSecurityLevel.SECURITY_WARNING: |
| 1175 return R.drawable.omnibox_https_warning; |
| 1176 case ConnectionSecurityHelperSecurityLevel.SECURITY_ERROR: |
| 1177 return R.drawable.omnibox_https_invalid; |
| 1178 case ConnectionSecurityHelperSecurityLevel.SECURE: |
| 1179 case ConnectionSecurityHelperSecurityLevel.EV_SECURE: |
| 1180 return usingLightTheme |
| 1181 ? R.drawable.omnibox_https_valid_light : R.drawable.omni
box_https_valid; |
| 1182 default: |
| 1183 assert false; |
| 1184 } |
| 1185 return 0; |
| 1186 } |
| 1187 |
| 1188 /** |
| 1189 * Updates the security icon displayed in the LocationBar. |
| 1190 */ |
| 1191 @Override |
| 1192 public void updateSecurityIcon(int securityLevel) { |
| 1193 if (showingOriginalUrlForPreview()) { |
| 1194 securityLevel = ConnectionSecurityHelperSecurityLevel.NONE; |
| 1195 } |
| 1196 if (mQueryInTheOmnibox) { |
| 1197 if (securityLevel == ConnectionSecurityHelperSecurityLevel.SECURE |
| 1198 || securityLevel == ConnectionSecurityHelperSecurityLevel.EV
_SECURE) { |
| 1199 securityLevel = ConnectionSecurityHelperSecurityLevel.NONE; |
| 1200 } else if (securityLevel == ConnectionSecurityHelperSecurityLevel.SE
CURITY_WARNING |
| 1201 || securityLevel == ConnectionSecurityHelperSecurityLevel.SE
CURITY_ERROR) { |
| 1202 setUrlToPageUrl(); |
| 1203 } |
| 1204 } |
| 1205 |
| 1206 // ImageView#setImageResource is no-op if given resource is the current
one. |
| 1207 mSecurityButton.setImageResource( |
| 1208 getSecurityIconResource(securityLevel, !shouldEmphasizeHttpsSche
me())); |
| 1209 |
| 1210 if (mSecurityIconType == securityLevel) return; |
| 1211 mSecurityIconType = securityLevel; |
| 1212 |
| 1213 if (securityLevel == ConnectionSecurityHelperSecurityLevel.NONE) { |
| 1214 updateSecurityButton(false); |
| 1215 } else { |
| 1216 updateSecurityButton(true); |
| 1217 // Since we emphasize the schema of the URL based on the security ty
pe, we need to |
| 1218 // refresh the emphasis. |
| 1219 mUrlBar.deEmphasizeUrl(); |
| 1220 } |
| 1221 emphasizeUrl(); |
| 1222 } |
| 1223 |
| 1224 private void emphasizeUrl() { |
| 1225 if (!mQueryInTheOmnibox) mUrlBar.emphasizeUrl(); |
| 1226 } |
| 1227 |
| 1228 @Override |
| 1229 public boolean shouldEmphasizeHttpsScheme() { |
| 1230 int securityLevel = getSecurityLevel(); |
| 1231 if (securityLevel == ConnectionSecurityHelperSecurityLevel.SECURITY_ERRO
R |
| 1232 || securityLevel == ConnectionSecurityHelperSecurityLevel.SECURI
TY_WARNING |
| 1233 || securityLevel == ConnectionSecurityHelperSecurityLevel.SECURI
TY_POLICY_WARNING) { |
| 1234 return true; |
| 1235 } |
| 1236 if (getToolbarDataProvider().isUsingBrandColor()) return false; |
| 1237 if (getToolbarDataProvider().isIncognito()) return false; |
| 1238 return true; |
| 1239 } |
| 1240 |
| 1241 /** |
| 1242 * Updates the display of the security button. |
| 1243 * @param enabled Whether the security button should be displayed. |
| 1244 */ |
| 1245 private void updateSecurityButton(boolean enabled) { |
| 1246 changeLocationBarIcon(enabled |
| 1247 && (!DeviceFormFactor.isTablet(getContext()) || !mUrlHasFocus)); |
| 1248 mSecurityButtonShown = enabled; |
| 1249 updateLocationBarIconContainerVisibility(); |
| 1250 } |
| 1251 |
| 1252 /** |
| 1253 * @return Whether the security button is currently being displayed. |
| 1254 */ |
| 1255 @VisibleForTesting |
| 1256 public boolean isSecurityButtonShown() { |
| 1257 return mSecurityButtonShown; |
| 1258 } |
| 1259 |
| 1260 /** |
| 1261 * Sets the type of the current navigation type and updates the UI to match
it. |
| 1262 * @param buttonType The type of navigation button to be shown. |
| 1263 */ |
| 1264 private void setNavigationButtonType(NavigationButtonType buttonType) { |
| 1265 switch (buttonType) { |
| 1266 case PAGE: |
| 1267 Drawable page = ApiCompatibilityUtils.getDrawable( |
| 1268 getResources(), R.drawable.ic_omnibox_page); |
| 1269 page.setColorFilter(mUseDarkColors |
| 1270 ? getResources().getColor(R.color.light_normal_color) |
| 1271 : Color.WHITE, PorterDuff.Mode.SRC_IN); |
| 1272 mNavigationButton.setImageDrawable(page); |
| 1273 break; |
| 1274 case MAGNIFIER: |
| 1275 mNavigationButton.setImageResource(R.drawable.ic_omnibox_magnifi
er); |
| 1276 break; |
| 1277 case EMPTY: |
| 1278 mNavigationButton.setImageResource(0); |
| 1279 break; |
| 1280 default: |
| 1281 assert false; |
| 1282 } |
| 1283 |
| 1284 if (mNavigationButton.getVisibility() != VISIBLE) { |
| 1285 mNavigationButton.setVisibility(VISIBLE); |
| 1286 } |
| 1287 mNavigationButtonType = buttonType; |
| 1288 updateLocationBarIconContainerVisibility(); |
| 1289 } |
| 1290 |
| 1291 /** |
| 1292 * Update the visibility of the location bar icon container based on the sta
te of the |
| 1293 * security and navigation icons. |
| 1294 */ |
| 1295 protected void updateLocationBarIconContainerVisibility() { |
| 1296 boolean showContainer = |
| 1297 mSecurityButtonShown || mNavigationButtonType != NavigationButto
nType.EMPTY; |
| 1298 findViewById(R.id.location_bar_icon).setVisibility(showContainer ? VISIB
LE : GONE); |
| 1299 } |
| 1300 |
| 1301 private boolean isStoredArticle(String url) { |
| 1302 DomDistillerService domDistillerService = |
| 1303 DomDistillerServiceFactory.getForProfile(Profile.getLastUsedProf
ile()); |
| 1304 String entryIdFromUrl = DomDistillerUrlUtils.getValueForKeyInUrl(url, "e
ntry_id"); |
| 1305 if (TextUtils.isEmpty(entryIdFromUrl)) return false; |
| 1306 return domDistillerService.hasEntry(entryIdFromUrl); |
| 1307 } |
| 1308 |
| 1309 /** |
| 1310 * Updates the layout params for the location bar start aligned views. |
| 1311 */ |
| 1312 protected void updateLayoutParams() { |
| 1313 int startMargin = 0; |
| 1314 int urlContainerChildIndex = -1; |
| 1315 for (int i = 0; i < getChildCount(); i++) { |
| 1316 View childView = getChildAt(i); |
| 1317 if (childView.getVisibility() != GONE) { |
| 1318 LayoutParams childLayoutParams = (LayoutParams) childView.getLay
outParams(); |
| 1319 if (ApiCompatibilityUtils.getMarginStart(childLayoutParams) != s
tartMargin) { |
| 1320 ApiCompatibilityUtils.setMarginStart(childLayoutParams, star
tMargin); |
| 1321 childView.setLayoutParams(childLayoutParams); |
| 1322 } |
| 1323 if (childView == mUrlContainer) { |
| 1324 urlContainerChildIndex = i; |
| 1325 break; |
| 1326 } |
| 1327 int widthMeasureSpec; |
| 1328 int heightMeasureSpec; |
| 1329 if (childLayoutParams.width == LayoutParams.WRAP_CONTENT) { |
| 1330 widthMeasureSpec = MeasureSpec.makeMeasureSpec( |
| 1331 getMeasuredWidth(), MeasureSpec.AT_MOST); |
| 1332 } else if (childLayoutParams.width == LayoutParams.MATCH_PARENT)
{ |
| 1333 widthMeasureSpec = MeasureSpec.makeMeasureSpec( |
| 1334 getMeasuredWidth(), MeasureSpec.EXACTLY); |
| 1335 } else { |
| 1336 widthMeasureSpec = MeasureSpec.makeMeasureSpec( |
| 1337 childLayoutParams.width, MeasureSpec.EXACTLY); |
| 1338 } |
| 1339 if (childLayoutParams.height == LayoutParams.WRAP_CONTENT) { |
| 1340 heightMeasureSpec = MeasureSpec.makeMeasureSpec( |
| 1341 getMeasuredHeight(), MeasureSpec.AT_MOST); |
| 1342 } else if (childLayoutParams.height == LayoutParams.MATCH_PARENT
) { |
| 1343 heightMeasureSpec = MeasureSpec.makeMeasureSpec( |
| 1344 getMeasuredHeight(), MeasureSpec.EXACTLY); |
| 1345 } else { |
| 1346 heightMeasureSpec = MeasureSpec.makeMeasureSpec( |
| 1347 childLayoutParams.height, MeasureSpec.EXACTLY); |
| 1348 } |
| 1349 childView.measure(widthMeasureSpec, heightMeasureSpec); |
| 1350 startMargin += childView.getMeasuredWidth(); |
| 1351 } |
| 1352 } |
| 1353 |
| 1354 assert urlContainerChildIndex != -1; |
| 1355 int urlContainerMarginEnd = 0; |
| 1356 for (int i = urlContainerChildIndex + 1; i < getChildCount(); i++) { |
| 1357 View childView = getChildAt(i); |
| 1358 if (childView.getVisibility() != GONE) { |
| 1359 LayoutParams childLayoutParams = (LayoutParams) childView.getLay
outParams(); |
| 1360 urlContainerMarginEnd = Math.max(urlContainerMarginEnd, |
| 1361 childLayoutParams.width |
| 1362 + ApiCompatibilityUtils.getMarginStart(childLayo
utParams) |
| 1363 + ApiCompatibilityUtils.getMarginEnd(childLayout
Params)); |
| 1364 } |
| 1365 } |
| 1366 LayoutParams urlLayoutParams = (LayoutParams) mUrlContainer.getLayoutPar
ams(); |
| 1367 if (ApiCompatibilityUtils.getMarginEnd(urlLayoutParams) != urlContainerM
arginEnd) { |
| 1368 ApiCompatibilityUtils.setMarginEnd(urlLayoutParams, urlContainerMarg
inEnd); |
| 1369 mUrlContainer.setLayoutParams(urlLayoutParams); |
| 1370 } |
| 1371 } |
| 1372 |
| 1373 /** |
| 1374 * @return Whether the delete button should be shown. |
| 1375 */ |
| 1376 protected boolean shouldShowDeleteButton() { |
| 1377 // Show the delete button at the endon the right when the bar has focus
and has some text. |
| 1378 return mUrlBar.hasFocus() && !TextUtils.isEmpty(mUrlBar.getText()); |
| 1379 } |
| 1380 |
| 1381 /** |
| 1382 * Updates the display of the delete URL content button. |
| 1383 */ |
| 1384 protected void updateDeleteButtonVisibility() { |
| 1385 } |
| 1386 |
| 1387 /** |
| 1388 * @return The suggestion list popup containing the omnibox results (or |
| 1389 * null if it has not yet been created). |
| 1390 */ |
| 1391 @VisibleForTesting |
| 1392 public OmniboxSuggestionsList getSuggestionList() { |
| 1393 return mSuggestionList; |
| 1394 } |
| 1395 |
| 1396 /** |
| 1397 * Initiates the mSuggestionListPopup. Done on demand to not slow down |
| 1398 * the initial inflation of the location bar. |
| 1399 */ |
| 1400 private void initSuggestionList() { |
| 1401 // Only called from onSuggestionsReceived(), which is a callback from a
listener set up by |
| 1402 // onNativeLibraryReady(), so this assert is safe. |
| 1403 assert mNativeInitialized : "Trying to initialize suggestions list befor
e native init"; |
| 1404 if (mSuggestionList != null) return; |
| 1405 |
| 1406 OnLayoutChangeListener suggestionListResizer = new OnLayoutChangeListene
r() { |
| 1407 @Override |
| 1408 public void onLayoutChange(View v, int left, int top, int right, int
bottom, |
| 1409 int oldLeft, int oldTop, int oldRight, int oldBottom) { |
| 1410 // On ICS, this update does not take affect unless it is posted
to the end of the |
| 1411 // current message queue. |
| 1412 post(new Runnable() { |
| 1413 @Override |
| 1414 public void run() { |
| 1415 if (mSuggestionList.isShown()) mSuggestionList.updateLay
outParams(); |
| 1416 } |
| 1417 }); |
| 1418 } |
| 1419 }; |
| 1420 getRootView().findViewById(R.id.control_container) |
| 1421 .addOnLayoutChangeListener(suggestionListResizer); |
| 1422 |
| 1423 mSuggestionList = new OmniboxSuggestionsList(getContext()); |
| 1424 mOmniboxResultsContainer.addView(mSuggestionList); |
| 1425 mSuggestionList.setAdapter(mSuggestionListAdapter); |
| 1426 mSuggestionList.setClipToPadding(false); |
| 1427 mSuggestionListAdapter.setSuggestionDelegate(new OmniboxSuggestionDelega
te() { |
| 1428 @Override |
| 1429 public void onSelection(OmniboxSuggestion suggestion, int position)
{ |
| 1430 mSuggestionSelectionInProgress = true; |
| 1431 String suggestionMatchUrl = updateSuggestionUrlIfNeeded(suggesti
on, position); |
| 1432 loadUrlFromOmniboxMatch(suggestionMatchUrl, suggestion.getTransi
tion(), position, |
| 1433 suggestion.getType()); |
| 1434 hideSuggestions(); |
| 1435 UiUtils.hideKeyboard(mUrlBar); |
| 1436 } |
| 1437 |
| 1438 @Override |
| 1439 public void onRefineSuggestion(OmniboxSuggestion suggestion) { |
| 1440 stopAutocomplete(false); |
| 1441 mUrlBar.setUrl(suggestion.getFillIntoEdit(), null); |
| 1442 mUrlBar.setSelection(mUrlBar.getText().length()); |
| 1443 RecordUserAction.record("MobileOmniboxRefineSuggestion"); |
| 1444 } |
| 1445 |
| 1446 @Override |
| 1447 public void onSetUrlToSuggestion(OmniboxSuggestion suggestion) { |
| 1448 if (mIgnoreOmniboxItemSelection) return; |
| 1449 setUrlBarText(null, null, suggestion.getFillIntoEdit()); |
| 1450 mUrlBar.setSelection(mUrlBar.getText().length()); |
| 1451 mIgnoreOmniboxItemSelection = true; |
| 1452 } |
| 1453 |
| 1454 @Override |
| 1455 public void onDeleteSuggestion(int position) { |
| 1456 if (mAutocomplete != null) mAutocomplete.deleteSuggestion(positi
on); |
| 1457 } |
| 1458 |
| 1459 @Override |
| 1460 public void onGestureDown() { |
| 1461 stopAutocomplete(false); |
| 1462 } |
| 1463 |
| 1464 @Override |
| 1465 public void onShowModal() { |
| 1466 mSuggestionModalShown = true; |
| 1467 } |
| 1468 |
| 1469 @Override |
| 1470 public void onHideModal() { |
| 1471 mSuggestionModalShown = false; |
| 1472 } |
| 1473 |
| 1474 @Override |
| 1475 public void onTextWidthsUpdated(float requiredWidth, float matchCont
entsWidth) { |
| 1476 mSuggestionList.updateMaxTextWidths(requiredWidth, matchContents
Width); |
| 1477 } |
| 1478 |
| 1479 @Override |
| 1480 public float getMaxRequiredWidth() { |
| 1481 return mSuggestionList.getMaxRequiredWidth(); |
| 1482 } |
| 1483 |
| 1484 @Override |
| 1485 public float getMaxMatchContentsWidth() { |
| 1486 return mSuggestionList.getMaxMatchContentsWidth(); |
| 1487 } |
| 1488 }); |
| 1489 } |
| 1490 |
| 1491 /** |
| 1492 * @return The view that the suggestion popup should be anchored below. |
| 1493 */ |
| 1494 protected View getSuggestionPopupAnchorView() { |
| 1495 return this; |
| 1496 } |
| 1497 |
| 1498 /** |
| 1499 * @return The background for the omnibox suggestions popup. |
| 1500 */ |
| 1501 protected Drawable getSuggestionPopupBackground() { |
| 1502 if (mToolbarDataProvider.isIncognito()) { |
| 1503 return new ColorDrawable(OMNIBOX_INCOGNITO_RESULTS_BG_COLOR); |
| 1504 } else { |
| 1505 return new ColorDrawable(OMNIBOX_RESULTS_BG_COLOR); |
| 1506 } |
| 1507 } |
| 1508 |
| 1509 /** |
| 1510 * Handles showing/hiding the suggestions list. |
| 1511 * @param visible Whether the suggestions list should be visible. |
| 1512 */ |
| 1513 protected void setSuggestionsListVisibility(final boolean visible) { |
| 1514 mSuggestionsShown = visible; |
| 1515 if (mSuggestionList != null) { |
| 1516 final boolean isShowing = mSuggestionList.isShown(); |
| 1517 if (visible && !isShowing) { |
| 1518 mSuggestionList.show(); |
| 1519 } else if (!visible && isShowing) { |
| 1520 mSuggestionList.setVisibility(GONE); |
| 1521 } |
| 1522 } |
| 1523 updateOmniboxResultsContainer(); |
| 1524 } |
| 1525 |
| 1526 /** |
| 1527 * Updates the URL we will navigate to from suggestion, if needed. This will
update the search |
| 1528 * URL to be of the corpus type if query in the omnibox is displayed and upd
ate aqs= parameter |
| 1529 * on regular web search URLs. |
| 1530 * |
| 1531 * @param suggestion The chosen omnibox suggestion. |
| 1532 * @param selectedIndex The index of the chosen omnibox suggestion. |
| 1533 * @return The url to navigate to. |
| 1534 */ |
| 1535 private String updateSuggestionUrlIfNeeded(OmniboxSuggestion suggestion, int
selectedIndex) { |
| 1536 // Only called once we have suggestions, and don't have a listener thoug
h which we can |
| 1537 // receive suggestions until the native side is ready, so this is safe |
| 1538 assert mNativeInitialized |
| 1539 : "updateSuggestionUrlIfNeeded called before native initializati
on"; |
| 1540 |
| 1541 String updatedUrl = null; |
| 1542 // Only replace URL queries for corpus search refinements, this does not
work well |
| 1543 // for regular web searches. |
| 1544 // TODO(mariakhomenko): improve efficiency by just checking whether corp
us exists. |
| 1545 if (mQueryInTheOmnibox && !suggestion.isUrlSuggestion() |
| 1546 && !TextUtils.isEmpty(mToolbarDataProvider.getCorpusChipText()))
{ |
| 1547 String query = suggestion.getFillIntoEdit(); |
| 1548 Tab currentTab = getCurrentTab(); |
| 1549 if (currentTab != null && !TextUtils.isEmpty(currentTab.getUrl()) |
| 1550 && !TextUtils.isEmpty(query)) { |
| 1551 updatedUrl = TemplateUrlService.getInstance().replaceSearchTerms
InUrl( |
| 1552 query, currentTab.getUrl()); |
| 1553 } |
| 1554 } else if (suggestion.getType() != Type.VOICE_SUGGEST) { |
| 1555 // TODO(mariakhomenko): Ideally we want to update match destination
URL with new aqs |
| 1556 // for query in the omnibox and voice suggestions, but it's currentl
y difficult to do. |
| 1557 long elapsedTimeSinceInputChange = mNewOmniboxEditSessionTimestamp >
0 |
| 1558 ? (SystemClock.elapsedRealtime() - mNewOmniboxEditSessionTim
estamp) : -1; |
| 1559 updatedUrl = mAutocomplete.updateMatchDestinationUrlWithQueryFormula
tionTime( |
| 1560 selectedIndex, elapsedTimeSinceInputChange); |
| 1561 } |
| 1562 |
| 1563 return updatedUrl == null ? suggestion.getUrl() : updatedUrl; |
| 1564 } |
| 1565 |
| 1566 private void clearSuggestions(boolean notifyChange) { |
| 1567 mSuggestionItems.clear(); |
| 1568 // Make sure to notify the adapter. If the ListView becomes out of sync |
| 1569 // with its adapter and it has not been notified, it will throw an |
| 1570 // exception when some UI events are propagated. |
| 1571 if (notifyChange) mSuggestionListAdapter.notifyDataSetChanged(); |
| 1572 } |
| 1573 |
| 1574 /** |
| 1575 * Hides the omnibox suggestion popup. |
| 1576 * |
| 1577 * <p> |
| 1578 * Signals the autocomplete controller to stop generating omnibox suggestion
s. |
| 1579 * |
| 1580 * @see AutocompleteController#stop(boolean) |
| 1581 */ |
| 1582 @Override |
| 1583 public void hideSuggestions() { |
| 1584 if (mAutocomplete == null) return; |
| 1585 |
| 1586 recordSuggestionsDismissed(); |
| 1587 |
| 1588 stopAutocomplete(true); |
| 1589 |
| 1590 setSuggestionsListVisibility(false); |
| 1591 clearSuggestions(true); |
| 1592 updateNavigationButton(); |
| 1593 |
| 1594 mSuggestionSelectionInProgress = false; |
| 1595 } |
| 1596 |
| 1597 /** |
| 1598 * Records a UMA action indicating that the user dismissed the suggestion li
st (e.g. pressed |
| 1599 * the back button or the 'x' button in the Omnibox). If there was an answe
r shown its type |
| 1600 * is recorded. |
| 1601 * |
| 1602 * The action is not recorded if mSelectionInProgress is true. This allows
us to avoid |
| 1603 * recording the action in the case where the user is selecting a suggestion
which is not |
| 1604 * considered a dismissal. |
| 1605 */ |
| 1606 private void recordSuggestionsDismissed() { |
| 1607 if (mSuggestionSelectionInProgress || mSuggestionItems.size() == 0) retu
rn; |
| 1608 |
| 1609 int answerTypeShown = 0; |
| 1610 for (int i = 0; i < mSuggestionItems.size(); i++) { |
| 1611 OmniboxSuggestion suggestion = mSuggestionItems.get(i).getSuggestion
(); |
| 1612 if (suggestion.hasAnswer()) { |
| 1613 try { |
| 1614 answerTypeShown = Integer.parseInt(suggestion.getAnswerType(
)); |
| 1615 } catch (NumberFormatException e) { |
| 1616 Log.e(getClass().getSimpleName(), |
| 1617 "Answer type in dismissed suggestions is not an int:
" |
| 1618 + suggestion.getAnswerType()); |
| 1619 } |
| 1620 break; |
| 1621 } |
| 1622 } |
| 1623 RecordHistogram.recordSparseSlowlyHistogram( |
| 1624 "Omnibox.SuggestionsDismissed.AnswerType", answerTypeShown); |
| 1625 } |
| 1626 |
| 1627 /** |
| 1628 * Signals the autocomplete controller to stop generating omnibox suggestion
s and cancels the |
| 1629 * queued task to start the autocomplete controller, if any. |
| 1630 * |
| 1631 * @param clear Whether to clear the most recent autocomplete results. |
| 1632 */ |
| 1633 private void stopAutocomplete(boolean clear) { |
| 1634 if (mAutocomplete != null) mAutocomplete.stop(clear); |
| 1635 cancelPendingAutocompleteStart(); |
| 1636 } |
| 1637 |
| 1638 /** |
| 1639 * Cancels the queued task to start the autocomplete controller, if any. |
| 1640 */ |
| 1641 private void cancelPendingAutocompleteStart() { |
| 1642 if (mRequestSuggestions != null) { |
| 1643 // There is a request for suggestions either waiting for the native
side |
| 1644 // to start, or on the message queue. Remove it from wherever it is. |
| 1645 if (!mDeferredNativeRunnables.remove(mRequestSuggestions)) { |
| 1646 removeCallbacks(mRequestSuggestions); |
| 1647 } |
| 1648 mRequestSuggestions = null; |
| 1649 } |
| 1650 } |
| 1651 |
| 1652 @Override |
| 1653 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> containe
r) { |
| 1654 // Don't restore the state of the location bar, it can lead to all kind
of bad states with |
| 1655 // the popup. |
| 1656 // When we restore tabs, we focus the selected tab so the URL of the pag
e shows. |
| 1657 } |
| 1658 |
| 1659 /** |
| 1660 * Performs a search query on the current {@link ChromeTab}. This calls |
| 1661 * {@link TemplateUrlService#getUrlForSearchQuery(String)} to get a url base
d on {@code query} |
| 1662 * and loads that url in the current {@link ChromeTab}. |
| 1663 * @param query The {@link String} that represents the text query that shoul
d be searched for. |
| 1664 */ |
| 1665 @VisibleForTesting |
| 1666 public void performSearchQueryForTest(String query) { |
| 1667 if (TextUtils.isEmpty(query)) return; |
| 1668 |
| 1669 String queryUrl = TemplateUrlService.getInstance().getUrlForSearchQuery(
query); |
| 1670 |
| 1671 if (!TextUtils.isEmpty(queryUrl)) { |
| 1672 loadUrl(queryUrl, PageTransition.GENERATED); |
| 1673 } else { |
| 1674 setSearchQuery(query); |
| 1675 } |
| 1676 } |
| 1677 |
| 1678 /** |
| 1679 * Sets the query string in the omnibox (ensuring the URL bar has focus and
triggering |
| 1680 * autocomplete for the specified query) as if the user typed it. |
| 1681 * |
| 1682 * @param query The query to be set in the omnibox. |
| 1683 */ |
| 1684 public void setSearchQuery(final String query) { |
| 1685 if (TextUtils.isEmpty(query)) return; |
| 1686 |
| 1687 if (!mNativeInitialized) { |
| 1688 mDeferredNativeRunnables.add(new Runnable() { |
| 1689 @Override |
| 1690 public void run() { |
| 1691 setSearchQuery(query); |
| 1692 } |
| 1693 }); |
| 1694 return; |
| 1695 } |
| 1696 |
| 1697 setUrlBarText(null, null, query); |
| 1698 mUrlBar.setSelection(0, mUrlBar.getText().length()); |
| 1699 mUrlBar.requestFocus(); |
| 1700 stopAutocomplete(false); |
| 1701 if (getCurrentTab() != null) { |
| 1702 mAutocomplete.start( |
| 1703 getCurrentTab().getProfile(), getCurrentTab().getUrl(), quer
y, false); |
| 1704 } |
| 1705 post(new Runnable() { |
| 1706 @Override |
| 1707 public void run() { |
| 1708 UiUtils.showKeyboard(mUrlBar); |
| 1709 } |
| 1710 }); |
| 1711 } |
| 1712 |
| 1713 /** |
| 1714 * Whether {@code v} is a location icon which can be clicked to show the |
| 1715 * origin info dialog. |
| 1716 */ |
| 1717 private boolean isLocationIcon(View v) { |
| 1718 return v == mSecurityButton || v == mNavigationButton; |
| 1719 } |
| 1720 |
| 1721 @Override |
| 1722 public void onClick(View v) { |
| 1723 if (v == mDeleteButton) { |
| 1724 if (!TextUtils.isEmpty(mUrlBar.getQueryText())) { |
| 1725 setUrlBarText(null, null, ""); |
| 1726 hideSuggestions(); |
| 1727 } |
| 1728 |
| 1729 startZeroSuggest(); |
| 1730 return; |
| 1731 } else if (!mUrlHasFocus && isLocationIcon(v)) { |
| 1732 Tab currentTab = getCurrentTab(); |
| 1733 if (currentTab != null && currentTab.getWebContents() != null) { |
| 1734 WebsiteSettingsPopup.show(getContext(), currentTab.getProfile(), |
| 1735 currentTab.getWebContents()); |
| 1736 } |
| 1737 } else if (v == mMicButton) { |
| 1738 RecordUserAction.record("MobileOmniboxVoiceSearch"); |
| 1739 startVoiceRecognition(); |
| 1740 } |
| 1741 } |
| 1742 |
| 1743 /** |
| 1744 * Whether we want to be showing inline autocomplete results. We don't want
to show them as the |
| 1745 * user deletes input. Also if there is a composition (e.g. while using the
Japanese IME), |
| 1746 * we must not autocomplete or we'll destroy the composition. |
| 1747 * @return Whether we want to be showing inline autocomplete results. |
| 1748 */ |
| 1749 private boolean shouldAutocomplete() { |
| 1750 if (mLastUrlEditWasDelete) return false; |
| 1751 Editable text = mUrlBar.getText(); |
| 1752 |
| 1753 return mUrlBar.isCursorAtEndOfTypedText() |
| 1754 && BaseInputConnection.getComposingSpanEnd(text) |
| 1755 == BaseInputConnection.getComposingSpanStart(text); |
| 1756 } |
| 1757 |
| 1758 @Override |
| 1759 public void onSuggestionsReceived(List<OmniboxSuggestion> newSuggestions, |
| 1760 String inlineAutocompleteText) { |
| 1761 // This is a callback from a listener that is set up by onNativeLibraryR
eady, |
| 1762 // so can only be called once the native side is set up. |
| 1763 assert mNativeInitialized : "Suggestions received before native side int
ialialized"; |
| 1764 |
| 1765 if (getCurrentTab() == null) { |
| 1766 // If the current tab is not available, drop the suggestions and hid
e the autocomplete. |
| 1767 hideSuggestions(); |
| 1768 return; |
| 1769 } |
| 1770 |
| 1771 String userText = mUrlBar.getTextWithoutAutocomplete(); |
| 1772 mUrlTextAfterSuggestionsReceived = userText + inlineAutocompleteText; |
| 1773 |
| 1774 boolean itemsChanged = false; |
| 1775 boolean itemCountChanged = false; |
| 1776 // If the length of the incoming suggestions matches that of those curre
ntly being shown, |
| 1777 // replace them inline to allow transient entries to retain their proper
highlighting. |
| 1778 if (mSuggestionItems.size() == newSuggestions.size()) { |
| 1779 for (int index = 0; index < newSuggestions.size(); index++) { |
| 1780 OmniboxResultItem suggestionItem = mSuggestionItems.get(index); |
| 1781 OmniboxSuggestion suggestion = suggestionItem.getSuggestion(); |
| 1782 OmniboxSuggestion newSuggestion = newSuggestions.get(index); |
| 1783 // Determine whether the suggestions have changed. If not, save
some time by not |
| 1784 // redrawing the suggestions UI. |
| 1785 if (suggestion.equals(newSuggestion) |
| 1786 && suggestion.getType() != OmniboxSuggestion.Type.SEARCH
_SUGGEST_TAIL) { |
| 1787 if (suggestionItem.getMatchedQuery().equals(userText)) { |
| 1788 continue; |
| 1789 } else if (!suggestion.getDisplayText().startsWith(userText) |
| 1790 && !suggestion.getUrl().contains(userText)) { |
| 1791 continue; |
| 1792 } |
| 1793 } |
| 1794 mSuggestionItems.set(index, new OmniboxResultItem(newSuggestion,
userText)); |
| 1795 itemsChanged = true; |
| 1796 } |
| 1797 } else { |
| 1798 itemsChanged = true; |
| 1799 itemCountChanged = true; |
| 1800 clearSuggestions(false); |
| 1801 for (int i = 0; i < newSuggestions.size(); i++) { |
| 1802 mSuggestionItems.add(new OmniboxResultItem(newSuggestions.get(i)
, userText)); |
| 1803 } |
| 1804 } |
| 1805 |
| 1806 if (mSuggestionItems.isEmpty()) { |
| 1807 if (mSuggestionsShown) hideSuggestions(); |
| 1808 return; |
| 1809 } |
| 1810 |
| 1811 if (shouldAutocomplete()) { |
| 1812 mUrlBar.setAutocompleteText(userText, inlineAutocompleteText); |
| 1813 } |
| 1814 |
| 1815 // Show the suggestion list. |
| 1816 initSuggestionList(); // It may not have been initialized yet. |
| 1817 mSuggestionList.resetMaxTextWidths(); |
| 1818 |
| 1819 if (itemsChanged) mSuggestionListAdapter.notifySuggestionsChanged(); |
| 1820 if (mUrlBar.hasFocus()) { |
| 1821 setSuggestionsListVisibility(true); |
| 1822 if (itemCountChanged) { |
| 1823 mSuggestionList.updateLayoutParams(); |
| 1824 } |
| 1825 } |
| 1826 |
| 1827 // Update the navigation button to show the default suggestion's icon. |
| 1828 updateNavigationButton(); |
| 1829 |
| 1830 if (!CommandLine.getInstance().hasSwitch(ChromeSwitches.DISABLE_INSTANT) |
| 1831 && PrivacyPreferencesManager.getInstance(getContext()).shouldPre
render()) { |
| 1832 mOmniboxPrerender.prerenderMaybe( |
| 1833 userText, |
| 1834 getOriginalUrl(), |
| 1835 mAutocomplete.getCurrentNativeAutocompleteResult(), |
| 1836 getCurrentTab().getProfile(), |
| 1837 getCurrentTab()); |
| 1838 } |
| 1839 } |
| 1840 |
| 1841 @Override |
| 1842 public void backKeyPressed() { |
| 1843 hideSuggestions(); |
| 1844 UiUtils.hideKeyboard(mUrlBar); |
| 1845 // Revert the URL to match the current page. |
| 1846 setUrlToPageUrl(); |
| 1847 // Focus the page. |
| 1848 Tab currentTab = getCurrentTab(); |
| 1849 if (currentTab != null) currentTab.requestFocus(); |
| 1850 } |
| 1851 |
| 1852 /** |
| 1853 * @return Returns the original url of the page. |
| 1854 */ |
| 1855 public String getOriginalUrl() { |
| 1856 return mOriginalUrl; |
| 1857 } |
| 1858 |
| 1859 /** |
| 1860 * Given the URL display text, this will remove any path portion contained w
ithin. |
| 1861 * @param displayText The text to strip the path from. |
| 1862 * @return A pair where the first item is the text without any path content
(if the path was |
| 1863 * successfully found), and the second item is the path content (or
null if no path |
| 1864 * was found or parsing the path failed). |
| 1865 * @see ToolbarDataProvider#getText() |
| 1866 */ |
| 1867 // TODO(tedchoc): Move this logic into the original display text calculation
. |
| 1868 @VisibleForTesting |
| 1869 public static Pair<String, String> splitPathFromUrlDisplayText(String displa
yText) { |
| 1870 int pathSearchOffset = 0; |
| 1871 Uri uri = Uri.parse(displayText); |
| 1872 String scheme = uri.getScheme(); |
| 1873 if (!TextUtils.isEmpty(scheme)) { |
| 1874 if (UNSUPPORTED_SCHEMES_TO_SPLIT.contains(scheme)) { |
| 1875 return Pair.create(displayText, null); |
| 1876 } else if (ACCEPTED_SCHEMES.contains(scheme)) { |
| 1877 for (pathSearchOffset = scheme.length(); |
| 1878 pathSearchOffset < displayText.length(); |
| 1879 pathSearchOffset++) { |
| 1880 char c = displayText.charAt(pathSearchOffset); |
| 1881 if (c != ':' && c != '/') break; |
| 1882 } |
| 1883 } |
| 1884 } |
| 1885 int pathOffset = -1; |
| 1886 if (pathSearchOffset < displayText.length()) { |
| 1887 pathOffset = displayText.indexOf('/', pathSearchOffset); |
| 1888 } |
| 1889 if (pathOffset != -1) { |
| 1890 String prePathText = displayText.substring(0, pathOffset); |
| 1891 // If the '/' is the last character and the beginning of the path, t
hen just drop |
| 1892 // the path entirely. |
| 1893 String pathText = pathOffset == displayText.length() - 1 |
| 1894 ? null : displayText.substring(pathOffset); |
| 1895 return Pair.create(prePathText, pathText); |
| 1896 } |
| 1897 return Pair.create(displayText, null); |
| 1898 } |
| 1899 |
| 1900 /** |
| 1901 * Sets the displayed URL to be the URL of the page currently showing. |
| 1902 * |
| 1903 * <p>The URL is converted to the most user friendly format (removing HTTP:/
/ for example). |
| 1904 * |
| 1905 * <p>If the current tab is null, the URL text will be cleared. |
| 1906 */ |
| 1907 @Override |
| 1908 public void setUrlToPageUrl() { |
| 1909 // If the URL is currently focused, do not replace the text they have en
tered with the URL. |
| 1910 // Once they stop editing the URL, the current tab's URL will automatica
lly be filled in. |
| 1911 if (mUrlBar.hasFocus()) return; |
| 1912 |
| 1913 mQueryInTheOmnibox = false; |
| 1914 |
| 1915 if (getCurrentTab() == null) { |
| 1916 setUrlBarText(null, null, ""); |
| 1917 return; |
| 1918 } |
| 1919 |
| 1920 // Profile may be null if switching to a tab that has not yet been initi
alized. |
| 1921 Profile profile = getCurrentTab().getProfile(); |
| 1922 if (profile != null) mOmniboxPrerender.clear(profile); |
| 1923 |
| 1924 String url = getCurrentTab().getUrl().trim(); |
| 1925 mOriginalUrl = url; |
| 1926 |
| 1927 if (NativePageFactory.isNativePageUrl(url, getCurrentTab().isIncognito()
)) { |
| 1928 // Don't show anything for Chrome URLs. |
| 1929 setUrlBarText("", null, null); |
| 1930 return; |
| 1931 } |
| 1932 |
| 1933 // Background view has similar case as snapshot. |
| 1934 BackgroundContentViewHelper backgroundViewHelper = |
| 1935 getCurrentTab().getBackgroundContentViewHelper(); |
| 1936 boolean hasPendingBackgroundPage = |
| 1937 backgroundViewHelper != null && backgroundViewHelper.hasPendingB
ackgroundPage(); |
| 1938 boolean isTransitioningFromPreviewPageToOriginal = |
| 1939 showingOriginalUrlForPreview() && !hasPendingBackgroundPage; |
| 1940 mShowingOriginalUrlForPreview = hasPendingBackgroundPage; |
| 1941 |
| 1942 boolean showingQuery = false; |
| 1943 String displayText = mToolbarDataProvider.getText(); |
| 1944 int securityLevel = getSecurityLevel(); |
| 1945 if (securityLevel != ConnectionSecurityHelperSecurityLevel.SECURITY_ERRO
R |
| 1946 && !TextUtils.isEmpty(displayText) |
| 1947 && mToolbarDataProvider.wouldReplaceURL()) { |
| 1948 url = displayText.trim(); |
| 1949 showingQuery = true; |
| 1950 mQueryInTheOmnibox = true; |
| 1951 } |
| 1952 String path = null; |
| 1953 if (!showingQuery && FeatureUtilities.isDocumentMode(getContext())) { |
| 1954 Pair<String, String> urlText = splitPathFromUrlDisplayText(displayTe
xt); |
| 1955 displayText = urlText.first; |
| 1956 path = urlText.second; |
| 1957 } |
| 1958 |
| 1959 if (DomDistillerUrlUtils.isDistilledPage(url)) { |
| 1960 if (isStoredArticle(url)) { |
| 1961 DomDistillerService domDistillerService = |
| 1962 DomDistillerServiceFactory.getForProfile(profile); |
| 1963 String originalUrl = domDistillerService.getUrlForEntry( |
| 1964 DomDistillerUrlUtils.getValueForKeyInUrl(url, "entry_id"
)); |
| 1965 displayText = |
| 1966 DomDistillerTabUtils.getFormattedUrlFromOriginalDistille
rUrl(originalUrl); |
| 1967 } else if (DomDistillerUrlUtils.getOriginalUrlFromDistillerUrl(url)
!= null) { |
| 1968 String originalUrl = DomDistillerUrlUtils.getOriginalUrlFromDist
illerUrl(url); |
| 1969 displayText = |
| 1970 DomDistillerTabUtils.getFormattedUrlFromOriginalDistille
rUrl(originalUrl); |
| 1971 } |
| 1972 } |
| 1973 |
| 1974 if (setUrlBarText(displayText, path, url) || isTransitioningFromPreviewP
ageToOriginal) { |
| 1975 mUrlBar.deEmphasizeUrl(); |
| 1976 emphasizeUrl(); |
| 1977 } |
| 1978 if (showingQuery) { |
| 1979 updateNavigationButton(); |
| 1980 } |
| 1981 updateCustomSelectionActionModeCallback(); |
| 1982 } |
| 1983 |
| 1984 /** |
| 1985 * Changes the text on the url bar |
| 1986 * @param displayText The text (URL or search terms) for user display. |
| 1987 * @param trailingText The trailing text (path portion of the URL) to be dis
played separately. |
| 1988 * @param text The original text (URL or search terms) for copy/cut. |
| 1989 * @return Whether the URL was changed as a result of this call. |
| 1990 */ |
| 1991 private boolean setUrlBarText(String displayText, String trailingText, Strin
g text) { |
| 1992 mIgnoreURLBarModification = true; |
| 1993 boolean urlChanged = mUrlContainer.setUrlText(displayText, trailingText,
text); |
| 1994 mIgnoreURLBarModification = false; |
| 1995 return urlChanged; |
| 1996 } |
| 1997 |
| 1998 /** |
| 1999 * Sets whether modifications to the URL bar should be ignored. |
| 2000 */ |
| 2001 @Override |
| 2002 public void setIgnoreURLBarModification(boolean value) { |
| 2003 mIgnoreURLBarModification = value; |
| 2004 } |
| 2005 |
| 2006 private void loadUrlFromOmniboxMatch(String url, int transition, int matchPo
sition, |
| 2007 OmniboxSuggestion.Type type) { |
| 2008 // loadUrl modifies AutocompleteController's state clearing the native |
| 2009 // AutocompleteResults needed by onSuggestionsSelected. Therefore, |
| 2010 // loadUrl should should be invoked last. |
| 2011 Tab currentTab = getCurrentTab(); |
| 2012 String currentPageUrl = currentTab != null ? currentTab.getUrl() : ""; |
| 2013 WebContents webContents = currentTab != null ? currentTab.getWebContents
() : null; |
| 2014 long elapsedTimeSinceModified = mNewOmniboxEditSessionTimestamp > 0 |
| 2015 ? (SystemClock.elapsedRealtime() - mNewOmniboxEditSessionTimesta
mp) : -1; |
| 2016 mAutocomplete.onSuggestionSelected(matchPosition, type, currentPageUrl, |
| 2017 mQueryInTheOmnibox, mUrlFocusedFromFakebox, elapsedTimeSinceModi
fied, |
| 2018 webContents); |
| 2019 loadUrl(url, transition); |
| 2020 } |
| 2021 |
| 2022 private void loadUrl(String url, int transition) { |
| 2023 Tab currentTab = getCurrentTab(); |
| 2024 |
| 2025 // The code of the rest of this class ensures that this can't be called
until the native |
| 2026 // side is initialized |
| 2027 assert mNativeInitialized : "Loading URL before native side initialized"
; |
| 2028 |
| 2029 if (currentTab != null |
| 2030 && (currentTab.isNativePage() || NewTabPage.isNTPUrl(currentTab.
getUrl()))) { |
| 2031 NewTabPageUma.recordOmniboxNavigation(url, transition); |
| 2032 // Passing in an empty string should not do anything unless the user
is at the NTP. |
| 2033 // Since the NTP has no url, pressing enter while clicking on the UR
L bar should refresh |
| 2034 // the page as it does when you click and press enter on any other s
ite. |
| 2035 if (url.isEmpty()) url = currentTab.getUrl(); |
| 2036 } |
| 2037 |
| 2038 // Loads the |url| in the current ContentView and gives focus to the Con
tentView. |
| 2039 if (currentTab != null && !url.isEmpty()) { |
| 2040 LoadUrlParams loadUrlParams = new LoadUrlParams(url); |
| 2041 loadUrlParams.setVerbatimHeaders( |
| 2042 GeolocationHeader.getGeoHeader(getContext(), url, currentTab
.isIncognito())); |
| 2043 loadUrlParams.setTransitionType(transition | PageTransition.FROM_ADD
RESS_BAR); |
| 2044 currentTab.loadUrl(loadUrlParams); |
| 2045 |
| 2046 setUrlToPageUrl(); |
| 2047 RecordUserAction.record("MobileOmniboxSearch"); |
| 2048 RecordUserAction.record("MobileTabClobbered"); |
| 2049 } else { |
| 2050 setUrlToPageUrl(); |
| 2051 } |
| 2052 |
| 2053 if (currentTab != null) currentTab.requestFocus(); |
| 2054 |
| 2055 // Prevent any upcoming omnibox suggestions from showing. We have to do
this after we load |
| 2056 // the URL as this will hide the suggestions and trigger a cancel of the
prerendered page. |
| 2057 stopAutocomplete(true); |
| 2058 } |
| 2059 |
| 2060 /** |
| 2061 * Update the location bar visuals based on a loading state change. |
| 2062 * @param updateUrl Whether to update the URL as a result of the this call. |
| 2063 */ |
| 2064 @Override |
| 2065 public void updateLoadingState(boolean updateUrl) { |
| 2066 if (updateUrl) setUrlToPageUrl(); |
| 2067 updateNavigationButton(); |
| 2068 updateSecurityIcon(getSecurityLevel()); |
| 2069 } |
| 2070 |
| 2071 /** |
| 2072 * @return The ChromeTab currently showing. |
| 2073 */ |
| 2074 @Override |
| 2075 public ChromeTab getCurrentTab() { |
| 2076 if (mToolbarDataProvider == null) return null; |
| 2077 return ChromeTab.fromTab(mToolbarDataProvider.getTab()); |
| 2078 } |
| 2079 |
| 2080 private ContentViewCore getContentViewCore() { |
| 2081 Tab currentTab = getCurrentTab(); |
| 2082 return currentTab != null ? currentTab.getContentViewCore() : null; |
| 2083 } |
| 2084 |
| 2085 private void updateOmniboxResultsContainer() { |
| 2086 if (mSuggestionsShown || mUrlHasFocus) { |
| 2087 if (mOmniboxResultsContainer == null) { |
| 2088 ViewStub overlayStub = |
| 2089 (ViewStub) getRootView().findViewById(R.id.omnibox_resul
ts_container_stub); |
| 2090 mOmniboxResultsContainer = (ViewGroup) overlayStub.inflate(); |
| 2091 mOmniboxResultsContainer.setBackgroundColor(CONTENT_OVERLAY_COLO
R); |
| 2092 // Prevent touch events from propagating down to the chrome view
. |
| 2093 mOmniboxResultsContainer.setOnTouchListener(new OnTouchListener(
) { |
| 2094 @Override |
| 2095 @SuppressLint("ClickableViewAccessibility") |
| 2096 public boolean onTouch(View v, MotionEvent event) { |
| 2097 int action = event.getActionMasked(); |
| 2098 if (action == MotionEvent.ACTION_CANCEL |
| 2099 || action == MotionEvent.ACTION_UP) { |
| 2100 mUrlBar.clearFocus(); |
| 2101 updateOmniboxResultsContainerBackground(false); |
| 2102 } |
| 2103 return true; |
| 2104 } |
| 2105 }); |
| 2106 } |
| 2107 updateOmniboxResultsContainerVisibility(true); |
| 2108 } else if (mOmniboxResultsContainer != null) { |
| 2109 updateOmniboxResultsContainerBackground(false); |
| 2110 } |
| 2111 } |
| 2112 |
| 2113 private void updateOmniboxResultsContainerVisibility(boolean visible) { |
| 2114 boolean currentlyVisible = mOmniboxResultsContainer.getVisibility() == V
ISIBLE; |
| 2115 if (currentlyVisible == visible) { |
| 2116 // This early return is necessary. Otherwise, calling |
| 2117 // updateOmniboxResultsContainerVisibility(true) twice in a row will
update |
| 2118 // mFocusedTabImportantForAccessibilityState incorrectly and cause |
| 2119 // mFocusedTabView to be stuck in IMPORTANT_FOR_ACCESSIBILITY_NO_HID
E_DESCENDANTS mode. |
| 2120 // http://crbug.com/445560 |
| 2121 return; |
| 2122 } |
| 2123 |
| 2124 if (visible) { |
| 2125 mOmniboxResultsContainer.setVisibility(VISIBLE); |
| 2126 |
| 2127 if (getContentViewCore() != null) { |
| 2128 mFocusedTabAccessibilityManager = |
| 2129 getContentViewCore().getBrowserAccessibilityManager(); |
| 2130 if (mFocusedTabAccessibilityManager != null) { |
| 2131 mFocusedTabAccessibilityManager.setVisible(false); |
| 2132 } |
| 2133 } |
| 2134 |
| 2135 if (getCurrentTab() != null && getCurrentTab().getView() != null) { |
| 2136 mFocusedTabView = getCurrentTab().getView(); |
| 2137 mFocusedTabImportantForAccessibilityState = |
| 2138 mFocusedTabView.getImportantForAccessibility(); |
| 2139 mFocusedTabView.setImportantForAccessibility( |
| 2140 IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); |
| 2141 } |
| 2142 } else { |
| 2143 mOmniboxResultsContainer.setVisibility(INVISIBLE); |
| 2144 |
| 2145 if (mFocusedTabAccessibilityManager != null) { |
| 2146 mFocusedTabAccessibilityManager.setVisible(true); |
| 2147 mFocusedTabAccessibilityManager = null; |
| 2148 } |
| 2149 |
| 2150 if (mFocusedTabView != null) { |
| 2151 mFocusedTabView.setImportantForAccessibility( |
| 2152 mFocusedTabImportantForAccessibilityState); |
| 2153 mFocusedTabView = null; |
| 2154 } |
| 2155 } |
| 2156 } |
| 2157 |
| 2158 /** |
| 2159 * Set the background of the omnibox results container. |
| 2160 * @param visible Whether the background should be made visible. |
| 2161 */ |
| 2162 private void updateOmniboxResultsContainerBackground(boolean visible) { |
| 2163 if (getToolbarDataProvider() == null) return; |
| 2164 |
| 2165 NewTabPage ntp = getToolbarDataProvider().getNewTabPageForCurrentTab(); |
| 2166 boolean locationBarShownInNTP = ntp != null && ntp.isLocationBarShownInN
TP(); |
| 2167 if (visible) { |
| 2168 if (locationBarShownInNTP) { |
| 2169 mOmniboxResultsContainer.getBackground().setAlpha(0); |
| 2170 } else { |
| 2171 fadeInOmniboxResultsContainerBackground(); |
| 2172 } |
| 2173 } else { |
| 2174 if (locationBarShownInNTP) { |
| 2175 updateOmniboxResultsContainerVisibility(false); |
| 2176 } else { |
| 2177 fadeOutOmniboxResultsContainerBackground(); |
| 2178 } |
| 2179 } |
| 2180 } |
| 2181 |
| 2182 /** |
| 2183 * Trigger a fade in of the omnibox results background. |
| 2184 */ |
| 2185 protected void fadeInOmniboxResultsContainerBackground() { |
| 2186 if (mFadeInOmniboxBackgroundAnimator == null) { |
| 2187 mFadeInOmniboxBackgroundAnimator = ObjectAnimator.ofInt( |
| 2188 getRootView().findViewById(R.id.omnibox_results_container).g
etBackground(), |
| 2189 "alpha", 0, 255); |
| 2190 mFadeInOmniboxBackgroundAnimator.setDuration(OMNIBOX_CONTAINER_BACKG
ROUND_FADE_MS); |
| 2191 mFadeInOmniboxBackgroundAnimator.setInterpolator( |
| 2192 BakedBezierInterpolator.FADE_IN_CURVE); |
| 2193 } |
| 2194 runOmniboxResultsFadeAnimation(mFadeInOmniboxBackgroundAnimator); |
| 2195 } |
| 2196 |
| 2197 private void fadeOutOmniboxResultsContainerBackground() { |
| 2198 if (mFadeOutOmniboxBackgroundAnimator == null) { |
| 2199 mFadeOutOmniboxBackgroundAnimator = ObjectAnimator.ofInt( |
| 2200 getRootView().findViewById(R.id.omnibox_results_container).g
etBackground(), |
| 2201 "alpha", 255, 0); |
| 2202 mFadeOutOmniboxBackgroundAnimator.setDuration(OMNIBOX_CONTAINER_BACK
GROUND_FADE_MS); |
| 2203 mFadeOutOmniboxBackgroundAnimator.setInterpolator( |
| 2204 BakedBezierInterpolator.FADE_OUT_CURVE); |
| 2205 mFadeOutOmniboxBackgroundAnimator.addListener(new AnimatorListenerAd
apter() { |
| 2206 private boolean mIsCancelled; |
| 2207 |
| 2208 @Override |
| 2209 public void onAnimationStart(Animator animation) { |
| 2210 mIsCancelled = false; |
| 2211 } |
| 2212 |
| 2213 @Override |
| 2214 public void onAnimationCancel(Animator animation) { |
| 2215 mIsCancelled = true; |
| 2216 } |
| 2217 |
| 2218 @Override |
| 2219 public void onAnimationEnd(Animator animation) { |
| 2220 if (mIsCancelled) return; |
| 2221 updateOmniboxResultsContainerVisibility(false); |
| 2222 } |
| 2223 }); |
| 2224 } |
| 2225 runOmniboxResultsFadeAnimation(mFadeOutOmniboxBackgroundAnimator); |
| 2226 } |
| 2227 |
| 2228 private void runOmniboxResultsFadeAnimation(Animator fadeAnimation) { |
| 2229 if (mOmniboxBackgroundAnimator == fadeAnimation |
| 2230 && mOmniboxBackgroundAnimator.isRunning()) { |
| 2231 return; |
| 2232 } else if (mOmniboxBackgroundAnimator != null) { |
| 2233 mOmniboxBackgroundAnimator.cancel(); |
| 2234 } |
| 2235 mOmniboxBackgroundAnimator = fadeAnimation; |
| 2236 mOmniboxBackgroundAnimator.start(); |
| 2237 } |
| 2238 |
| 2239 /** |
| 2240 * @return Whether voice search is supported in the current browser configur
ation. |
| 2241 */ |
| 2242 protected boolean isVoiceSearchEnabled() { |
| 2243 return mToolbarDataProvider != null && !mToolbarDataProvider.isIncognito
() |
| 2244 && FeatureUtilities.isRecognitionIntentPresent(getContext(), tru
e); |
| 2245 } |
| 2246 |
| 2247 @Override |
| 2248 protected void onWindowVisibilityChanged(int visibility) { |
| 2249 super.onWindowVisibilityChanged(visibility); |
| 2250 if (visibility == View.VISIBLE) updateMicButtonState(); |
| 2251 } |
| 2252 |
| 2253 /** |
| 2254 * Call to notify the location bar that the state of the voice search microp
hone button may |
| 2255 * need to be updated. |
| 2256 */ |
| 2257 @Override |
| 2258 public void updateMicButtonState() { |
| 2259 mMicButton.setVisibility(isVoiceSearchEnabled() ? View.VISIBLE : View.GO
NE); |
| 2260 } |
| 2261 |
| 2262 /** |
| 2263 * Call to force the UI to update the state of various buttons based on whet
her or not the |
| 2264 * current tab is incognito. |
| 2265 */ |
| 2266 @Override |
| 2267 public void updateVisualsForState() { |
| 2268 if (updateUseDarkColors() || getToolbarDataProvider().isUsingBrandColor(
)) { |
| 2269 updateSecurityIcon(getSecurityLevel()); |
| 2270 } |
| 2271 ColorStateList colorStateList = getResources().getColorStateList(mUseDar
kColors |
| 2272 ? R.color.dark_mode_tint : R.color.light_mode_tint); |
| 2273 mMicButton.setTint(colorStateList); |
| 2274 mDeleteButton.setTint(colorStateList); |
| 2275 |
| 2276 setNavigationButtonType(mNavigationButtonType); |
| 2277 mUrlContainer.setUseDarkTextColors(mUseDarkColors); |
| 2278 |
| 2279 if (mSuggestionList != null) { |
| 2280 mSuggestionList.setBackground(getSuggestionPopupBackground()); |
| 2281 } |
| 2282 mSuggestionListAdapter.setUseDarkColors(mUseDarkColors); |
| 2283 } |
| 2284 |
| 2285 /** |
| 2286 * Checks the current specs and updates {@link LocationBar#mUseDarkColors} i
f necessary. |
| 2287 * @return Whether {@link LocationBar#mUseDarkColors} has been updated. |
| 2288 */ |
| 2289 private boolean updateUseDarkColors() { |
| 2290 Tab tab = getCurrentTab(); |
| 2291 boolean brandColorNeedsLightText = false; |
| 2292 if (getToolbarDataProvider().isUsingBrandColor() && !mUrlHasFocus) { |
| 2293 int currentPrimaryColor = getToolbarDataProvider().getPrimaryColor()
; |
| 2294 brandColorNeedsLightText = |
| 2295 BrandColorUtils.shouldUseLightDrawablesForToolbar(currentPri
maryColor); |
| 2296 } |
| 2297 |
| 2298 boolean useDarkColors = tab == null || !(tab.isIncognito() || brandColor
NeedsLightText); |
| 2299 boolean hasChanged = useDarkColors != mUseDarkColors; |
| 2300 mUseDarkColors = useDarkColors; |
| 2301 |
| 2302 return hasChanged; |
| 2303 } |
| 2304 |
| 2305 /** |
| 2306 * Triggers a voice recognition intent to allow the user to specify a search
query. |
| 2307 */ |
| 2308 @Override |
| 2309 public void startVoiceRecognition() { |
| 2310 Activity activity = mWindowAndroid.getActivity().get(); |
| 2311 if (activity == null) return; |
| 2312 |
| 2313 Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); |
| 2314 intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, |
| 2315 RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH); |
| 2316 intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, |
| 2317 activity.getComponentName().flattenToString()); |
| 2318 intent.putExtra(RecognizerIntent.EXTRA_WEB_SEARCH_ONLY, true); |
| 2319 |
| 2320 if (mWindowAndroid.showCancelableIntent(intent, this, R.string.voice_sea
rch_error) < 0) { |
| 2321 // Requery whether or not the recognition intent can be handled. |
| 2322 FeatureUtilities.isRecognitionIntentPresent(activity, false); |
| 2323 updateMicButtonState(); |
| 2324 } |
| 2325 } |
| 2326 |
| 2327 // WindowAndroid.IntentCallback implementation: |
| 2328 @Override |
| 2329 public void onIntentCompleted(WindowAndroid window, int resultCode, |
| 2330 ContentResolver contentResolver, Intent data) { |
| 2331 if (resultCode != Activity.RESULT_OK) return; |
| 2332 if (data.getExtras() == null) return; |
| 2333 |
| 2334 VoiceResult topResult = mAutocomplete.onVoiceResults(data.getExtras()); |
| 2335 if (topResult == null) return; |
| 2336 |
| 2337 String topResultQuery = topResult.getMatch(); |
| 2338 if (TextUtils.isEmpty(topResultQuery)) return; |
| 2339 |
| 2340 if (topResult.getConfidence() < VOICE_SEARCH_CONFIDENCE_NAVIGATE_THRESHO
LD) { |
| 2341 setSearchQuery(topResultQuery); |
| 2342 return; |
| 2343 } |
| 2344 |
| 2345 String url = AutocompleteController.nativeQualifyPartialURLQuery(topResu
ltQuery); |
| 2346 if (url == null) { |
| 2347 url = TemplateUrlService.getInstance().getUrlForVoiceSearchQuery( |
| 2348 topResultQuery); |
| 2349 } |
| 2350 loadUrl(url, PageTransition.TYPED); |
| 2351 } |
| 2352 |
| 2353 /** |
| 2354 * Tracks how the URL bar was focused (i.e. from the omnibox or the fakebox)
and records a UMA |
| 2355 * stat for this. Should be called whenever the URL bar gains or loses focus
. |
| 2356 * @param hasFocus Whether the URL bar now has focus. |
| 2357 */ |
| 2358 private void updateFocusSource(boolean hasFocus) { |
| 2359 if (!hasFocus) { |
| 2360 mUrlFocusedFromFakebox = false; |
| 2361 mHasRecordedUrlFocusSource = false; |
| 2362 return; |
| 2363 } |
| 2364 |
| 2365 // Record UMA event for how the URL bar was focused. |
| 2366 assert !mHasRecordedUrlFocusSource; |
| 2367 if (mHasRecordedUrlFocusSource) return; |
| 2368 |
| 2369 Tab currentTab = getCurrentTab(); |
| 2370 if (currentTab == null) return; |
| 2371 |
| 2372 String url = currentTab.getUrl(); |
| 2373 if (mUrlFocusedFromFakebox) { |
| 2374 RecordUserAction.record("MobileFocusedFakeboxOnNtp"); |
| 2375 } else { |
| 2376 if (currentTab.isNativePage() && NewTabPage.isNTPUrl(url)) { |
| 2377 RecordUserAction.record("MobileFocusedOmniboxOnNtp"); |
| 2378 } else { |
| 2379 RecordUserAction.record("MobileFocusedOmniboxNotOnNtp"); |
| 2380 } |
| 2381 } |
| 2382 mHasRecordedUrlFocusSource = true; |
| 2383 } |
| 2384 |
| 2385 @Override |
| 2386 public void onTabLoadingNTP(NewTabPage ntp) { |
| 2387 ntp.setFakeboxDelegate(this); |
| 2388 } |
| 2389 |
| 2390 @Override |
| 2391 public View getContainerView() { |
| 2392 return this; |
| 2393 } |
| 2394 } |
OLD | NEW |