Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(5)

Unified Diff: chrome/android/java_staging/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java

Issue 1141283003: Upstream oodles of Chrome for Android code into Chromium. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: final patch? Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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;
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698