| Index: chrome/android/java_staging/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
|
| diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..0ba64c3f7589aa254eb9920bec7ff26d1a2dfd1b
|
| --- /dev/null
|
| +++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
|
| @@ -0,0 +1,2394 @@
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +package org.chromium.chrome.browser.omnibox;
|
| +
|
| +import static org.chromium.chrome.browser.toolbar.ToolbarPhone.URL_FOCUS_CHANGE_ANIMATION_DURATION_MS;
|
| +
|
| +import android.animation.Animator;
|
| +import android.animation.AnimatorListenerAdapter;
|
| +import android.animation.AnimatorSet;
|
| +import android.animation.ObjectAnimator;
|
| +import android.annotation.SuppressLint;
|
| +import android.app.Activity;
|
| +import android.content.ClipData;
|
| +import android.content.ClipboardManager;
|
| +import android.content.ContentResolver;
|
| +import android.content.Context;
|
| +import android.content.Intent;
|
| +import android.content.res.ColorStateList;
|
| +import android.graphics.Color;
|
| +import android.graphics.PorterDuff;
|
| +import android.graphics.Rect;
|
| +import android.graphics.drawable.ColorDrawable;
|
| +import android.graphics.drawable.Drawable;
|
| +import android.net.Uri;
|
| +import android.os.Parcelable;
|
| +import android.os.SystemClock;
|
| +import android.provider.Settings;
|
| +import android.speech.RecognizerIntent;
|
| +import android.text.Editable;
|
| +import android.text.InputType;
|
| +import android.text.Selection;
|
| +import android.text.TextUtils;
|
| +import android.text.TextWatcher;
|
| +import android.util.AttributeSet;
|
| +import android.util.Log;
|
| +import android.util.Pair;
|
| +import android.util.SparseArray;
|
| +import android.view.ActionMode;
|
| +import android.view.KeyEvent;
|
| +import android.view.LayoutInflater;
|
| +import android.view.Menu;
|
| +import android.view.MenuItem;
|
| +import android.view.MotionEvent;
|
| +import android.view.View;
|
| +import android.view.View.OnClickListener;
|
| +import android.view.ViewGroup;
|
| +import android.view.ViewStub;
|
| +import android.view.inputmethod.BaseInputConnection;
|
| +import android.widget.FrameLayout;
|
| +import android.widget.ImageButton;
|
| +import android.widget.ImageView;
|
| +import android.widget.ListView;
|
| +
|
| +import com.google.android.apps.chrome.R;
|
| +
|
| +import org.chromium.base.ApiCompatibilityUtils;
|
| +import org.chromium.base.CollectionUtil;
|
| +import org.chromium.base.CommandLine;
|
| +import org.chromium.base.VisibleForTesting;
|
| +import org.chromium.base.metrics.RecordHistogram;
|
| +import org.chromium.base.metrics.RecordUserAction;
|
| +import org.chromium.chrome.browser.ChromeSwitches;
|
| +import org.chromium.chrome.browser.ContextualMenuBar;
|
| +import org.chromium.chrome.browser.ContextualMenuBar.ActionBarDelegate;
|
| +import org.chromium.chrome.browser.CustomSelectionActionModeCallback;
|
| +import org.chromium.chrome.browser.Tab;
|
| +import org.chromium.chrome.browser.WebsiteSettingsPopup;
|
| +import org.chromium.chrome.browser.WindowDelegate;
|
| +import org.chromium.chrome.browser.appmenu.AppMenuButtonHelper;
|
| +import org.chromium.chrome.browser.document.BrandColorUtils;
|
| +import org.chromium.chrome.browser.dom_distiller.DomDistillerServiceFactory;
|
| +import org.chromium.chrome.browser.dom_distiller.DomDistillerTabUtils;
|
| +import org.chromium.chrome.browser.ntp.NativePageFactory;
|
| +import org.chromium.chrome.browser.ntp.NewTabPage;
|
| +import org.chromium.chrome.browser.ntp.NewTabPage.FakeboxDelegate;
|
| +import org.chromium.chrome.browser.ntp.NewTabPageUma;
|
| +import org.chromium.chrome.browser.omnibox.AutocompleteController.OnSuggestionsReceivedListener;
|
| +import org.chromium.chrome.browser.omnibox.OmniboxResultsAdapter.OmniboxResultItem;
|
| +import org.chromium.chrome.browser.omnibox.OmniboxResultsAdapter.OmniboxSuggestionDelegate;
|
| +import org.chromium.chrome.browser.omnibox.OmniboxSuggestion.Type;
|
| +import org.chromium.chrome.browser.omnibox.VoiceSuggestionProvider.VoiceResult;
|
| +import org.chromium.chrome.browser.omnibox.geo.GeolocationHeader;
|
| +import org.chromium.chrome.browser.preferences.privacy.PrivacyPreferencesManager;
|
| +import org.chromium.chrome.browser.profiles.Profile;
|
| +import org.chromium.chrome.browser.search_engines.TemplateUrlService;
|
| +import org.chromium.chrome.browser.ssl.ConnectionSecurityHelperSecurityLevel;
|
| +import org.chromium.chrome.browser.tab.BackgroundContentViewHelper;
|
| +import org.chromium.chrome.browser.tab.ChromeTab;
|
| +import org.chromium.chrome.browser.toolbar.ToolbarDataProvider;
|
| +import org.chromium.chrome.browser.util.FeatureUtilities;
|
| +import org.chromium.chrome.browser.util.KeyNavigationUtil;
|
| +import org.chromium.chrome.browser.util.ViewUtils;
|
| +import org.chromium.chrome.browser.widget.TintedImageButton;
|
| +import org.chromium.components.dom_distiller.core.DomDistillerService;
|
| +import org.chromium.components.dom_distiller.core.DomDistillerUrlUtils;
|
| +import org.chromium.content.browser.ContentViewCore;
|
| +import org.chromium.content.browser.accessibility.BrowserAccessibilityManager;
|
| +import org.chromium.content_public.browser.LoadUrlParams;
|
| +import org.chromium.content_public.browser.WebContents;
|
| +import org.chromium.ui.UiUtils;
|
| +import org.chromium.ui.base.DeviceFormFactor;
|
| +import org.chromium.ui.base.PageTransition;
|
| +import org.chromium.ui.base.WindowAndroid;
|
| +import org.chromium.ui.interpolators.BakedBezierInterpolator;
|
| +
|
| +import java.util.ArrayList;
|
| +import java.util.HashSet;
|
| +import java.util.List;
|
| +
|
| +/**
|
| + * This class represents the location bar where the user types in URLs and
|
| + * search terms.
|
| + */
|
| +public class LocationBarLayout extends FrameLayout implements OnClickListener,
|
| + OnSuggestionsReceivedListener, LocationBar, FakeboxDelegate,
|
| + WindowAndroid.IntentCallback {
|
| +
|
| + // Delay triggering the omnibox results upon key press to allow the location bar to repaint
|
| + // with the new characters.
|
| + private static final long OMNIBOX_SUGGESTION_START_DELAY_MS = 30;
|
| +
|
| + private static final int OMNIBOX_CONTAINER_BACKGROUND_FADE_MS = 250;
|
| +
|
| + // The minimum confidence threshold that will result in navigating directly to a voice search
|
| + // response (as opposed to treating it like a typed string in the Omnibox).
|
| + private static final float VOICE_SEARCH_CONFIDENCE_NAVIGATE_THRESHOLD = 0.9f;
|
| +
|
| + private static final int CONTENT_OVERLAY_COLOR = Color.argb(166, 0, 0, 0);
|
| + private static final int OMNIBOX_RESULTS_BG_COLOR = Color.rgb(245, 245, 246);
|
| + private static final int OMNIBOX_INCOGNITO_RESULTS_BG_COLOR = Color.rgb(50, 50, 50);
|
| +
|
| + /**
|
| + * URI schemes that ContentView can handle.
|
| + *
|
| + * Copied from UrlUtilities.java. UrlUtilities uses a URI to check for schemes, which
|
| + * is more strict than Uri and causes the path stripping to fail.
|
| + *
|
| + * The following additions have been made: "chrome", "ftp".
|
| + */
|
| + private static final HashSet<String> ACCEPTED_SCHEMES = CollectionUtil.newHashSet(
|
| + "about", "data", "file", "ftp", "http", "https", "inline", "javascript", "chrome");
|
| + private static final HashSet<String> UNSUPPORTED_SCHEMES_TO_SPLIT =
|
| + CollectionUtil.newHashSet("file", "javascript", "data");
|
| +
|
| + protected ImageView mNavigationButton;
|
| + protected ImageButton mSecurityButton;
|
| + protected TintedImageButton mDeleteButton;
|
| + protected TintedImageButton mMicButton;
|
| + protected UrlBar mUrlBar;
|
| + protected UrlContainer mUrlContainer;
|
| + private ContextualMenuBar mContextualMenuBar = null;
|
| +
|
| + private AutocompleteController mAutocomplete;
|
| +
|
| + private ToolbarDataProvider mToolbarDataProvider;
|
| + private UrlFocusChangeListener mUrlFocusChangeListener;
|
| +
|
| + private boolean mNativeInitialized;
|
| +
|
| + private final List<Runnable> mDeferredNativeRunnables = new ArrayList<Runnable>();
|
| +
|
| + // The type of the navigation button currently showing.
|
| + private NavigationButtonType mNavigationButtonType;
|
| +
|
| + // The type of the security icon currently active.
|
| + private int mSecurityIconType;
|
| +
|
| + private final OmniboxResultsAdapter mSuggestionListAdapter;
|
| + private OmniboxSuggestionsList mSuggestionList;
|
| +
|
| + private final List<OmniboxResultItem> mSuggestionItems;
|
| +
|
| + /**
|
| + * The text shown in the URL bar (user text + inline autocomplete) after the most recent set of
|
| + * omnibox suggestions was received. When the user presses enter in the omnibox, this value is
|
| + * compared to the URL bar text to determine whether the first suggestion is still valid.
|
| + */
|
| + private String mUrlTextAfterSuggestionsReceived;
|
| +
|
| + // Set to true when the URL bar text is modified programmatically. Initially set
|
| + // to true until the old state has been loaded.
|
| + private boolean mIgnoreURLBarModification = true;
|
| + private boolean mIgnoreOmniboxItemSelection = true;
|
| +
|
| + private boolean mLastUrlEditWasDelete = false;
|
| +
|
| + // True if we are showing the search query instead of the url.
|
| + private boolean mQueryInTheOmnibox = false;
|
| +
|
| + private String mOriginalUrl = "";
|
| +
|
| + private WindowAndroid mWindowAndroid;
|
| + private WindowDelegate mWindowDelegate;
|
| +
|
| + private Runnable mRequestSuggestions;
|
| +
|
| + private ViewGroup mOmniboxResultsContainer;
|
| + private ObjectAnimator mFadeInOmniboxBackgroundAnimator;
|
| + private ObjectAnimator mFadeOutOmniboxBackgroundAnimator;
|
| + private Animator mOmniboxBackgroundAnimator;
|
| +
|
| + private boolean mSuggestionsShown;
|
| + private boolean mUrlHasFocus;
|
| + private boolean mUrlFocusedFromFakebox;
|
| + private boolean mHasRecordedUrlFocusSource;
|
| +
|
| + // Set to true when the user has started typing new input in the omnibox, set to false
|
| + // when the omnibox loses focus or becomes empty.
|
| + private boolean mHasStartedNewOmniboxEditSession;
|
| + // The timestamp (using SystemClock.elapsedRealtime()) at the point when the user started
|
| + // modifying the omnibox with new input.
|
| + private long mNewOmniboxEditSessionTimestamp = -1;
|
| +
|
| + private boolean mSecurityButtonShown;
|
| +
|
| + private AnimatorSet mLocationBarIconActiveAnimator;
|
| + private AnimatorSet mSecurityButtonShowAnimator;
|
| + private AnimatorSet mNavigationIconShowAnimator;
|
| +
|
| + private TextWatcher mTextWatcher;
|
| +
|
| + private OmniboxPrerender mOmniboxPrerender;
|
| +
|
| + private View mFocusedTabView;
|
| + private int mFocusedTabImportantForAccessibilityState;
|
| + private BrowserAccessibilityManager mFocusedTabAccessibilityManager;
|
| +
|
| + // True if we are showing original url for preview page. This is will be true when there is a
|
| + // background page loaded in background content view.
|
| + private boolean mShowingOriginalUrlForPreview;
|
| +
|
| + private boolean mSuggestionModalShown;
|
| + private boolean mUseDarkColors;
|
| +
|
| + // True if the user has just selected a suggestion from the suggestion list. This suppresses
|
| + // the recording of the dismissal of the suggestion list. (The list is only considered to have
|
| + // been dismissed if the user didn't choose one of the suggestions shown.) This signal is used
|
| + // instead of a parameter to hideSuggestions because that method is often called from multiple
|
| + // code paths in a not necessarily obvious or even deterministic order.
|
| + private boolean mSuggestionSelectionInProgress;
|
| +
|
| + private CustomSelectionActionModeCallback mDefaultActionModeCallbackForTextEdit;
|
| +
|
| + /**
|
| + * Listener for receiving the messages related with interacting with the omnibox during startup.
|
| + */
|
| + public interface OmniboxLivenessListener {
|
| + /**
|
| + * Called after the first draw when the omnibox can receive touch events.
|
| + */
|
| + void onOmniboxInteractive();
|
| +
|
| + /**
|
| + * Called when the native libraries are loaded and listeners with native components
|
| + * have been initialized.
|
| + */
|
| + void onOmniboxFullyFunctional();
|
| +
|
| + /**
|
| + * Called when the omnibox is focused.
|
| + */
|
| + void onOmniboxFocused();
|
| + }
|
| +
|
| + /**
|
| + * Class to handle text changes in the URL bar, ensuring that the appropriate
|
| + * buttons are displayed as the text changes, and requesting suggestions.
|
| + */
|
| + private final class UrlBarTextWatcher implements TextWatcher {
|
| + @Override
|
| + public void afterTextChanged(final Editable editableText) {
|
| + updateDeleteButtonVisibility();
|
| + updateNavigationButton();
|
| +
|
| + if (mIgnoreURLBarModification) return;
|
| +
|
| + if (!mHasStartedNewOmniboxEditSession && mNativeInitialized) {
|
| + RecordUserAction.record("MobileFirstEditInOmnibox");
|
| + mAutocomplete.resetSession();
|
| + mHasStartedNewOmniboxEditSession = true;
|
| + mNewOmniboxEditSessionTimestamp = SystemClock.elapsedRealtime();
|
| + }
|
| +
|
| + if (!isInTouchMode() && mSuggestionList != null) {
|
| + mSuggestionList.setSelection(0);
|
| + }
|
| +
|
| + final String textWithoutAutocomplete = mUrlBar.getTextWithoutAutocomplete();
|
| +
|
| + stopAutocomplete(false);
|
| + if (TextUtils.isEmpty(textWithoutAutocomplete)) {
|
| + hideSuggestions();
|
| + startZeroSuggest();
|
| + } else {
|
| + assert mRequestSuggestions == null : "Multiple omnibox requests in flight.";
|
| + mRequestSuggestions = new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + boolean preventAutocomplete = !shouldAutocomplete()
|
| + || (editableText != null && Selection.getSelectionEnd(editableText)
|
| + != editableText.length());
|
| + mRequestSuggestions = null;
|
| + if (getCurrentTab() == null) return;
|
| + mAutocomplete.start(
|
| + getCurrentTab().getProfile(),
|
| + getCurrentTab().getUrl(),
|
| + textWithoutAutocomplete, preventAutocomplete);
|
| + }
|
| + };
|
| + if (mNativeInitialized) {
|
| + postDelayed(mRequestSuggestions, OMNIBOX_SUGGESTION_START_DELAY_MS);
|
| + } else {
|
| + mDeferredNativeRunnables.add(mRequestSuggestions);
|
| + }
|
| + }
|
| +
|
| + // Occasionally, was seeing the selection in the URL not being cleared during
|
| + // very rapid editing. This is here to hopefully force a selection reset during
|
| + // deletes.
|
| + if (mLastUrlEditWasDelete) mUrlBar.setSelection(mUrlBar.getSelectionStart());
|
| + }
|
| +
|
| + @Override
|
| + public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
| + cancelPendingAutocompleteStart();
|
| + }
|
| +
|
| + @Override
|
| + public void onTextChanged(CharSequence s, int start, int before, int count) {
|
| + // We need to determine whether the text change was triggered by a delete (so we
|
| + // don't autocomplete of the entered text in that case). With soft-keyboard, there
|
| + // is no way to know that the delete button was pressed, so we track text removal
|
| + // changes.
|
| + mLastUrlEditWasDelete = (count == 0);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Class to handle input from a hardware keyboard when the focus is on the URL bar. In
|
| + * particular, handle navigating the suggestions list from the keyboard.
|
| + */
|
| + private final class UrlBarKeyListener implements OnKeyListener {
|
| + @Override
|
| + public boolean onKey(View v, int keyCode, KeyEvent event) {
|
| + if (KeyNavigationUtil.isGoDown(event)
|
| + && mSuggestionList != null
|
| + && mSuggestionList.isShown()) {
|
| + int suggestionCount = mSuggestionListAdapter.getCount();
|
| + if (mSuggestionList.getSelectedItemPosition() < suggestionCount - 1) {
|
| + if (suggestionCount > 0) mIgnoreOmniboxItemSelection = false;
|
| + } else {
|
| + // Do not pass down events when the last item is already selected as it will
|
| + // dismiss the suggestion list.
|
| + return true;
|
| + }
|
| +
|
| + if (mSuggestionList.getSelectedItemPosition()
|
| + == ListView.INVALID_POSITION) {
|
| + // When clearing the selection after a text change, state is not reset
|
| + // correctly so hitting down again will cause it to start from the previous
|
| + // selection point. We still have to send the key down event to let the list
|
| + // view items take focus, but then we select the first item explicitly.
|
| + boolean result = mSuggestionList.onKeyDown(keyCode, event);
|
| + mSuggestionList.setSelection(0);
|
| + return result;
|
| + } else {
|
| + return mSuggestionList.onKeyDown(keyCode, event);
|
| + }
|
| + } else if (KeyNavigationUtil.isGoUp(event)
|
| + && mSuggestionList != null
|
| + && mSuggestionList.isShown()) {
|
| + if (mSuggestionList.getSelectedItemPosition() != 0
|
| + && mSuggestionListAdapter.getCount() > 0) {
|
| + mIgnoreOmniboxItemSelection = false;
|
| + }
|
| + return mSuggestionList.onKeyDown(keyCode, event);
|
| + } else if (KeyNavigationUtil.isGoRight(event)
|
| + && mSuggestionList != null
|
| + && mSuggestionList.isShown()
|
| + && mSuggestionList.getSelectedItemPosition()
|
| + != ListView.INVALID_POSITION) {
|
| + OmniboxResultItem selectedItem =
|
| + (OmniboxResultItem) mSuggestionListAdapter.getItem(
|
| + mSuggestionList.getSelectedItemPosition());
|
| + // Set the UrlBar text to empty, so that it will trigger a text change when we
|
| + // set the text to the suggestion again.
|
| + setUrlBarText(null, null, "");
|
| + mUrlBar.setText(selectedItem.getSuggestion().getFillIntoEdit());
|
| + mSuggestionList.setSelection(0);
|
| + mUrlBar.setSelection(mUrlBar.getText().length());
|
| + return true;
|
| + } else if (KeyNavigationUtil.isEnter(event)
|
| + && LocationBarLayout.this.getVisibility() == VISIBLE) {
|
| + UiUtils.hideKeyboard(mUrlBar);
|
| + mSuggestionSelectionInProgress = true;
|
| + final String urlText = mUrlBar.getQueryText();
|
| + if (mNativeInitialized) {
|
| + findMatchAndLoadUrl(urlText);
|
| + } else {
|
| + mDeferredNativeRunnables.add(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + findMatchAndLoadUrl(urlText);
|
| + }
|
| + });
|
| + }
|
| + return true;
|
| + }
|
| +
|
| + return false;
|
| + }
|
| +
|
| + private void findMatchAndLoadUrl(String urlText) {
|
| + int suggestionMatchPosition;
|
| + OmniboxSuggestion suggestionMatch;
|
| +
|
| + if (mSuggestionList != null
|
| + && mSuggestionList.isShown()
|
| + && mSuggestionList.getSelectedItemPosition()
|
| + != ListView.INVALID_POSITION) {
|
| + // Bluetooth keyboard case: the user highlighted a suggestion with the arrow
|
| + // keys, then pressed enter.
|
| + suggestionMatchPosition = mSuggestionList.getSelectedItemPosition();
|
| + OmniboxResultItem selectedItem =
|
| + (OmniboxResultItem) mSuggestionListAdapter.getItem(suggestionMatchPosition);
|
| + suggestionMatch = selectedItem.getSuggestion();
|
| + } else if (!mSuggestionItems.isEmpty()
|
| + && urlText.equals(mUrlTextAfterSuggestionsReceived)) {
|
| + // Common case: the user typed something, received suggestions, then pressed enter.
|
| + suggestionMatch = mSuggestionItems.get(0).getSuggestion();
|
| + suggestionMatchPosition = 0;
|
| + } else {
|
| + // Less common case: there are no valid omnibox suggestions. This can happen if the
|
| + // user tapped the URL bar to dismiss the suggestions, then pressed enter. This can
|
| + // also happen if the user presses enter before any suggestions have been received
|
| + // from the autocomplete controller.
|
| + suggestionMatch = mAutocomplete.classify(urlText);
|
| + suggestionMatchPosition = 0;
|
| +
|
| + // If urlText couldn't be classified, bail.
|
| + if (suggestionMatch == null) return;
|
| + }
|
| +
|
| + String suggestionMatchUrl = updateSuggestionUrlIfNeeded(suggestionMatch,
|
| + suggestionMatchPosition);
|
| +
|
| + // It's important to use the page transition from the suggestion or we might end
|
| + // up saving generated URLs as typed URLs, which would then pollute the subsequent
|
| + // omnibox results.
|
| + loadUrlFromOmniboxMatch(suggestionMatchUrl, suggestionMatch.getTransition(),
|
| + suggestionMatchPosition, suggestionMatch.getType());
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Specifies the types of buttons shown to signify different types of navigation elements.
|
| + */
|
| + protected enum NavigationButtonType {
|
| + PAGE,
|
| + MAGNIFIER,
|
| + EMPTY,
|
| + }
|
| +
|
| + /**
|
| + * @param outRect Populated with a {@link Rect} that represents the {@link Tab} specific content
|
| + * of this {@link LocationBar}.
|
| + */
|
| + public void getContentRect(Rect outRect) {
|
| + outRect.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(),
|
| + getHeight() - getPaddingBottom());
|
| + }
|
| +
|
| + /**
|
| + * A widget for showing a list of omnibox suggestions.
|
| + */
|
| + @VisibleForTesting
|
| + public class OmniboxSuggestionsList extends ListView {
|
| + private final int mSuggestionHeight;
|
| + private final int mSuggestionAnswerHeight;
|
| + private final View mAnchorView;
|
| +
|
| + private final int[] mTempPosition = new int[2];
|
| + private final Rect mTempRect = new Rect();
|
| +
|
| + private final int mBackgroundVerticalPadding;
|
| +
|
| + private float mMaxRequiredWidth;
|
| + private float mMaxMatchContentsWidth;
|
| +
|
| + /**
|
| + * Constructs a new list designed for containing omnibox suggestions.
|
| + * @param context Context used for contained views.
|
| + */
|
| + public OmniboxSuggestionsList(Context context) {
|
| + super(context, null, android.R.attr.dropDownListViewStyle);
|
| + setDivider(null);
|
| + setFocusable(true);
|
| + setFocusableInTouchMode(true);
|
| +
|
| + mSuggestionHeight = context.getResources().getDimensionPixelOffset(
|
| + R.dimen.omnibox_suggestion_height);
|
| + mSuggestionAnswerHeight = context.getResources().getDimensionPixelOffset(
|
| + R.dimen.omnibox_suggestion_answer_height);
|
| +
|
| + int paddingTop = context.getResources().getDimensionPixelOffset(
|
| + R.dimen.omnibox_suggestion_list_padding_top);
|
| + int paddingBottom = context.getResources().getDimensionPixelOffset(
|
| + R.dimen.omnibox_suggestion_list_padding_bottom);
|
| + ApiCompatibilityUtils.setPaddingRelative(this, 0, paddingTop, 0, paddingBottom);
|
| +
|
| + Drawable background = getSuggestionPopupBackground();
|
| + setBackground(background);
|
| + background.getPadding(mTempRect);
|
| +
|
| + mBackgroundVerticalPadding =
|
| + mTempRect.top + mTempRect.bottom + getPaddingTop() + getPaddingBottom();
|
| +
|
| + mAnchorView = LocationBarLayout.this.getRootView().findViewById(R.id.toolbar);
|
| + }
|
| +
|
| + private void show() {
|
| + updateLayoutParams();
|
| + if (getVisibility() != VISIBLE) {
|
| + mIgnoreOmniboxItemSelection = true; // Reset to default value.
|
| + setVisibility(VISIBLE);
|
| + if (getSelectedItemPosition() != 0) setSelection(0);
|
| + }
|
| + updateSuggestionsLayoutDirection(mUrlBar.getUrlDirection());
|
| + }
|
| +
|
| + /**
|
| + * Invalidates all of the suggestion views in the list. Only applicable when this
|
| + * is visible.
|
| + */
|
| + public void invalidateSuggestionViews() {
|
| + if (!isShown()) return;
|
| + ListView suggestionsList = mSuggestionList;
|
| + for (int i = 0; i < suggestionsList.getChildCount(); i++) {
|
| + if (suggestionsList.getChildAt(i) instanceof SuggestionView) {
|
| + suggestionsList.getChildAt(i).postInvalidateOnAnimation();
|
| + }
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Updates the maximum widths required to render the suggestions.
|
| + * This is needed for infinite suggestions where we try to vertically align the leading
|
| + * ellipsis.
|
| + */
|
| + public void resetMaxTextWidths() {
|
| + mMaxRequiredWidth = 0;
|
| + mMaxMatchContentsWidth = 0;
|
| + }
|
| +
|
| + /**
|
| + * Updates the max text width values for the suggestions.
|
| + * @param requiredWidth a new required width.
|
| + * @param matchContentsWidth a new match contents width.
|
| + */
|
| + public void updateMaxTextWidths(float requiredWidth, float matchContentsWidth) {
|
| + mMaxRequiredWidth = Math.max(mMaxRequiredWidth, requiredWidth);
|
| + mMaxMatchContentsWidth = Math.max(mMaxMatchContentsWidth, matchContentsWidth);
|
| + }
|
| +
|
| + /**
|
| + * @return max required width for the suggestions.
|
| + */
|
| + public float getMaxRequiredWidth() {
|
| + return mMaxRequiredWidth;
|
| + }
|
| +
|
| + /**
|
| + * @return max match contents width for the suggestions.
|
| + */
|
| + public float getMaxMatchContentsWidth() {
|
| + return mMaxMatchContentsWidth;
|
| + }
|
| +
|
| + private void updateLayoutParams() {
|
| + boolean updateLayout = false;
|
| + FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
|
| + if (layoutParams == null) {
|
| + layoutParams = new FrameLayout.LayoutParams(0, 0);
|
| + setLayoutParams(layoutParams);
|
| + }
|
| +
|
| + // Compare the relative positions of the anchor view to the list parent view to
|
| + // determine the offset to apply to the suggestions list. By using layout positioning,
|
| + // this avoids issues where getLocationInWindow can be inaccurate on certain devices.
|
| + View contentView =
|
| + LocationBarLayout.this.getRootView().findViewById(android.R.id.content);
|
| + ViewUtils.getRelativeLayoutPosition(contentView, mAnchorView, mTempPosition);
|
| + int anchorX = mTempPosition[0];
|
| + int anchorY = mTempPosition[1];
|
| +
|
| + ViewUtils.getRelativeLayoutPosition(contentView, (View) getParent(), mTempPosition);
|
| + int parentY = mTempPosition[1];
|
| +
|
| + int anchorBottomRelativeToContent = anchorY + mAnchorView.getMeasuredHeight();
|
| + int desiredTopMargin = anchorBottomRelativeToContent - parentY;
|
| + if (layoutParams.topMargin != desiredTopMargin) {
|
| + layoutParams.topMargin = desiredTopMargin;
|
| + updateLayout = true;
|
| + }
|
| +
|
| + int contentLeft = contentView.getLeft();
|
| + int anchorLeftRelativeToContent = anchorX - contentLeft;
|
| + if (layoutParams.leftMargin != anchorLeftRelativeToContent) {
|
| + layoutParams.leftMargin = anchorLeftRelativeToContent;
|
| + updateLayout = true;
|
| + }
|
| +
|
| + getWindowDelegate().getWindowVisibleDisplayFrame(mTempRect);
|
| + int decorHeight = getWindowDelegate().getDecorViewHeight();
|
| + int availableViewportHeight = Math.min(mTempRect.height(), decorHeight);
|
| + int availableListHeight = availableViewportHeight - anchorBottomRelativeToContent;
|
| + int desiredHeight = Math.min(availableListHeight, getIdealHeight());
|
| + if (layoutParams.height != desiredHeight) {
|
| + layoutParams.height = desiredHeight;
|
| + updateLayout = true;
|
| + }
|
| +
|
| + int desiredWidth = getDesiredWidth();
|
| + if (layoutParams.width != desiredWidth) {
|
| + layoutParams.width = desiredWidth;
|
| + updateLayout = true;
|
| + }
|
| +
|
| + if (updateLayout) requestLayout();
|
| + }
|
| +
|
| + private int getIdealHeight() {
|
| + int idealListSize = mBackgroundVerticalPadding;
|
| + for (int i = 0; i < mSuggestionItems.size(); i++) {
|
| + OmniboxResultItem item = mSuggestionItems.get(i);
|
| + if (!TextUtils.isEmpty(item.getSuggestion().getAnswerContents())) {
|
| + idealListSize += mSuggestionAnswerHeight;
|
| + } else {
|
| + idealListSize += mSuggestionHeight;
|
| + }
|
| + }
|
| + return idealListSize;
|
| + }
|
| +
|
| + private int getDesiredWidth() {
|
| + return mAnchorView.getWidth();
|
| + }
|
| +
|
| + @Override
|
| + public void onWindowFocusChanged(boolean hasWindowFocus) {
|
| + super.onWindowFocusChanged(hasWindowFocus);
|
| + if (!hasWindowFocus && !mSuggestionModalShown) hideSuggestions();
|
| + }
|
| +
|
| + @Override
|
| + protected void layoutChildren() {
|
| + super.layoutChildren();
|
| + // In ICS, the selected view is not marked as selected despite calling setSelection(0),
|
| + // so we bootstrap this after the children have been laid out.
|
| + if (!isInTouchMode() && getSelectedView() != null) {
|
| + getSelectedView().setSelected(true);
|
| + }
|
| + }
|
| + }
|
| +
|
| + public LocationBarLayout(Context context, AttributeSet attrs) {
|
| + super(context, attrs);
|
| +
|
| + LayoutInflater.from(context).inflate(R.layout.location_bar, this, true);
|
| + mNavigationButton = (ImageView) findViewById(R.id.navigation_button);
|
| + assert mNavigationButton != null : "Missing navigation type view.";
|
| + mNavigationButtonType = DeviceFormFactor.isTablet(context)
|
| + ? NavigationButtonType.PAGE : NavigationButtonType.EMPTY;
|
| +
|
| + mSecurityButton = (ImageButton) findViewById(R.id.security_button);
|
| + mSecurityIconType = ConnectionSecurityHelperSecurityLevel.NONE;
|
| +
|
| + mDeleteButton = (TintedImageButton) findViewById(R.id.delete_button);
|
| +
|
| + mUrlBar = (UrlBar) findViewById(R.id.url_bar);
|
| + // The HTC Sense IME will attempt to autocomplete words in the Omnibox when Prediction is
|
| + // enabled. We want to disable this feature and rely on the Omnibox's implementation.
|
| + // Their IME does not respect ~TYPE_TEXT_FLAG_AUTO_COMPLETE nor any of the other InputType
|
| + // options I tried, but setting the filter variation prevents it. Sadly, it also removes
|
| + // the .com button, but the prediction was buggy as it would autocomplete words even when
|
| + // typing at the beginning of the omnibox text when other content was present (messing up
|
| + // what was previously there). See bug: http://b/issue?id=6200071
|
| + String defaultIme = Settings.Secure.getString(getContext().getContentResolver(),
|
| + Settings.Secure.DEFAULT_INPUT_METHOD);
|
| + if (defaultIme != null && defaultIme.contains("com.htc.android.htcime")) {
|
| + mUrlBar.setInputType(mUrlBar.getInputType() | InputType.TYPE_TEXT_VARIATION_FILTER);
|
| + }
|
| + mUrlBar.setDelegate(this);
|
| +
|
| + mUrlContainer = (UrlContainer) findViewById(R.id.url_container);
|
| +
|
| + mSuggestionItems = new ArrayList<OmniboxResultItem>();
|
| + mSuggestionListAdapter = new OmniboxResultsAdapter(getContext(), this, mSuggestionItems);
|
| +
|
| + mMicButton = (TintedImageButton) findViewById(R.id.mic_button);
|
| + }
|
| +
|
| + @Override
|
| + protected void onFinishInflate() {
|
| + super.onFinishInflate();
|
| +
|
| + mUrlBar.setCursorVisible(false);
|
| + mNavigationButton.setVisibility(VISIBLE);
|
| + mSecurityButton.setVisibility(INVISIBLE);
|
| +
|
| + setLayoutTransition(null);
|
| +
|
| + AnimatorListenerAdapter iconChangeAnimatorListener = new AnimatorListenerAdapter() {
|
| + @Override
|
| + public void onAnimationEnd(Animator animation) {
|
| + if (animation == mSecurityButtonShowAnimator) {
|
| + mNavigationButton.setVisibility(INVISIBLE);
|
| + } else if (animation == mNavigationIconShowAnimator) {
|
| + mSecurityButton.setVisibility(INVISIBLE);
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public void onAnimationStart(Animator animation) {
|
| + if (animation == mSecurityButtonShowAnimator) {
|
| + mSecurityButton.setVisibility(VISIBLE);
|
| + } else if (animation == mNavigationIconShowAnimator) {
|
| + mNavigationButton.setVisibility(VISIBLE);
|
| + }
|
| + }
|
| + };
|
| +
|
| + mSecurityButtonShowAnimator = new AnimatorSet();
|
| + mSecurityButtonShowAnimator.playTogether(
|
| + ObjectAnimator.ofFloat(mNavigationButton, ALPHA, 0),
|
| + ObjectAnimator.ofFloat(mSecurityButton, ALPHA, 1));
|
| + mSecurityButtonShowAnimator.setDuration(URL_FOCUS_CHANGE_ANIMATION_DURATION_MS);
|
| + mSecurityButtonShowAnimator.addListener(iconChangeAnimatorListener);
|
| +
|
| + mNavigationIconShowAnimator = new AnimatorSet();
|
| + mNavigationIconShowAnimator.playTogether(
|
| + ObjectAnimator.ofFloat(mNavigationButton, ALPHA, 1),
|
| + ObjectAnimator.ofFloat(mSecurityButton, ALPHA, 0));
|
| + mNavigationIconShowAnimator.setDuration(URL_FOCUS_CHANGE_ANIMATION_DURATION_MS);
|
| + mNavigationIconShowAnimator.addListener(iconChangeAnimatorListener);
|
| +
|
| + mUrlBar.setOnKeyListener(new UrlBarKeyListener());
|
| +
|
| + mTextWatcher = new UrlBarTextWatcher();
|
| + mUrlBar.setLocationBarTextWatcher(mTextWatcher);
|
| +
|
| + // mLocationBar's direction is tied to this UrlBar's text direction. Icons inside the
|
| + // location bar, e.g. lock, refresh, X, should be reversed if UrlBar's text is RTL.
|
| + mUrlBar.setUrlDirectionListener(new UrlBar.UrlDirectionListener() {
|
| + @Override
|
| + public void onUrlDirectionChanged(int layoutDirection) {
|
| + ApiCompatibilityUtils.setLayoutDirection(LocationBarLayout.this, layoutDirection);
|
| + updateSuggestionsLayoutDirection(layoutDirection);
|
| + }
|
| + });
|
| +
|
| + mUrlBar.setSelectAllOnFocus(true);
|
| + }
|
| +
|
| + @Override
|
| + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
| + updateLayoutParams();
|
| + super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
| + }
|
| +
|
| + private void updateSuggestionsLayoutDirection(int layoutDirection) {
|
| + if (mSuggestionList != null && mSuggestionList.isShown()) {
|
| + ListView listView = mSuggestionList;
|
| + ApiCompatibilityUtils.setLayoutDirection(listView, layoutDirection);
|
| + for (int i = 0; i < listView.getChildCount(); i++) {
|
| + ApiCompatibilityUtils.setLayoutDirection(listView.getChildAt(i),
|
| + layoutDirection);
|
| + }
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public void initializeControls(WindowDelegate windowDelegate,
|
| + ActionBarDelegate actionBarDelegate, WindowAndroid windowAndroid) {
|
| + mWindowDelegate = windowDelegate;
|
| +
|
| + mContextualMenuBar = new ContextualMenuBar(getContext(), actionBarDelegate);
|
| + mContextualMenuBar.setCustomSelectionActionModeCallback(
|
| + new CustomSelectionActionModeCallback() {
|
| + @Override
|
| + public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
| + super.onPrepareActionMode(mode, menu);
|
| + mode.getMenuInflater().inflate(R.menu.textselectionmenu, menu);
|
| + return true;
|
| + }
|
| +
|
| + @Override
|
| + public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
| + if (item.getItemId() == R.id.copy_url) {
|
| + ClipboardManager clipboard =
|
| + (ClipboardManager) getContext().getSystemService(
|
| + Context.CLIPBOARD_SERVICE);
|
| + ClipData clip = ClipData.newPlainText("url", mOriginalUrl);
|
| + clipboard.setPrimaryClip(clip);
|
| + mode.finish();
|
| + return true;
|
| + } else {
|
| + return super.onActionItemClicked(mode, item);
|
| + }
|
| + }
|
| + });
|
| +
|
| + mWindowAndroid = windowAndroid;
|
| + mMicButton.setOnClickListener(this);
|
| + }
|
| +
|
| + /**
|
| + * @return The WindowDelegate for the LocationBar. This should be used for all Window related
|
| + * state queries.
|
| + */
|
| + protected WindowDelegate getWindowDelegate() {
|
| + return mWindowDelegate;
|
| + }
|
| +
|
| + /**
|
| + * Handles native dependent initialization for this class.
|
| + */
|
| + @Override
|
| + public void onNativeLibraryReady() {
|
| + mNativeInitialized = true;
|
| +
|
| + mSecurityButton.setOnClickListener(this);
|
| + mNavigationButton.setOnClickListener(this);
|
| + updateMicButtonState();
|
| + mDeleteButton.setOnClickListener(this);
|
| +
|
| + mAutocomplete = new AutocompleteController(this);
|
| +
|
| + mOmniboxPrerender = new OmniboxPrerender();
|
| +
|
| + for (Runnable deferredRunnable : mDeferredNativeRunnables) {
|
| + post(deferredRunnable);
|
| + }
|
| + mDeferredNativeRunnables.clear();
|
| +
|
| + mUrlBar.onOmniboxFullyFunctional();
|
| +
|
| + updateCustomSelectionActionModeCallback();
|
| + updateVisualsForState();
|
| + }
|
| +
|
| + /**
|
| + * @return Whether or not to animate icon changes.
|
| + */
|
| + protected boolean shouldAnimateIconChanges() {
|
| + return mUrlHasFocus;
|
| + }
|
| +
|
| + /**
|
| + * Sets the autocomplete controller for the location bar.
|
| + *
|
| + * @param controller The controller that will handle autocomplete/omnibox suggestions.
|
| + * @note Only used for testing.
|
| + */
|
| + @VisibleForTesting
|
| + public void setAutocompleteController(AutocompleteController controller) {
|
| + mAutocomplete = controller;
|
| + }
|
| +
|
| + /**
|
| + * Updates the profile used for generating autocomplete suggestions.
|
| + * @param profile The profile to be used.
|
| + */
|
| + @Override
|
| + public void setAutocompleteProfile(Profile profile) {
|
| + // This will only be called once at least one tab exists, and the tab model is told to
|
| + // update its state. During Chrome initialization the tab model update happens after the
|
| + // call to onNativeLibraryReady, so this assert will not fire.
|
| + assert mNativeInitialized :
|
| + "Setting Autocomplete Profile before native side initialized";
|
| + mAutocomplete.setProfile(profile);
|
| + mOmniboxPrerender.initializeForProfile(profile);
|
| + }
|
| +
|
| + private void changeLocationBarIcon(boolean showSecurityButton) {
|
| + if (mLocationBarIconActiveAnimator != null && mLocationBarIconActiveAnimator.isRunning()) {
|
| + mLocationBarIconActiveAnimator.cancel();
|
| + }
|
| + View viewToBeShown = showSecurityButton ? mSecurityButton : mNavigationButton;
|
| + if (viewToBeShown.getVisibility() == VISIBLE && viewToBeShown.getAlpha() == 1) {
|
| + return;
|
| + }
|
| + if (showSecurityButton) {
|
| + mLocationBarIconActiveAnimator = mSecurityButtonShowAnimator;
|
| + } else {
|
| + mLocationBarIconActiveAnimator = mNavigationIconShowAnimator;
|
| + }
|
| + if (shouldAnimateIconChanges()) {
|
| + mLocationBarIconActiveAnimator.setDuration(URL_FOCUS_CHANGE_ANIMATION_DURATION_MS);
|
| + } else {
|
| + mLocationBarIconActiveAnimator.setDuration(0);
|
| + }
|
| + mLocationBarIconActiveAnimator.start();
|
| + }
|
| +
|
| + @Override
|
| + public void onUrlPreFocusChanged(boolean gainFocus) {
|
| + if (mToolbarDataProvider == null || mToolbarDataProvider.getTab() == null) return;
|
| +
|
| + if (!mQueryInTheOmnibox
|
| + && FeatureUtilities.isDocumentMode(getContext())
|
| + && !TextUtils.isEmpty(mUrlBar.getText())) {
|
| + mUrlBar.setUrl(mToolbarDataProvider.getTab().getUrl(), null);
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public void setUrlBarFocus(boolean shouldBeFocused) {
|
| + if (shouldBeFocused) {
|
| + mUrlBar.requestFocus();
|
| + } else {
|
| + mUrlBar.clearFocus();
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public long getFirstUrlBarFocusTime() {
|
| + return mUrlBar.getFirstFocusTime();
|
| + }
|
| +
|
| + /**
|
| + * Triggered when the URL input field has gained or lost focus.
|
| + * @param hasFocus Whether the URL field has gained focus.
|
| + */
|
| + public void onUrlFocusChange(boolean hasFocus) {
|
| + mUrlHasFocus = hasFocus;
|
| + updateFocusSource(hasFocus);
|
| + updateDeleteButtonVisibility();
|
| + Tab currentTab = getCurrentTab();
|
| + if (hasFocus) {
|
| + mUrlBar.deEmphasizeUrl();
|
| + } else {
|
| + hideSuggestions();
|
| +
|
| + // Focus change caused by a close-tab may result in an invalid current tab.
|
| + if (currentTab != null) {
|
| + setUrlToPageUrl();
|
| + emphasizeUrl();
|
| + }
|
| + }
|
| +
|
| + if (getToolbarDataProvider().isUsingBrandColor()) {
|
| + updateVisualsForState();
|
| + if (mUrlHasFocus) mUrlBar.selectAll();
|
| + }
|
| +
|
| + if (mUrlFocusChangeListener != null) mUrlFocusChangeListener.onUrlFocusChange(hasFocus);
|
| + changeLocationBarIcon(
|
| + (!DeviceFormFactor.isTablet(getContext()) || !hasFocus) && isSecurityButtonShown());
|
| + mUrlBar.setCursorVisible(hasFocus);
|
| + if (mQueryInTheOmnibox) mUrlBar.setSelection(mUrlBar.getSelectionEnd());
|
| +
|
| + updateOmniboxResultsContainer();
|
| + if (hasFocus) updateOmniboxResultsContainerBackground(true);
|
| +
|
| + if (hasFocus && currentTab != null && !currentTab.isIncognito()) {
|
| + if (mNativeInitialized
|
| + && TemplateUrlService.getInstance().isDefaultSearchEngineGoogle()) {
|
| + GeolocationHeader.primeLocationForGeoHeader(getContext());
|
| + } else {
|
| + mDeferredNativeRunnables.add(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + if (TemplateUrlService.getInstance().isDefaultSearchEngineGoogle()) {
|
| + GeolocationHeader.primeLocationForGeoHeader(getContext());
|
| + }
|
| + }
|
| + });
|
| + }
|
| + }
|
| +
|
| + if (mNativeInitialized) {
|
| + startZeroSuggest();
|
| + } else {
|
| + mDeferredNativeRunnables.add(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + if (TextUtils.isEmpty(mUrlBar.getQueryText())) {
|
| + startZeroSuggest();
|
| + }
|
| + }
|
| + });
|
| + }
|
| +
|
| + // Add and remove text watcher from the URL bar with focus, so that it's
|
| + // not called when we modify the displayed information on focus.
|
| + if (hasFocus) {
|
| + mUrlBar.addTextChangedListener(mTextWatcher);
|
| + } else {
|
| + mUrlBar.removeTextChangedListener(mTextWatcher);
|
| + }
|
| +
|
| + if (!hasFocus) {
|
| + mHasStartedNewOmniboxEditSession = false;
|
| + mNewOmniboxEditSessionTimestamp = -1;
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Make a zero suggest request if native is loaded, the URL bar has focus, and the
|
| + * current tab is not incognito.
|
| + */
|
| + private void startZeroSuggest() {
|
| + // Reset "edited" state in the omnibox if zero suggest is triggered -- new edits
|
| + // now count as a new session.
|
| + mHasStartedNewOmniboxEditSession = false;
|
| + mNewOmniboxEditSessionTimestamp = -1;
|
| + Tab currentTab = getCurrentTab();
|
| + if (mNativeInitialized
|
| + && mUrlHasFocus
|
| + && currentTab != null
|
| + && !currentTab.isIncognito()) {
|
| + mAutocomplete.startZeroSuggest(currentTab.getProfile(), mUrlBar.getQueryText(),
|
| + currentTab.getUrl(), mQueryInTheOmnibox, mUrlFocusedFromFakebox);
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public void setDefaultTextEditActionModeCallback(CustomSelectionActionModeCallback callback) {
|
| + mDefaultActionModeCallbackForTextEdit = callback;
|
| + }
|
| +
|
| + /**
|
| + * If query in the omnibox, sets UrlBar's ActionModeCallback to show copy url button. Else,
|
| + * it is set to the default one.
|
| + */
|
| + private void updateCustomSelectionActionModeCallback() {
|
| + if (mQueryInTheOmnibox) {
|
| + mUrlBar.setCustomSelectionActionModeCallback(
|
| + mContextualMenuBar.getCustomSelectionActionModeCallback());
|
| + } else {
|
| + mUrlBar.setCustomSelectionActionModeCallback(mDefaultActionModeCallbackForTextEdit);
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public void requestUrlFocusFromFakebox(String pastedText) {
|
| + mUrlFocusedFromFakebox = true;
|
| + mUrlBar.requestFocus();
|
| +
|
| + if (pastedText != null) {
|
| + // This must be happen after requestUrlFocus(), which changes the selection.
|
| + mUrlBar.setUrl(pastedText, null);
|
| + mUrlBar.setSelection(mUrlBar.getText().length());
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Sets the toolbar that owns this LocationBar.
|
| + */
|
| + @Override
|
| + public void setToolbarDataProvider(ToolbarDataProvider toolbarDataProvider) {
|
| + mToolbarDataProvider = toolbarDataProvider;
|
| +
|
| + mUrlBar.setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
| + @Override
|
| + public void onFocusChange(View v, final boolean hasFocus) {
|
| + onUrlFocusChange(hasFocus);
|
| + }
|
| + });
|
| + }
|
| +
|
| + @Override
|
| + public void setMenuButtonHelper(AppMenuButtonHelper helper) { }
|
| +
|
| + @Override
|
| + public View getMenuAnchor() {
|
| + return null;
|
| + }
|
| +
|
| + /**
|
| + * Sets the URL focus change listner that will be notified when the URL gains or loses focus.
|
| + * @param listener The listener to be registered.
|
| + */
|
| + @Override
|
| + public void setUrlFocusChangeListener(UrlFocusChangeListener listener) {
|
| + mUrlFocusChangeListener = listener;
|
| + }
|
| +
|
| + /**
|
| + * @return The toolbar data provider.
|
| + */
|
| + @VisibleForTesting
|
| + protected ToolbarDataProvider getToolbarDataProvider() {
|
| + return mToolbarDataProvider;
|
| + }
|
| +
|
| + private static NavigationButtonType suggestionTypeToNavigationButtonType(
|
| + OmniboxSuggestion.Type suggestionType) {
|
| + switch (suggestionType) {
|
| + case NAVSUGGEST:
|
| + case HISTORY_URL:
|
| + case URL_WHAT_YOU_TYPED:
|
| + case HISTORY_TITLE:
|
| + case HISTORY_BODY:
|
| + case HISTORY_KEYWORD:
|
| + return NavigationButtonType.PAGE;
|
| + case SEARCH_WHAT_YOU_TYPED:
|
| + case SEARCH_HISTORY:
|
| + case SEARCH_SUGGEST:
|
| + case SEARCH_SUGGEST_ENTITY:
|
| + case SEARCH_SUGGEST_TAIL:
|
| + case SEARCH_SUGGEST_PERSONALIZED:
|
| + case SEARCH_SUGGEST_PROFILE:
|
| + case VOICE_SUGGEST:
|
| + case SEARCH_OTHER_ENGINE:
|
| + case OPEN_HISTORY_PAGE:
|
| + return NavigationButtonType.MAGNIFIER;
|
| + default:
|
| + assert false;
|
| + return NavigationButtonType.MAGNIFIER;
|
| + }
|
| + }
|
| +
|
| + // Updates the navigation button based on the URL string
|
| + private void updateNavigationButton() {
|
| + boolean isTablet = DeviceFormFactor.isTablet(getContext());
|
| + NavigationButtonType type = NavigationButtonType.EMPTY;
|
| + if (isTablet && !mSuggestionItems.isEmpty()) {
|
| + // If there are suggestions showing, show the icon for the default suggestion.
|
| + type = suggestionTypeToNavigationButtonType(
|
| + mSuggestionItems.get(0).getSuggestion().getType());
|
| + } else if (mQueryInTheOmnibox) {
|
| + type = NavigationButtonType.MAGNIFIER;
|
| + } else if (isTablet) {
|
| + type = NavigationButtonType.PAGE;
|
| + }
|
| +
|
| + if (type != mNavigationButtonType) setNavigationButtonType(type);
|
| + }
|
| +
|
| + /**
|
| + * @return Whether the query is shown in the omnibox instead of the url.
|
| + */
|
| + public boolean showingQueryInTheOmnibox() {
|
| + return mQueryInTheOmnibox;
|
| + }
|
| +
|
| + /**
|
| + * @return Whether original url is shown for preview page.
|
| + */
|
| + @Override
|
| + public boolean showingOriginalUrlForPreview() {
|
| + return mShowingOriginalUrlForPreview;
|
| + }
|
| +
|
| + private int getSecurityLevel() {
|
| + if (getCurrentTab() == null) return ConnectionSecurityHelperSecurityLevel.NONE;
|
| + return getCurrentTab().getSecurityLevel();
|
| + }
|
| +
|
| + /**
|
| + * Determines the icon that should be displayed for the current security level.
|
| + * @param securityLevel The security level for which the resource will be returned.
|
| + * @param usingLightTheme Whether light themed security assets should be used.
|
| + * @return The resource ID of the icon that should be displayed, 0 if no icon should show.
|
| + */
|
| + public static int getSecurityIconResource(int securityLevel, boolean usingLightTheme) {
|
| + switch (securityLevel) {
|
| + case ConnectionSecurityHelperSecurityLevel.NONE:
|
| + return 0;
|
| + case ConnectionSecurityHelperSecurityLevel.SECURITY_WARNING:
|
| + return R.drawable.omnibox_https_warning;
|
| + case ConnectionSecurityHelperSecurityLevel.SECURITY_ERROR:
|
| + return R.drawable.omnibox_https_invalid;
|
| + case ConnectionSecurityHelperSecurityLevel.SECURE:
|
| + case ConnectionSecurityHelperSecurityLevel.EV_SECURE:
|
| + return usingLightTheme
|
| + ? R.drawable.omnibox_https_valid_light : R.drawable.omnibox_https_valid;
|
| + default:
|
| + assert false;
|
| + }
|
| + return 0;
|
| + }
|
| +
|
| + /**
|
| + * Updates the security icon displayed in the LocationBar.
|
| + */
|
| + @Override
|
| + public void updateSecurityIcon(int securityLevel) {
|
| + if (showingOriginalUrlForPreview()) {
|
| + securityLevel = ConnectionSecurityHelperSecurityLevel.NONE;
|
| + }
|
| + if (mQueryInTheOmnibox) {
|
| + if (securityLevel == ConnectionSecurityHelperSecurityLevel.SECURE
|
| + || securityLevel == ConnectionSecurityHelperSecurityLevel.EV_SECURE) {
|
| + securityLevel = ConnectionSecurityHelperSecurityLevel.NONE;
|
| + } else if (securityLevel == ConnectionSecurityHelperSecurityLevel.SECURITY_WARNING
|
| + || securityLevel == ConnectionSecurityHelperSecurityLevel.SECURITY_ERROR) {
|
| + setUrlToPageUrl();
|
| + }
|
| + }
|
| +
|
| + // ImageView#setImageResource is no-op if given resource is the current one.
|
| + mSecurityButton.setImageResource(
|
| + getSecurityIconResource(securityLevel, !shouldEmphasizeHttpsScheme()));
|
| +
|
| + if (mSecurityIconType == securityLevel) return;
|
| + mSecurityIconType = securityLevel;
|
| +
|
| + if (securityLevel == ConnectionSecurityHelperSecurityLevel.NONE) {
|
| + updateSecurityButton(false);
|
| + } else {
|
| + updateSecurityButton(true);
|
| + // Since we emphasize the schema of the URL based on the security type, we need to
|
| + // refresh the emphasis.
|
| + mUrlBar.deEmphasizeUrl();
|
| + }
|
| + emphasizeUrl();
|
| + }
|
| +
|
| + private void emphasizeUrl() {
|
| + if (!mQueryInTheOmnibox) mUrlBar.emphasizeUrl();
|
| + }
|
| +
|
| + @Override
|
| + public boolean shouldEmphasizeHttpsScheme() {
|
| + int securityLevel = getSecurityLevel();
|
| + if (securityLevel == ConnectionSecurityHelperSecurityLevel.SECURITY_ERROR
|
| + || securityLevel == ConnectionSecurityHelperSecurityLevel.SECURITY_WARNING
|
| + || securityLevel == ConnectionSecurityHelperSecurityLevel.SECURITY_POLICY_WARNING) {
|
| + return true;
|
| + }
|
| + if (getToolbarDataProvider().isUsingBrandColor()) return false;
|
| + if (getToolbarDataProvider().isIncognito()) return false;
|
| + return true;
|
| + }
|
| +
|
| + /**
|
| + * Updates the display of the security button.
|
| + * @param enabled Whether the security button should be displayed.
|
| + */
|
| + private void updateSecurityButton(boolean enabled) {
|
| + changeLocationBarIcon(enabled
|
| + && (!DeviceFormFactor.isTablet(getContext()) || !mUrlHasFocus));
|
| + mSecurityButtonShown = enabled;
|
| + updateLocationBarIconContainerVisibility();
|
| + }
|
| +
|
| + /**
|
| + * @return Whether the security button is currently being displayed.
|
| + */
|
| + @VisibleForTesting
|
| + public boolean isSecurityButtonShown() {
|
| + return mSecurityButtonShown;
|
| + }
|
| +
|
| + /**
|
| + * Sets the type of the current navigation type and updates the UI to match it.
|
| + * @param buttonType The type of navigation button to be shown.
|
| + */
|
| + private void setNavigationButtonType(NavigationButtonType buttonType) {
|
| + switch (buttonType) {
|
| + case PAGE:
|
| + Drawable page = ApiCompatibilityUtils.getDrawable(
|
| + getResources(), R.drawable.ic_omnibox_page);
|
| + page.setColorFilter(mUseDarkColors
|
| + ? getResources().getColor(R.color.light_normal_color)
|
| + : Color.WHITE, PorterDuff.Mode.SRC_IN);
|
| + mNavigationButton.setImageDrawable(page);
|
| + break;
|
| + case MAGNIFIER:
|
| + mNavigationButton.setImageResource(R.drawable.ic_omnibox_magnifier);
|
| + break;
|
| + case EMPTY:
|
| + mNavigationButton.setImageResource(0);
|
| + break;
|
| + default:
|
| + assert false;
|
| + }
|
| +
|
| + if (mNavigationButton.getVisibility() != VISIBLE) {
|
| + mNavigationButton.setVisibility(VISIBLE);
|
| + }
|
| + mNavigationButtonType = buttonType;
|
| + updateLocationBarIconContainerVisibility();
|
| + }
|
| +
|
| + /**
|
| + * Update the visibility of the location bar icon container based on the state of the
|
| + * security and navigation icons.
|
| + */
|
| + protected void updateLocationBarIconContainerVisibility() {
|
| + boolean showContainer =
|
| + mSecurityButtonShown || mNavigationButtonType != NavigationButtonType.EMPTY;
|
| + findViewById(R.id.location_bar_icon).setVisibility(showContainer ? VISIBLE : GONE);
|
| + }
|
| +
|
| + private boolean isStoredArticle(String url) {
|
| + DomDistillerService domDistillerService =
|
| + DomDistillerServiceFactory.getForProfile(Profile.getLastUsedProfile());
|
| + String entryIdFromUrl = DomDistillerUrlUtils.getValueForKeyInUrl(url, "entry_id");
|
| + if (TextUtils.isEmpty(entryIdFromUrl)) return false;
|
| + return domDistillerService.hasEntry(entryIdFromUrl);
|
| + }
|
| +
|
| + /**
|
| + * Updates the layout params for the location bar start aligned views.
|
| + */
|
| + protected void updateLayoutParams() {
|
| + int startMargin = 0;
|
| + int urlContainerChildIndex = -1;
|
| + for (int i = 0; i < getChildCount(); i++) {
|
| + View childView = getChildAt(i);
|
| + if (childView.getVisibility() != GONE) {
|
| + LayoutParams childLayoutParams = (LayoutParams) childView.getLayoutParams();
|
| + if (ApiCompatibilityUtils.getMarginStart(childLayoutParams) != startMargin) {
|
| + ApiCompatibilityUtils.setMarginStart(childLayoutParams, startMargin);
|
| + childView.setLayoutParams(childLayoutParams);
|
| + }
|
| + if (childView == mUrlContainer) {
|
| + urlContainerChildIndex = i;
|
| + break;
|
| + }
|
| + int widthMeasureSpec;
|
| + int heightMeasureSpec;
|
| + if (childLayoutParams.width == LayoutParams.WRAP_CONTENT) {
|
| + widthMeasureSpec = MeasureSpec.makeMeasureSpec(
|
| + getMeasuredWidth(), MeasureSpec.AT_MOST);
|
| + } else if (childLayoutParams.width == LayoutParams.MATCH_PARENT) {
|
| + widthMeasureSpec = MeasureSpec.makeMeasureSpec(
|
| + getMeasuredWidth(), MeasureSpec.EXACTLY);
|
| + } else {
|
| + widthMeasureSpec = MeasureSpec.makeMeasureSpec(
|
| + childLayoutParams.width, MeasureSpec.EXACTLY);
|
| + }
|
| + if (childLayoutParams.height == LayoutParams.WRAP_CONTENT) {
|
| + heightMeasureSpec = MeasureSpec.makeMeasureSpec(
|
| + getMeasuredHeight(), MeasureSpec.AT_MOST);
|
| + } else if (childLayoutParams.height == LayoutParams.MATCH_PARENT) {
|
| + heightMeasureSpec = MeasureSpec.makeMeasureSpec(
|
| + getMeasuredHeight(), MeasureSpec.EXACTLY);
|
| + } else {
|
| + heightMeasureSpec = MeasureSpec.makeMeasureSpec(
|
| + childLayoutParams.height, MeasureSpec.EXACTLY);
|
| + }
|
| + childView.measure(widthMeasureSpec, heightMeasureSpec);
|
| + startMargin += childView.getMeasuredWidth();
|
| + }
|
| + }
|
| +
|
| + assert urlContainerChildIndex != -1;
|
| + int urlContainerMarginEnd = 0;
|
| + for (int i = urlContainerChildIndex + 1; i < getChildCount(); i++) {
|
| + View childView = getChildAt(i);
|
| + if (childView.getVisibility() != GONE) {
|
| + LayoutParams childLayoutParams = (LayoutParams) childView.getLayoutParams();
|
| + urlContainerMarginEnd = Math.max(urlContainerMarginEnd,
|
| + childLayoutParams.width
|
| + + ApiCompatibilityUtils.getMarginStart(childLayoutParams)
|
| + + ApiCompatibilityUtils.getMarginEnd(childLayoutParams));
|
| + }
|
| + }
|
| + LayoutParams urlLayoutParams = (LayoutParams) mUrlContainer.getLayoutParams();
|
| + if (ApiCompatibilityUtils.getMarginEnd(urlLayoutParams) != urlContainerMarginEnd) {
|
| + ApiCompatibilityUtils.setMarginEnd(urlLayoutParams, urlContainerMarginEnd);
|
| + mUrlContainer.setLayoutParams(urlLayoutParams);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * @return Whether the delete button should be shown.
|
| + */
|
| + protected boolean shouldShowDeleteButton() {
|
| + // Show the delete button at the endon the right when the bar has focus and has some text.
|
| + return mUrlBar.hasFocus() && !TextUtils.isEmpty(mUrlBar.getText());
|
| + }
|
| +
|
| + /**
|
| + * Updates the display of the delete URL content button.
|
| + */
|
| + protected void updateDeleteButtonVisibility() {
|
| + }
|
| +
|
| + /**
|
| + * @return The suggestion list popup containing the omnibox results (or
|
| + * null if it has not yet been created).
|
| + */
|
| + @VisibleForTesting
|
| + public OmniboxSuggestionsList getSuggestionList() {
|
| + return mSuggestionList;
|
| + }
|
| +
|
| + /**
|
| + * Initiates the mSuggestionListPopup. Done on demand to not slow down
|
| + * the initial inflation of the location bar.
|
| + */
|
| + private void initSuggestionList() {
|
| + // Only called from onSuggestionsReceived(), which is a callback from a listener set up by
|
| + // onNativeLibraryReady(), so this assert is safe.
|
| + assert mNativeInitialized : "Trying to initialize suggestions list before native init";
|
| + if (mSuggestionList != null) return;
|
| +
|
| + OnLayoutChangeListener suggestionListResizer = new OnLayoutChangeListener() {
|
| + @Override
|
| + public void onLayoutChange(View v, int left, int top, int right, int bottom,
|
| + int oldLeft, int oldTop, int oldRight, int oldBottom) {
|
| + // On ICS, this update does not take affect unless it is posted to the end of the
|
| + // current message queue.
|
| + post(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + if (mSuggestionList.isShown()) mSuggestionList.updateLayoutParams();
|
| + }
|
| + });
|
| + }
|
| + };
|
| + getRootView().findViewById(R.id.control_container)
|
| + .addOnLayoutChangeListener(suggestionListResizer);
|
| +
|
| + mSuggestionList = new OmniboxSuggestionsList(getContext());
|
| + mOmniboxResultsContainer.addView(mSuggestionList);
|
| + mSuggestionList.setAdapter(mSuggestionListAdapter);
|
| + mSuggestionList.setClipToPadding(false);
|
| + mSuggestionListAdapter.setSuggestionDelegate(new OmniboxSuggestionDelegate() {
|
| + @Override
|
| + public void onSelection(OmniboxSuggestion suggestion, int position) {
|
| + mSuggestionSelectionInProgress = true;
|
| + String suggestionMatchUrl = updateSuggestionUrlIfNeeded(suggestion, position);
|
| + loadUrlFromOmniboxMatch(suggestionMatchUrl, suggestion.getTransition(), position,
|
| + suggestion.getType());
|
| + hideSuggestions();
|
| + UiUtils.hideKeyboard(mUrlBar);
|
| + }
|
| +
|
| + @Override
|
| + public void onRefineSuggestion(OmniboxSuggestion suggestion) {
|
| + stopAutocomplete(false);
|
| + mUrlBar.setUrl(suggestion.getFillIntoEdit(), null);
|
| + mUrlBar.setSelection(mUrlBar.getText().length());
|
| + RecordUserAction.record("MobileOmniboxRefineSuggestion");
|
| + }
|
| +
|
| + @Override
|
| + public void onSetUrlToSuggestion(OmniboxSuggestion suggestion) {
|
| + if (mIgnoreOmniboxItemSelection) return;
|
| + setUrlBarText(null, null, suggestion.getFillIntoEdit());
|
| + mUrlBar.setSelection(mUrlBar.getText().length());
|
| + mIgnoreOmniboxItemSelection = true;
|
| + }
|
| +
|
| + @Override
|
| + public void onDeleteSuggestion(int position) {
|
| + if (mAutocomplete != null) mAutocomplete.deleteSuggestion(position);
|
| + }
|
| +
|
| + @Override
|
| + public void onGestureDown() {
|
| + stopAutocomplete(false);
|
| + }
|
| +
|
| + @Override
|
| + public void onShowModal() {
|
| + mSuggestionModalShown = true;
|
| + }
|
| +
|
| + @Override
|
| + public void onHideModal() {
|
| + mSuggestionModalShown = false;
|
| + }
|
| +
|
| + @Override
|
| + public void onTextWidthsUpdated(float requiredWidth, float matchContentsWidth) {
|
| + mSuggestionList.updateMaxTextWidths(requiredWidth, matchContentsWidth);
|
| + }
|
| +
|
| + @Override
|
| + public float getMaxRequiredWidth() {
|
| + return mSuggestionList.getMaxRequiredWidth();
|
| + }
|
| +
|
| + @Override
|
| + public float getMaxMatchContentsWidth() {
|
| + return mSuggestionList.getMaxMatchContentsWidth();
|
| + }
|
| + });
|
| + }
|
| +
|
| + /**
|
| + * @return The view that the suggestion popup should be anchored below.
|
| + */
|
| + protected View getSuggestionPopupAnchorView() {
|
| + return this;
|
| + }
|
| +
|
| + /**
|
| + * @return The background for the omnibox suggestions popup.
|
| + */
|
| + protected Drawable getSuggestionPopupBackground() {
|
| + if (mToolbarDataProvider.isIncognito()) {
|
| + return new ColorDrawable(OMNIBOX_INCOGNITO_RESULTS_BG_COLOR);
|
| + } else {
|
| + return new ColorDrawable(OMNIBOX_RESULTS_BG_COLOR);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Handles showing/hiding the suggestions list.
|
| + * @param visible Whether the suggestions list should be visible.
|
| + */
|
| + protected void setSuggestionsListVisibility(final boolean visible) {
|
| + mSuggestionsShown = visible;
|
| + if (mSuggestionList != null) {
|
| + final boolean isShowing = mSuggestionList.isShown();
|
| + if (visible && !isShowing) {
|
| + mSuggestionList.show();
|
| + } else if (!visible && isShowing) {
|
| + mSuggestionList.setVisibility(GONE);
|
| + }
|
| + }
|
| + updateOmniboxResultsContainer();
|
| + }
|
| +
|
| + /**
|
| + * Updates the URL we will navigate to from suggestion, if needed. This will update the search
|
| + * URL to be of the corpus type if query in the omnibox is displayed and update aqs= parameter
|
| + * on regular web search URLs.
|
| + *
|
| + * @param suggestion The chosen omnibox suggestion.
|
| + * @param selectedIndex The index of the chosen omnibox suggestion.
|
| + * @return The url to navigate to.
|
| + */
|
| + private String updateSuggestionUrlIfNeeded(OmniboxSuggestion suggestion, int selectedIndex) {
|
| + // Only called once we have suggestions, and don't have a listener though which we can
|
| + // receive suggestions until the native side is ready, so this is safe
|
| + assert mNativeInitialized
|
| + : "updateSuggestionUrlIfNeeded called before native initialization";
|
| +
|
| + String updatedUrl = null;
|
| + // Only replace URL queries for corpus search refinements, this does not work well
|
| + // for regular web searches.
|
| + // TODO(mariakhomenko): improve efficiency by just checking whether corpus exists.
|
| + if (mQueryInTheOmnibox && !suggestion.isUrlSuggestion()
|
| + && !TextUtils.isEmpty(mToolbarDataProvider.getCorpusChipText())) {
|
| + String query = suggestion.getFillIntoEdit();
|
| + Tab currentTab = getCurrentTab();
|
| + if (currentTab != null && !TextUtils.isEmpty(currentTab.getUrl())
|
| + && !TextUtils.isEmpty(query)) {
|
| + updatedUrl = TemplateUrlService.getInstance().replaceSearchTermsInUrl(
|
| + query, currentTab.getUrl());
|
| + }
|
| + } else if (suggestion.getType() != Type.VOICE_SUGGEST) {
|
| + // TODO(mariakhomenko): Ideally we want to update match destination URL with new aqs
|
| + // for query in the omnibox and voice suggestions, but it's currently difficult to do.
|
| + long elapsedTimeSinceInputChange = mNewOmniboxEditSessionTimestamp > 0
|
| + ? (SystemClock.elapsedRealtime() - mNewOmniboxEditSessionTimestamp) : -1;
|
| + updatedUrl = mAutocomplete.updateMatchDestinationUrlWithQueryFormulationTime(
|
| + selectedIndex, elapsedTimeSinceInputChange);
|
| + }
|
| +
|
| + return updatedUrl == null ? suggestion.getUrl() : updatedUrl;
|
| + }
|
| +
|
| + private void clearSuggestions(boolean notifyChange) {
|
| + mSuggestionItems.clear();
|
| + // Make sure to notify the adapter. If the ListView becomes out of sync
|
| + // with its adapter and it has not been notified, it will throw an
|
| + // exception when some UI events are propagated.
|
| + if (notifyChange) mSuggestionListAdapter.notifyDataSetChanged();
|
| + }
|
| +
|
| + /**
|
| + * Hides the omnibox suggestion popup.
|
| + *
|
| + * <p>
|
| + * Signals the autocomplete controller to stop generating omnibox suggestions.
|
| + *
|
| + * @see AutocompleteController#stop(boolean)
|
| + */
|
| + @Override
|
| + public void hideSuggestions() {
|
| + if (mAutocomplete == null) return;
|
| +
|
| + recordSuggestionsDismissed();
|
| +
|
| + stopAutocomplete(true);
|
| +
|
| + setSuggestionsListVisibility(false);
|
| + clearSuggestions(true);
|
| + updateNavigationButton();
|
| +
|
| + mSuggestionSelectionInProgress = false;
|
| + }
|
| +
|
| + /**
|
| + * Records a UMA action indicating that the user dismissed the suggestion list (e.g. pressed
|
| + * the back button or the 'x' button in the Omnibox). If there was an answer shown its type
|
| + * is recorded.
|
| + *
|
| + * The action is not recorded if mSelectionInProgress is true. This allows us to avoid
|
| + * recording the action in the case where the user is selecting a suggestion which is not
|
| + * considered a dismissal.
|
| + */
|
| + private void recordSuggestionsDismissed() {
|
| + if (mSuggestionSelectionInProgress || mSuggestionItems.size() == 0) return;
|
| +
|
| + int answerTypeShown = 0;
|
| + for (int i = 0; i < mSuggestionItems.size(); i++) {
|
| + OmniboxSuggestion suggestion = mSuggestionItems.get(i).getSuggestion();
|
| + if (suggestion.hasAnswer()) {
|
| + try {
|
| + answerTypeShown = Integer.parseInt(suggestion.getAnswerType());
|
| + } catch (NumberFormatException e) {
|
| + Log.e(getClass().getSimpleName(),
|
| + "Answer type in dismissed suggestions is not an int: "
|
| + + suggestion.getAnswerType());
|
| + }
|
| + break;
|
| + }
|
| + }
|
| + RecordHistogram.recordSparseSlowlyHistogram(
|
| + "Omnibox.SuggestionsDismissed.AnswerType", answerTypeShown);
|
| + }
|
| +
|
| + /**
|
| + * Signals the autocomplete controller to stop generating omnibox suggestions and cancels the
|
| + * queued task to start the autocomplete controller, if any.
|
| + *
|
| + * @param clear Whether to clear the most recent autocomplete results.
|
| + */
|
| + private void stopAutocomplete(boolean clear) {
|
| + if (mAutocomplete != null) mAutocomplete.stop(clear);
|
| + cancelPendingAutocompleteStart();
|
| + }
|
| +
|
| + /**
|
| + * Cancels the queued task to start the autocomplete controller, if any.
|
| + */
|
| + private void cancelPendingAutocompleteStart() {
|
| + if (mRequestSuggestions != null) {
|
| + // There is a request for suggestions either waiting for the native side
|
| + // to start, or on the message queue. Remove it from wherever it is.
|
| + if (!mDeferredNativeRunnables.remove(mRequestSuggestions)) {
|
| + removeCallbacks(mRequestSuggestions);
|
| + }
|
| + mRequestSuggestions = null;
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
|
| + // Don't restore the state of the location bar, it can lead to all kind of bad states with
|
| + // the popup.
|
| + // When we restore tabs, we focus the selected tab so the URL of the page shows.
|
| + }
|
| +
|
| + /**
|
| + * Performs a search query on the current {@link ChromeTab}. This calls
|
| + * {@link TemplateUrlService#getUrlForSearchQuery(String)} to get a url based on {@code query}
|
| + * and loads that url in the current {@link ChromeTab}.
|
| + * @param query The {@link String} that represents the text query that should be searched for.
|
| + */
|
| + @VisibleForTesting
|
| + public void performSearchQueryForTest(String query) {
|
| + if (TextUtils.isEmpty(query)) return;
|
| +
|
| + String queryUrl = TemplateUrlService.getInstance().getUrlForSearchQuery(query);
|
| +
|
| + if (!TextUtils.isEmpty(queryUrl)) {
|
| + loadUrl(queryUrl, PageTransition.GENERATED);
|
| + } else {
|
| + setSearchQuery(query);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Sets the query string in the omnibox (ensuring the URL bar has focus and triggering
|
| + * autocomplete for the specified query) as if the user typed it.
|
| + *
|
| + * @param query The query to be set in the omnibox.
|
| + */
|
| + public void setSearchQuery(final String query) {
|
| + if (TextUtils.isEmpty(query)) return;
|
| +
|
| + if (!mNativeInitialized) {
|
| + mDeferredNativeRunnables.add(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + setSearchQuery(query);
|
| + }
|
| + });
|
| + return;
|
| + }
|
| +
|
| + setUrlBarText(null, null, query);
|
| + mUrlBar.setSelection(0, mUrlBar.getText().length());
|
| + mUrlBar.requestFocus();
|
| + stopAutocomplete(false);
|
| + if (getCurrentTab() != null) {
|
| + mAutocomplete.start(
|
| + getCurrentTab().getProfile(), getCurrentTab().getUrl(), query, false);
|
| + }
|
| + post(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + UiUtils.showKeyboard(mUrlBar);
|
| + }
|
| + });
|
| + }
|
| +
|
| + /**
|
| + * Whether {@code v} is a location icon which can be clicked to show the
|
| + * origin info dialog.
|
| + */
|
| + private boolean isLocationIcon(View v) {
|
| + return v == mSecurityButton || v == mNavigationButton;
|
| + }
|
| +
|
| + @Override
|
| + public void onClick(View v) {
|
| + if (v == mDeleteButton) {
|
| + if (!TextUtils.isEmpty(mUrlBar.getQueryText())) {
|
| + setUrlBarText(null, null, "");
|
| + hideSuggestions();
|
| + }
|
| +
|
| + startZeroSuggest();
|
| + return;
|
| + } else if (!mUrlHasFocus && isLocationIcon(v)) {
|
| + Tab currentTab = getCurrentTab();
|
| + if (currentTab != null && currentTab.getWebContents() != null) {
|
| + WebsiteSettingsPopup.show(getContext(), currentTab.getProfile(),
|
| + currentTab.getWebContents());
|
| + }
|
| + } else if (v == mMicButton) {
|
| + RecordUserAction.record("MobileOmniboxVoiceSearch");
|
| + startVoiceRecognition();
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Whether we want to be showing inline autocomplete results. We don't want to show them as the
|
| + * user deletes input. Also if there is a composition (e.g. while using the Japanese IME),
|
| + * we must not autocomplete or we'll destroy the composition.
|
| + * @return Whether we want to be showing inline autocomplete results.
|
| + */
|
| + private boolean shouldAutocomplete() {
|
| + if (mLastUrlEditWasDelete) return false;
|
| + Editable text = mUrlBar.getText();
|
| +
|
| + return mUrlBar.isCursorAtEndOfTypedText()
|
| + && BaseInputConnection.getComposingSpanEnd(text)
|
| + == BaseInputConnection.getComposingSpanStart(text);
|
| + }
|
| +
|
| + @Override
|
| + public void onSuggestionsReceived(List<OmniboxSuggestion> newSuggestions,
|
| + String inlineAutocompleteText) {
|
| + // This is a callback from a listener that is set up by onNativeLibraryReady,
|
| + // so can only be called once the native side is set up.
|
| + assert mNativeInitialized : "Suggestions received before native side intialialized";
|
| +
|
| + if (getCurrentTab() == null) {
|
| + // If the current tab is not available, drop the suggestions and hide the autocomplete.
|
| + hideSuggestions();
|
| + return;
|
| + }
|
| +
|
| + String userText = mUrlBar.getTextWithoutAutocomplete();
|
| + mUrlTextAfterSuggestionsReceived = userText + inlineAutocompleteText;
|
| +
|
| + boolean itemsChanged = false;
|
| + boolean itemCountChanged = false;
|
| + // If the length of the incoming suggestions matches that of those currently being shown,
|
| + // replace them inline to allow transient entries to retain their proper highlighting.
|
| + if (mSuggestionItems.size() == newSuggestions.size()) {
|
| + for (int index = 0; index < newSuggestions.size(); index++) {
|
| + OmniboxResultItem suggestionItem = mSuggestionItems.get(index);
|
| + OmniboxSuggestion suggestion = suggestionItem.getSuggestion();
|
| + OmniboxSuggestion newSuggestion = newSuggestions.get(index);
|
| + // Determine whether the suggestions have changed. If not, save some time by not
|
| + // redrawing the suggestions UI.
|
| + if (suggestion.equals(newSuggestion)
|
| + && suggestion.getType() != OmniboxSuggestion.Type.SEARCH_SUGGEST_TAIL) {
|
| + if (suggestionItem.getMatchedQuery().equals(userText)) {
|
| + continue;
|
| + } else if (!suggestion.getDisplayText().startsWith(userText)
|
| + && !suggestion.getUrl().contains(userText)) {
|
| + continue;
|
| + }
|
| + }
|
| + mSuggestionItems.set(index, new OmniboxResultItem(newSuggestion, userText));
|
| + itemsChanged = true;
|
| + }
|
| + } else {
|
| + itemsChanged = true;
|
| + itemCountChanged = true;
|
| + clearSuggestions(false);
|
| + for (int i = 0; i < newSuggestions.size(); i++) {
|
| + mSuggestionItems.add(new OmniboxResultItem(newSuggestions.get(i), userText));
|
| + }
|
| + }
|
| +
|
| + if (mSuggestionItems.isEmpty()) {
|
| + if (mSuggestionsShown) hideSuggestions();
|
| + return;
|
| + }
|
| +
|
| + if (shouldAutocomplete()) {
|
| + mUrlBar.setAutocompleteText(userText, inlineAutocompleteText);
|
| + }
|
| +
|
| + // Show the suggestion list.
|
| + initSuggestionList(); // It may not have been initialized yet.
|
| + mSuggestionList.resetMaxTextWidths();
|
| +
|
| + if (itemsChanged) mSuggestionListAdapter.notifySuggestionsChanged();
|
| + if (mUrlBar.hasFocus()) {
|
| + setSuggestionsListVisibility(true);
|
| + if (itemCountChanged) {
|
| + mSuggestionList.updateLayoutParams();
|
| + }
|
| + }
|
| +
|
| + // Update the navigation button to show the default suggestion's icon.
|
| + updateNavigationButton();
|
| +
|
| + if (!CommandLine.getInstance().hasSwitch(ChromeSwitches.DISABLE_INSTANT)
|
| + && PrivacyPreferencesManager.getInstance(getContext()).shouldPrerender()) {
|
| + mOmniboxPrerender.prerenderMaybe(
|
| + userText,
|
| + getOriginalUrl(),
|
| + mAutocomplete.getCurrentNativeAutocompleteResult(),
|
| + getCurrentTab().getProfile(),
|
| + getCurrentTab());
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public void backKeyPressed() {
|
| + hideSuggestions();
|
| + UiUtils.hideKeyboard(mUrlBar);
|
| + // Revert the URL to match the current page.
|
| + setUrlToPageUrl();
|
| + // Focus the page.
|
| + Tab currentTab = getCurrentTab();
|
| + if (currentTab != null) currentTab.requestFocus();
|
| + }
|
| +
|
| + /**
|
| + * @return Returns the original url of the page.
|
| + */
|
| + public String getOriginalUrl() {
|
| + return mOriginalUrl;
|
| + }
|
| +
|
| + /**
|
| + * Given the URL display text, this will remove any path portion contained within.
|
| + * @param displayText The text to strip the path from.
|
| + * @return A pair where the first item is the text without any path content (if the path was
|
| + * successfully found), and the second item is the path content (or null if no path
|
| + * was found or parsing the path failed).
|
| + * @see ToolbarDataProvider#getText()
|
| + */
|
| + // TODO(tedchoc): Move this logic into the original display text calculation.
|
| + @VisibleForTesting
|
| + public static Pair<String, String> splitPathFromUrlDisplayText(String displayText) {
|
| + int pathSearchOffset = 0;
|
| + Uri uri = Uri.parse(displayText);
|
| + String scheme = uri.getScheme();
|
| + if (!TextUtils.isEmpty(scheme)) {
|
| + if (UNSUPPORTED_SCHEMES_TO_SPLIT.contains(scheme)) {
|
| + return Pair.create(displayText, null);
|
| + } else if (ACCEPTED_SCHEMES.contains(scheme)) {
|
| + for (pathSearchOffset = scheme.length();
|
| + pathSearchOffset < displayText.length();
|
| + pathSearchOffset++) {
|
| + char c = displayText.charAt(pathSearchOffset);
|
| + if (c != ':' && c != '/') break;
|
| + }
|
| + }
|
| + }
|
| + int pathOffset = -1;
|
| + if (pathSearchOffset < displayText.length()) {
|
| + pathOffset = displayText.indexOf('/', pathSearchOffset);
|
| + }
|
| + if (pathOffset != -1) {
|
| + String prePathText = displayText.substring(0, pathOffset);
|
| + // If the '/' is the last character and the beginning of the path, then just drop
|
| + // the path entirely.
|
| + String pathText = pathOffset == displayText.length() - 1
|
| + ? null : displayText.substring(pathOffset);
|
| + return Pair.create(prePathText, pathText);
|
| + }
|
| + return Pair.create(displayText, null);
|
| + }
|
| +
|
| + /**
|
| + * Sets the displayed URL to be the URL of the page currently showing.
|
| + *
|
| + * <p>The URL is converted to the most user friendly format (removing HTTP:// for example).
|
| + *
|
| + * <p>If the current tab is null, the URL text will be cleared.
|
| + */
|
| + @Override
|
| + public void setUrlToPageUrl() {
|
| + // If the URL is currently focused, do not replace the text they have entered with the URL.
|
| + // Once they stop editing the URL, the current tab's URL will automatically be filled in.
|
| + if (mUrlBar.hasFocus()) return;
|
| +
|
| + mQueryInTheOmnibox = false;
|
| +
|
| + if (getCurrentTab() == null) {
|
| + setUrlBarText(null, null, "");
|
| + return;
|
| + }
|
| +
|
| + // Profile may be null if switching to a tab that has not yet been initialized.
|
| + Profile profile = getCurrentTab().getProfile();
|
| + if (profile != null) mOmniboxPrerender.clear(profile);
|
| +
|
| + String url = getCurrentTab().getUrl().trim();
|
| + mOriginalUrl = url;
|
| +
|
| + if (NativePageFactory.isNativePageUrl(url, getCurrentTab().isIncognito())) {
|
| + // Don't show anything for Chrome URLs.
|
| + setUrlBarText("", null, null);
|
| + return;
|
| + }
|
| +
|
| + // Background view has similar case as snapshot.
|
| + BackgroundContentViewHelper backgroundViewHelper =
|
| + getCurrentTab().getBackgroundContentViewHelper();
|
| + boolean hasPendingBackgroundPage =
|
| + backgroundViewHelper != null && backgroundViewHelper.hasPendingBackgroundPage();
|
| + boolean isTransitioningFromPreviewPageToOriginal =
|
| + showingOriginalUrlForPreview() && !hasPendingBackgroundPage;
|
| + mShowingOriginalUrlForPreview = hasPendingBackgroundPage;
|
| +
|
| + boolean showingQuery = false;
|
| + String displayText = mToolbarDataProvider.getText();
|
| + int securityLevel = getSecurityLevel();
|
| + if (securityLevel != ConnectionSecurityHelperSecurityLevel.SECURITY_ERROR
|
| + && !TextUtils.isEmpty(displayText)
|
| + && mToolbarDataProvider.wouldReplaceURL()) {
|
| + url = displayText.trim();
|
| + showingQuery = true;
|
| + mQueryInTheOmnibox = true;
|
| + }
|
| + String path = null;
|
| + if (!showingQuery && FeatureUtilities.isDocumentMode(getContext())) {
|
| + Pair<String, String> urlText = splitPathFromUrlDisplayText(displayText);
|
| + displayText = urlText.first;
|
| + path = urlText.second;
|
| + }
|
| +
|
| + if (DomDistillerUrlUtils.isDistilledPage(url)) {
|
| + if (isStoredArticle(url)) {
|
| + DomDistillerService domDistillerService =
|
| + DomDistillerServiceFactory.getForProfile(profile);
|
| + String originalUrl = domDistillerService.getUrlForEntry(
|
| + DomDistillerUrlUtils.getValueForKeyInUrl(url, "entry_id"));
|
| + displayText =
|
| + DomDistillerTabUtils.getFormattedUrlFromOriginalDistillerUrl(originalUrl);
|
| + } else if (DomDistillerUrlUtils.getOriginalUrlFromDistillerUrl(url) != null) {
|
| + String originalUrl = DomDistillerUrlUtils.getOriginalUrlFromDistillerUrl(url);
|
| + displayText =
|
| + DomDistillerTabUtils.getFormattedUrlFromOriginalDistillerUrl(originalUrl);
|
| + }
|
| + }
|
| +
|
| + if (setUrlBarText(displayText, path, url) || isTransitioningFromPreviewPageToOriginal) {
|
| + mUrlBar.deEmphasizeUrl();
|
| + emphasizeUrl();
|
| + }
|
| + if (showingQuery) {
|
| + updateNavigationButton();
|
| + }
|
| + updateCustomSelectionActionModeCallback();
|
| + }
|
| +
|
| + /**
|
| + * Changes the text on the url bar
|
| + * @param displayText The text (URL or search terms) for user display.
|
| + * @param trailingText The trailing text (path portion of the URL) to be displayed separately.
|
| + * @param text The original text (URL or search terms) for copy/cut.
|
| + * @return Whether the URL was changed as a result of this call.
|
| + */
|
| + private boolean setUrlBarText(String displayText, String trailingText, String text) {
|
| + mIgnoreURLBarModification = true;
|
| + boolean urlChanged = mUrlContainer.setUrlText(displayText, trailingText, text);
|
| + mIgnoreURLBarModification = false;
|
| + return urlChanged;
|
| + }
|
| +
|
| + /**
|
| + * Sets whether modifications to the URL bar should be ignored.
|
| + */
|
| + @Override
|
| + public void setIgnoreURLBarModification(boolean value) {
|
| + mIgnoreURLBarModification = value;
|
| + }
|
| +
|
| + private void loadUrlFromOmniboxMatch(String url, int transition, int matchPosition,
|
| + OmniboxSuggestion.Type type) {
|
| + // loadUrl modifies AutocompleteController's state clearing the native
|
| + // AutocompleteResults needed by onSuggestionsSelected. Therefore,
|
| + // loadUrl should should be invoked last.
|
| + Tab currentTab = getCurrentTab();
|
| + String currentPageUrl = currentTab != null ? currentTab.getUrl() : "";
|
| + WebContents webContents = currentTab != null ? currentTab.getWebContents() : null;
|
| + long elapsedTimeSinceModified = mNewOmniboxEditSessionTimestamp > 0
|
| + ? (SystemClock.elapsedRealtime() - mNewOmniboxEditSessionTimestamp) : -1;
|
| + mAutocomplete.onSuggestionSelected(matchPosition, type, currentPageUrl,
|
| + mQueryInTheOmnibox, mUrlFocusedFromFakebox, elapsedTimeSinceModified,
|
| + webContents);
|
| + loadUrl(url, transition);
|
| + }
|
| +
|
| + private void loadUrl(String url, int transition) {
|
| + Tab currentTab = getCurrentTab();
|
| +
|
| + // The code of the rest of this class ensures that this can't be called until the native
|
| + // side is initialized
|
| + assert mNativeInitialized : "Loading URL before native side initialized";
|
| +
|
| + if (currentTab != null
|
| + && (currentTab.isNativePage() || NewTabPage.isNTPUrl(currentTab.getUrl()))) {
|
| + NewTabPageUma.recordOmniboxNavigation(url, transition);
|
| + // Passing in an empty string should not do anything unless the user is at the NTP.
|
| + // Since the NTP has no url, pressing enter while clicking on the URL bar should refresh
|
| + // the page as it does when you click and press enter on any other site.
|
| + if (url.isEmpty()) url = currentTab.getUrl();
|
| + }
|
| +
|
| + // Loads the |url| in the current ContentView and gives focus to the ContentView.
|
| + if (currentTab != null && !url.isEmpty()) {
|
| + LoadUrlParams loadUrlParams = new LoadUrlParams(url);
|
| + loadUrlParams.setVerbatimHeaders(
|
| + GeolocationHeader.getGeoHeader(getContext(), url, currentTab.isIncognito()));
|
| + loadUrlParams.setTransitionType(transition | PageTransition.FROM_ADDRESS_BAR);
|
| + currentTab.loadUrl(loadUrlParams);
|
| +
|
| + setUrlToPageUrl();
|
| + RecordUserAction.record("MobileOmniboxSearch");
|
| + RecordUserAction.record("MobileTabClobbered");
|
| + } else {
|
| + setUrlToPageUrl();
|
| + }
|
| +
|
| + if (currentTab != null) currentTab.requestFocus();
|
| +
|
| + // Prevent any upcoming omnibox suggestions from showing. We have to do this after we load
|
| + // the URL as this will hide the suggestions and trigger a cancel of the prerendered page.
|
| + stopAutocomplete(true);
|
| + }
|
| +
|
| + /**
|
| + * Update the location bar visuals based on a loading state change.
|
| + * @param updateUrl Whether to update the URL as a result of the this call.
|
| + */
|
| + @Override
|
| + public void updateLoadingState(boolean updateUrl) {
|
| + if (updateUrl) setUrlToPageUrl();
|
| + updateNavigationButton();
|
| + updateSecurityIcon(getSecurityLevel());
|
| + }
|
| +
|
| + /**
|
| + * @return The ChromeTab currently showing.
|
| + */
|
| + @Override
|
| + public ChromeTab getCurrentTab() {
|
| + if (mToolbarDataProvider == null) return null;
|
| + return ChromeTab.fromTab(mToolbarDataProvider.getTab());
|
| + }
|
| +
|
| + private ContentViewCore getContentViewCore() {
|
| + Tab currentTab = getCurrentTab();
|
| + return currentTab != null ? currentTab.getContentViewCore() : null;
|
| + }
|
| +
|
| + private void updateOmniboxResultsContainer() {
|
| + if (mSuggestionsShown || mUrlHasFocus) {
|
| + if (mOmniboxResultsContainer == null) {
|
| + ViewStub overlayStub =
|
| + (ViewStub) getRootView().findViewById(R.id.omnibox_results_container_stub);
|
| + mOmniboxResultsContainer = (ViewGroup) overlayStub.inflate();
|
| + mOmniboxResultsContainer.setBackgroundColor(CONTENT_OVERLAY_COLOR);
|
| + // Prevent touch events from propagating down to the chrome view.
|
| + mOmniboxResultsContainer.setOnTouchListener(new OnTouchListener() {
|
| + @Override
|
| + @SuppressLint("ClickableViewAccessibility")
|
| + public boolean onTouch(View v, MotionEvent event) {
|
| + int action = event.getActionMasked();
|
| + if (action == MotionEvent.ACTION_CANCEL
|
| + || action == MotionEvent.ACTION_UP) {
|
| + mUrlBar.clearFocus();
|
| + updateOmniboxResultsContainerBackground(false);
|
| + }
|
| + return true;
|
| + }
|
| + });
|
| + }
|
| + updateOmniboxResultsContainerVisibility(true);
|
| + } else if (mOmniboxResultsContainer != null) {
|
| + updateOmniboxResultsContainerBackground(false);
|
| + }
|
| + }
|
| +
|
| + private void updateOmniboxResultsContainerVisibility(boolean visible) {
|
| + boolean currentlyVisible = mOmniboxResultsContainer.getVisibility() == VISIBLE;
|
| + if (currentlyVisible == visible) {
|
| + // This early return is necessary. Otherwise, calling
|
| + // updateOmniboxResultsContainerVisibility(true) twice in a row will update
|
| + // mFocusedTabImportantForAccessibilityState incorrectly and cause
|
| + // mFocusedTabView to be stuck in IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS mode.
|
| + // http://crbug.com/445560
|
| + return;
|
| + }
|
| +
|
| + if (visible) {
|
| + mOmniboxResultsContainer.setVisibility(VISIBLE);
|
| +
|
| + if (getContentViewCore() != null) {
|
| + mFocusedTabAccessibilityManager =
|
| + getContentViewCore().getBrowserAccessibilityManager();
|
| + if (mFocusedTabAccessibilityManager != null) {
|
| + mFocusedTabAccessibilityManager.setVisible(false);
|
| + }
|
| + }
|
| +
|
| + if (getCurrentTab() != null && getCurrentTab().getView() != null) {
|
| + mFocusedTabView = getCurrentTab().getView();
|
| + mFocusedTabImportantForAccessibilityState =
|
| + mFocusedTabView.getImportantForAccessibility();
|
| + mFocusedTabView.setImportantForAccessibility(
|
| + IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
|
| + }
|
| + } else {
|
| + mOmniboxResultsContainer.setVisibility(INVISIBLE);
|
| +
|
| + if (mFocusedTabAccessibilityManager != null) {
|
| + mFocusedTabAccessibilityManager.setVisible(true);
|
| + mFocusedTabAccessibilityManager = null;
|
| + }
|
| +
|
| + if (mFocusedTabView != null) {
|
| + mFocusedTabView.setImportantForAccessibility(
|
| + mFocusedTabImportantForAccessibilityState);
|
| + mFocusedTabView = null;
|
| + }
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Set the background of the omnibox results container.
|
| + * @param visible Whether the background should be made visible.
|
| + */
|
| + private void updateOmniboxResultsContainerBackground(boolean visible) {
|
| + if (getToolbarDataProvider() == null) return;
|
| +
|
| + NewTabPage ntp = getToolbarDataProvider().getNewTabPageForCurrentTab();
|
| + boolean locationBarShownInNTP = ntp != null && ntp.isLocationBarShownInNTP();
|
| + if (visible) {
|
| + if (locationBarShownInNTP) {
|
| + mOmniboxResultsContainer.getBackground().setAlpha(0);
|
| + } else {
|
| + fadeInOmniboxResultsContainerBackground();
|
| + }
|
| + } else {
|
| + if (locationBarShownInNTP) {
|
| + updateOmniboxResultsContainerVisibility(false);
|
| + } else {
|
| + fadeOutOmniboxResultsContainerBackground();
|
| + }
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Trigger a fade in of the omnibox results background.
|
| + */
|
| + protected void fadeInOmniboxResultsContainerBackground() {
|
| + if (mFadeInOmniboxBackgroundAnimator == null) {
|
| + mFadeInOmniboxBackgroundAnimator = ObjectAnimator.ofInt(
|
| + getRootView().findViewById(R.id.omnibox_results_container).getBackground(),
|
| + "alpha", 0, 255);
|
| + mFadeInOmniboxBackgroundAnimator.setDuration(OMNIBOX_CONTAINER_BACKGROUND_FADE_MS);
|
| + mFadeInOmniboxBackgroundAnimator.setInterpolator(
|
| + BakedBezierInterpolator.FADE_IN_CURVE);
|
| + }
|
| + runOmniboxResultsFadeAnimation(mFadeInOmniboxBackgroundAnimator);
|
| + }
|
| +
|
| + private void fadeOutOmniboxResultsContainerBackground() {
|
| + if (mFadeOutOmniboxBackgroundAnimator == null) {
|
| + mFadeOutOmniboxBackgroundAnimator = ObjectAnimator.ofInt(
|
| + getRootView().findViewById(R.id.omnibox_results_container).getBackground(),
|
| + "alpha", 255, 0);
|
| + mFadeOutOmniboxBackgroundAnimator.setDuration(OMNIBOX_CONTAINER_BACKGROUND_FADE_MS);
|
| + mFadeOutOmniboxBackgroundAnimator.setInterpolator(
|
| + BakedBezierInterpolator.FADE_OUT_CURVE);
|
| + mFadeOutOmniboxBackgroundAnimator.addListener(new AnimatorListenerAdapter() {
|
| + private boolean mIsCancelled;
|
| +
|
| + @Override
|
| + public void onAnimationStart(Animator animation) {
|
| + mIsCancelled = false;
|
| + }
|
| +
|
| + @Override
|
| + public void onAnimationCancel(Animator animation) {
|
| + mIsCancelled = true;
|
| + }
|
| +
|
| + @Override
|
| + public void onAnimationEnd(Animator animation) {
|
| + if (mIsCancelled) return;
|
| + updateOmniboxResultsContainerVisibility(false);
|
| + }
|
| + });
|
| + }
|
| + runOmniboxResultsFadeAnimation(mFadeOutOmniboxBackgroundAnimator);
|
| + }
|
| +
|
| + private void runOmniboxResultsFadeAnimation(Animator fadeAnimation) {
|
| + if (mOmniboxBackgroundAnimator == fadeAnimation
|
| + && mOmniboxBackgroundAnimator.isRunning()) {
|
| + return;
|
| + } else if (mOmniboxBackgroundAnimator != null) {
|
| + mOmniboxBackgroundAnimator.cancel();
|
| + }
|
| + mOmniboxBackgroundAnimator = fadeAnimation;
|
| + mOmniboxBackgroundAnimator.start();
|
| + }
|
| +
|
| + /**
|
| + * @return Whether voice search is supported in the current browser configuration.
|
| + */
|
| + protected boolean isVoiceSearchEnabled() {
|
| + return mToolbarDataProvider != null && !mToolbarDataProvider.isIncognito()
|
| + && FeatureUtilities.isRecognitionIntentPresent(getContext(), true);
|
| + }
|
| +
|
| + @Override
|
| + protected void onWindowVisibilityChanged(int visibility) {
|
| + super.onWindowVisibilityChanged(visibility);
|
| + if (visibility == View.VISIBLE) updateMicButtonState();
|
| + }
|
| +
|
| + /**
|
| + * Call to notify the location bar that the state of the voice search microphone button may
|
| + * need to be updated.
|
| + */
|
| + @Override
|
| + public void updateMicButtonState() {
|
| + mMicButton.setVisibility(isVoiceSearchEnabled() ? View.VISIBLE : View.GONE);
|
| + }
|
| +
|
| + /**
|
| + * Call to force the UI to update the state of various buttons based on whether or not the
|
| + * current tab is incognito.
|
| + */
|
| + @Override
|
| + public void updateVisualsForState() {
|
| + if (updateUseDarkColors() || getToolbarDataProvider().isUsingBrandColor()) {
|
| + updateSecurityIcon(getSecurityLevel());
|
| + }
|
| + ColorStateList colorStateList = getResources().getColorStateList(mUseDarkColors
|
| + ? R.color.dark_mode_tint : R.color.light_mode_tint);
|
| + mMicButton.setTint(colorStateList);
|
| + mDeleteButton.setTint(colorStateList);
|
| +
|
| + setNavigationButtonType(mNavigationButtonType);
|
| + mUrlContainer.setUseDarkTextColors(mUseDarkColors);
|
| +
|
| + if (mSuggestionList != null) {
|
| + mSuggestionList.setBackground(getSuggestionPopupBackground());
|
| + }
|
| + mSuggestionListAdapter.setUseDarkColors(mUseDarkColors);
|
| + }
|
| +
|
| + /**
|
| + * Checks the current specs and updates {@link LocationBar#mUseDarkColors} if necessary.
|
| + * @return Whether {@link LocationBar#mUseDarkColors} has been updated.
|
| + */
|
| + private boolean updateUseDarkColors() {
|
| + Tab tab = getCurrentTab();
|
| + boolean brandColorNeedsLightText = false;
|
| + if (getToolbarDataProvider().isUsingBrandColor() && !mUrlHasFocus) {
|
| + int currentPrimaryColor = getToolbarDataProvider().getPrimaryColor();
|
| + brandColorNeedsLightText =
|
| + BrandColorUtils.shouldUseLightDrawablesForToolbar(currentPrimaryColor);
|
| + }
|
| +
|
| + boolean useDarkColors = tab == null || !(tab.isIncognito() || brandColorNeedsLightText);
|
| + boolean hasChanged = useDarkColors != mUseDarkColors;
|
| + mUseDarkColors = useDarkColors;
|
| +
|
| + return hasChanged;
|
| + }
|
| +
|
| + /**
|
| + * Triggers a voice recognition intent to allow the user to specify a search query.
|
| + */
|
| + @Override
|
| + public void startVoiceRecognition() {
|
| + Activity activity = mWindowAndroid.getActivity().get();
|
| + if (activity == null) return;
|
| +
|
| + Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
|
| + intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
|
| + RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
|
| + intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE,
|
| + activity.getComponentName().flattenToString());
|
| + intent.putExtra(RecognizerIntent.EXTRA_WEB_SEARCH_ONLY, true);
|
| +
|
| + if (mWindowAndroid.showCancelableIntent(intent, this, R.string.voice_search_error) < 0) {
|
| + // Requery whether or not the recognition intent can be handled.
|
| + FeatureUtilities.isRecognitionIntentPresent(activity, false);
|
| + updateMicButtonState();
|
| + }
|
| + }
|
| +
|
| + // WindowAndroid.IntentCallback implementation:
|
| + @Override
|
| + public void onIntentCompleted(WindowAndroid window, int resultCode,
|
| + ContentResolver contentResolver, Intent data) {
|
| + if (resultCode != Activity.RESULT_OK) return;
|
| + if (data.getExtras() == null) return;
|
| +
|
| + VoiceResult topResult = mAutocomplete.onVoiceResults(data.getExtras());
|
| + if (topResult == null) return;
|
| +
|
| + String topResultQuery = topResult.getMatch();
|
| + if (TextUtils.isEmpty(topResultQuery)) return;
|
| +
|
| + if (topResult.getConfidence() < VOICE_SEARCH_CONFIDENCE_NAVIGATE_THRESHOLD) {
|
| + setSearchQuery(topResultQuery);
|
| + return;
|
| + }
|
| +
|
| + String url = AutocompleteController.nativeQualifyPartialURLQuery(topResultQuery);
|
| + if (url == null) {
|
| + url = TemplateUrlService.getInstance().getUrlForVoiceSearchQuery(
|
| + topResultQuery);
|
| + }
|
| + loadUrl(url, PageTransition.TYPED);
|
| + }
|
| +
|
| + /**
|
| + * Tracks how the URL bar was focused (i.e. from the omnibox or the fakebox) and records a UMA
|
| + * stat for this. Should be called whenever the URL bar gains or loses focus.
|
| + * @param hasFocus Whether the URL bar now has focus.
|
| + */
|
| + private void updateFocusSource(boolean hasFocus) {
|
| + if (!hasFocus) {
|
| + mUrlFocusedFromFakebox = false;
|
| + mHasRecordedUrlFocusSource = false;
|
| + return;
|
| + }
|
| +
|
| + // Record UMA event for how the URL bar was focused.
|
| + assert !mHasRecordedUrlFocusSource;
|
| + if (mHasRecordedUrlFocusSource) return;
|
| +
|
| + Tab currentTab = getCurrentTab();
|
| + if (currentTab == null) return;
|
| +
|
| + String url = currentTab.getUrl();
|
| + if (mUrlFocusedFromFakebox) {
|
| + RecordUserAction.record("MobileFocusedFakeboxOnNtp");
|
| + } else {
|
| + if (currentTab.isNativePage() && NewTabPage.isNTPUrl(url)) {
|
| + RecordUserAction.record("MobileFocusedOmniboxOnNtp");
|
| + } else {
|
| + RecordUserAction.record("MobileFocusedOmniboxNotOnNtp");
|
| + }
|
| + }
|
| + mHasRecordedUrlFocusSource = true;
|
| + }
|
| +
|
| + @Override
|
| + public void onTabLoadingNTP(NewTabPage ntp) {
|
| + ntp.setFakeboxDelegate(this);
|
| + }
|
| +
|
| + @Override
|
| + public View getContainerView() {
|
| + return this;
|
| + }
|
| +}
|
|
|