// 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.Manifest;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
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.pm.PackageManager;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
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.support.annotation.IntDef;
import android.text.InputType;
import android.text.TextUtils;
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.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
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.R;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.NativePage;
import org.chromium.chrome.browser.WindowDelegate;
import org.chromium.chrome.browser.appmenu.AppMenuButtonHelper;
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.VoiceSuggestionProvider.VoiceResult;
import org.chromium.chrome.browser.omnibox.geo.GeolocationHeader;
import org.chromium.chrome.browser.omnibox.geo.GeolocationSnackbarController;
import org.chromium.chrome.browser.pageinfo.WebsiteSettingsPopup;
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.tab.Tab;
import org.chromium.chrome.browser.toolbar.ActionModeController;
import org.chromium.chrome.browser.toolbar.ActionModeController.ActionBarDelegate;
import org.chromium.chrome.browser.toolbar.ToolbarActionModeCallback;
import org.chromium.chrome.browser.toolbar.ToolbarDataProvider;
import org.chromium.chrome.browser.toolbar.ToolbarPhone;
import org.chromium.chrome.browser.util.ColorUtils;
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.chrome.browser.widget.animation.AnimatorProperties;
import org.chromium.chrome.browser.widget.animation.CancelAwareAnimatorListener;
import org.chromium.components.security_state.ConnectionSecurityLevel;
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.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
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;
// Delay showing the geolocation snackbar when the omnibox is focused until the keyboard is
// hopefully visible.
private static final int GEOLOCATION_SNACKBAR_SHOW_DELAY_MS = 750;
// 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 = 0xA6000000;
private static final int OMNIBOX_RESULTS_BG_COLOR = 0xFFF5F5F6;
private static final int OMNIBOX_INCOGNITO_RESULTS_BG_COLOR = 0xFF323232;
/**
* 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 TintedImageButton mSecurityButton;
protected TextView mVerboseStatusTextView;
protected TintedImageButton mDeleteButton;
protected TintedImageButton mMicButton;
protected UrlBar mUrlBar;
private ActionModeController mActionModeController = null;
private AutocompleteController mAutocomplete;
protected ToolbarDataProvider mToolbarDataProvider;
private UrlFocusChangeListener mUrlFocusChangeListener;
protected 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 mSecurityIconResource;
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;
private boolean mIgnoreOmniboxItemSelection = true;
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;
protected boolean mUrlFocusChangeInProgress;
private boolean mUrlFocusedFromFakebox;
private boolean mUrlFocusedWithoutAnimations;
private boolean mVoiceSearchEnabled;
// 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;
@LocationBarButtonType private int mLocationBarButtonType;
private AnimatorSet mLocationBarIconActiveAnimator;
private AnimatorSet mSecurityButtonShowAnimator;
private AnimatorSet mNavigationIconShowAnimator;
private OmniboxPrerender mOmniboxPrerender;
private boolean mSuggestionModalShown;
private boolean mUseDarkColors;
private boolean mIsEmphasizingHttpsScheme;
// 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 ToolbarActionModeCallback mDefaultActionModeCallbackForTextEdit;
private Runnable mShowSuggestions;
/**
* 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 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);
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;
} else if (keyCode == KeyEvent.KEYCODE_BACK) {
if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
// Tell the framework to start tracking this event.
getKeyDispatcherState().startTracking(event, this);
return true;
} else if (event.getAction() == KeyEvent.ACTION_UP) {
getKeyDispatcherState().handleUpEvent(event);
if (event.isTracking() && !event.isCanceled()) {
backKeyPressed();
return true;
}
}
} else if (keyCode == KeyEvent.KEYCODE_ESCAPE) {
if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
revertChanges();
return true;
}
}
return false;
}
private void findMatchAndLoadUrl(String urlText) {
int suggestionMatchPosition;
OmniboxSuggestion suggestionMatch;
boolean skipOutOfBoundsCheck = false;
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;
// Classify matches don't propagate to java, so skip the OOB check.
skipOutOfBoundsCheck = true;
// If urlText couldn't be classified, bail.
if (suggestionMatch == null) return;
}
String suggestionMatchUrl = updateSuggestionUrlIfNeeded(suggestionMatch,
suggestionMatchPosition, skipOutOfBoundsCheck);
// 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. There is one special case where the suggestion text was pasted,
// where we want the transition type to be LINK.
int transition = suggestionMatch.getType() == OmniboxSuggestionType.URL_WHAT_YOU_TYPED
&& mUrlBar.isPastedText() ? PageTransition.LINK
: suggestionMatch.getTransition();
loadUrlFromOmniboxMatch(suggestionMatchUrl, transition, suggestionMatchPosition,
suggestionMatch.getType());
}
}
/**
* Specifies the types of buttons shown to signify different types of navigation elements.
*/
protected enum NavigationButtonType {
PAGE,
MAGNIFIER,
EMPTY,
}
/** Specifies which button should be shown in location bar, if any. */
@Retention(RetentionPolicy.SOURCE)
@IntDef({BUTTON_TYPE_NONE, BUTTON_TYPE_SECURITY_ICON, BUTTON_TYPE_NAVIGATION_ICON})
public @interface LocationBarButtonType {}
/** No button should be shown. */
public static final int BUTTON_TYPE_NONE = 0;
/** Security button should be shown (includes offline icon). */
public static final int BUTTON_TYPE_SECURITY_ICON = 1;
/** Navigation button should be shown. */
public static final int BUTTON_TYPE_NAVIGATION_ICON = 2;
/**
* @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);
}
}
/**
* 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())) {
int num = SuggestionView.parseNumAnswerLines(
item.getSuggestion().getAnswer().getSecondLine().getTextFields());
if (num > 1) {
idealListSize += mSuggestionAnswerHeight * 2;
} else {
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);
}
}
private void updateSuggestionsLayoutDirection(int layoutDirection) {
if (!isShown()) return;
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);
if (!(childView instanceof SuggestionView)) continue;
ApiCompatibilityUtils.setLayoutDirection(childView, layoutDirection);
}
}
}
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 = (TintedImageButton) findViewById(R.id.security_button);
mSecurityIconResource = 0;
mVerboseStatusTextView = (TextView) findViewById(R.id.location_bar_verbose_status);
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);
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);
mLocationBarButtonType = getLocationBarButtonToShow();
mNavigationButton.setVisibility(
mLocationBarButtonType == BUTTON_TYPE_NAVIGATION_ICON ? VISIBLE : INVISIBLE);
mSecurityButton.setVisibility(
mLocationBarButtonType == BUTTON_TYPE_SECURITY_ICON ? VISIBLE : 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());
// 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);
if (mSuggestionList != null) {
mSuggestionList.updateSuggestionsLayoutDirection(layoutDirection);
}
}
});
mUrlBar.setSelectAllOnFocus(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
updateLayoutParams();
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
boolean retVal = super.dispatchKeyEvent(event);
if (retVal && mUrlHasFocus && mUrlFocusedWithoutAnimations
&& event.getAction() == KeyEvent.ACTION_DOWN && event.isPrintingKey()
&& event.hasNoModifiers()) {
handleUrlFocusAnimation(mUrlHasFocus);
}
return retVal;
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (mUrlHasFocus && mUrlFocusedWithoutAnimations
&& newConfig.keyboard != Configuration.KEYBOARD_QWERTY) {
// If we lose the hardware keyboard and the focus animations were not run, then the
// user has not typed any text, so we will just clear the focus instead.
setUrlBarFocus(false);
}
}
@Override
public void initializeControls(WindowDelegate windowDelegate,
ActionBarDelegate actionBarDelegate, WindowAndroid windowAndroid) {
mWindowDelegate = windowDelegate;
mActionModeController = new ActionModeController(getContext(), actionBarDelegate);
mActionModeController.setCustomSelectionActionModeCallback(
new ToolbarActionModeCallback() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
boolean retVal = super.onCreateActionMode(mode, menu);
mode.getMenuInflater().inflate(R.menu.textselectionmenu, menu);
return retVal;
}
@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;
// If the user focused the omnibox prior to the native libraries being initialized,
// autocomplete will not always be enabled, so we force it enabled in that case.
mUrlBar.setIgnoreTextChangesForAutocomplete(false);
}
/**
* @param focusable Whether the url bar should be focusable.
*/
public void setUrlBarFocusable(boolean focusable) {
if (mUrlBar == null) return;
mUrlBar.setFocusable(focusable);
mUrlBar.setFocusableInTouchMode(focusable);
}
/**
* @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);
mVerboseStatusTextView.setOnClickListener(this);
updateMicButtonState();
mDeleteButton.setOnClickListener(this);
mMicButton.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) {
if (mAutocomplete != null) stopAutocomplete(true);
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);
}
@LocationBarButtonType private int getLocationBarButtonToShow() {
boolean isOffline = getCurrentTab() != null && getCurrentTab().isOfflinePage();
boolean isTablet = DeviceFormFactor.isTablet(getContext());
if (mUrlHasFocus) {
return isTablet ? BUTTON_TYPE_NAVIGATION_ICON : BUTTON_TYPE_NONE;
}
return getSecurityIconResource(getSecurityLevel(), !isTablet, isOffline) != 0
? BUTTON_TYPE_SECURITY_ICON
: BUTTON_TYPE_NONE;
}
private void changeLocationBarIcon() {
if (mLocationBarIconActiveAnimator != null && mLocationBarIconActiveAnimator.isRunning()) {
mLocationBarIconActiveAnimator.cancel();
}
mLocationBarButtonType = getLocationBarButtonToShow();
View viewToBeShown = null;
switch (mLocationBarButtonType) {
case BUTTON_TYPE_SECURITY_ICON:
viewToBeShown = mSecurityButton;
mLocationBarIconActiveAnimator = mSecurityButtonShowAnimator;
break;
case BUTTON_TYPE_NAVIGATION_ICON:
viewToBeShown = mNavigationButton;
mLocationBarIconActiveAnimator = mNavigationIconShowAnimator;
break;
case BUTTON_TYPE_NONE:
default:
mLocationBarIconActiveAnimator = null;
return;
}
if (viewToBeShown.getVisibility() == VISIBLE && viewToBeShown.getAlpha() == 1) {
return;
}
if (shouldAnimateIconChanges()) {
mLocationBarIconActiveAnimator.setDuration(URL_FOCUS_CHANGE_ANIMATION_DURATION_MS);
} else {
mLocationBarIconActiveAnimator.setDuration(0);
}
mLocationBarIconActiveAnimator.start();
}
@Override
public void setUrlBarFocus(boolean shouldBeFocused) {
if (shouldBeFocused) {
mUrlBar.requestFocus();
} else {
mUrlBar.clearFocus();
}
}
@Override
public boolean isUrlBarFocused() {
return mUrlHasFocus;
}
@Override
public void selectAll() {
mUrlBar.selectAll();
}
@Override
public void revertChanges() {
if (!mUrlHasFocus) {
setUrlToPageUrl();
} else {
Tab tab = mToolbarDataProvider.getTab();
if (NativePageFactory.isNativePageUrl(tab.getUrl(), tab.isIncognito())) {
setUrlBarText("", null);
} else {
setUrlBarText(
mToolbarDataProvider.getText(), getCurrentTabUrl());
selectAll();
}
hideSuggestions();
UiUtils.hideKeyboard(mUrlBar);
}
}
@Override
public long getFirstUrlBarFocusTime() {
return mUrlBar.getFirstFocusTime();
}
/**
* @return Whether the URL focus change is taking place, e.g. a focus animation is running on
* a phone device.
*/
protected boolean isUrlFocusChangeInProgress() {
return mUrlFocusChangeInProgress;
}
/**
* @param inProgress Whether a URL focus change is taking place.
*/
protected void setUrlFocusChangeInProgress(boolean inProgress) {
mUrlFocusChangeInProgress = inProgress;
if (!inProgress) {
updateButtonVisibility();
}
}
/**
* 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;
updateButtonVisibility();
updateNavigationButton();
Tab currentTab = getCurrentTab();
if (hasFocus) {
if (mNativeInitialized) RecordUserAction.record("FocusLocation");
mUrlBar.deEmphasizeUrl();
} else {
mUrlFocusedFromFakebox = false;
mUrlFocusedWithoutAnimations = false;
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();
}
changeLocationBarIcon();
updateVerboseStatusVisibility();
updateLocationBarIconContainerVisibility();
mUrlBar.setCursorVisible(hasFocus);
if (!mUrlFocusedWithoutAnimations) handleUrlFocusAnimation(hasFocus);
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();
}
}
});
}
if (!hasFocus) {
mHasStartedNewOmniboxEditSession = false;
mNewOmniboxEditSessionTimestamp = -1;
}
if (hasFocus && currentTab != null) {
ChromeActivity activity = (ChromeActivity) mWindowAndroid.getActivity().get();
if (activity != null) {
GeolocationSnackbarController.maybeShowSnackbar(activity.getSnackbarManager(),
LocationBarLayout.this, currentTab.isIncognito(),
GEOLOCATION_SNACKBAR_SHOW_DELAY_MS);
}
}
}
/**
* Handle and run any necessary animations that are triggered off focusing the UrlBar.
* @param hasFocus Whether focus was gained.
*/
protected void handleUrlFocusAnimation(boolean hasFocus) {
if (hasFocus) mUrlFocusedWithoutAnimations = false;
if (mUrlFocusChangeListener != null) mUrlFocusChangeListener.onUrlFocusChange(hasFocus);
updateOmniboxResultsContainer();
if (hasFocus) updateOmniboxResultsContainerBackground(true);
}
/**
* 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(), mUrlFocusedFromFakebox);
}
}
@Override
public void onTextChangedForAutocomplete(final boolean textDeleted) {
cancelPendingAutocompleteStart();
updateButtonVisibility();
updateNavigationButton();
if (!mHasStartedNewOmniboxEditSession && mNativeInitialized) {
mAutocomplete.resetSession();
mHasStartedNewOmniboxEditSession = true;
mNewOmniboxEditSessionTimestamp = SystemClock.elapsedRealtime();
}
if (!isInTouchMode() && mSuggestionList != null) {
mSuggestionList.setSelection(0);
}
stopAutocomplete(false);
if (TextUtils.isEmpty(mUrlBar.getTextWithoutAutocomplete())) {
hideSuggestions();
startZeroSuggest();
} else {
assert mRequestSuggestions == null : "Multiple omnibox requests in flight.";
mRequestSuggestions = new Runnable() {
@Override
public void run() {
String textWithoutAutocomplete = mUrlBar.getTextWithoutAutocomplete();
boolean preventAutocomplete = textDeleted || !mUrlBar.shouldAutocomplete();
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 (textDeleted) mUrlBar.setSelection(mUrlBar.getSelectionStart());
}
@Override
public void setDefaultTextEditActionModeCallback(ToolbarActionModeCallback 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() {
mUrlBar.setCustomSelectionActionModeCallback(mDefaultActionModeCallbackForTextEdit);
}
@Override
public void requestUrlFocusFromFakebox(String pastedText) {
mUrlFocusedFromFakebox = true;
if (mUrlHasFocus && mUrlFocusedWithoutAnimations) {
handleUrlFocusAnimation(mUrlHasFocus);
} else {
setUrlBarFocus(true);
}
if (pastedText != null) {
// This must be happen after requestUrlFocus(), which changes the selection.
mUrlBar.setUrl(pastedText, null);
mUrlBar.setSelection(mUrlBar.getText().length());
}
}
@Override
public boolean isCurrentPage(NativePage nativePage) {
assert nativePage != null;
return nativePage == mToolbarDataProvider.getNewTabPageForCurrentTab();
}
@Override
public void showUrlBarCursorWithoutFocusAnimations() {
if (mUrlHasFocus || mUrlFocusedFromFakebox) return;
mUrlFocusedWithoutAnimations = true;
setUrlBarFocus(true);
}
/**
* 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 suggestion) {
if (suggestion.isUrlSuggestion()) {
return NavigationButtonType.PAGE;
} else {
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());
} else if (isTablet) {
type = NavigationButtonType.PAGE;
}
if (type != mNavigationButtonType) setNavigationButtonType(type);
}
private int getSecurityLevel() {
if (getCurrentTab() == null) return ConnectionSecurityLevel.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 isSmallDevice Whether the device form factor is small (like a phone) or large
* (like a tablet).
* @return The resource ID of the icon that should be displayed, 0 if no icon should show.
*/
public static int getSecurityIconResource(
int securityLevel, boolean isSmallDevice, boolean isOfflinePage) {
// Both conditions should be met, because isOfflinePage might take longer to be cleared.
if (securityLevel == ConnectionSecurityLevel.NONE && isOfflinePage) {
return R.drawable.offline_pin_round;
}
switch (securityLevel) {
case ConnectionSecurityLevel.NONE:
case ConnectionSecurityLevel.HTTP_SHOW_WARNING:
return isSmallDevice ? 0 : R.drawable.omnibox_info;
case ConnectionSecurityLevel.SECURITY_WARNING:
return R.drawable.omnibox_info;
case ConnectionSecurityLevel.DANGEROUS:
return R.drawable.omnibox_https_invalid;
case ConnectionSecurityLevel.SECURE:
case ConnectionSecurityLevel.EV_SECURE:
return R.drawable.omnibox_https_valid;
default:
assert false;
}
return 0;
}
/**
* @param securityLevel The security level for which the color will be returned.
* @param provider The {@link ToolbarDataProvider}.
* @param resources The Resources for the Context.
* @param isOmniboxOpaque Whether the omnibox is an opaque color.
* @return The {@link ColorStateList} to use to tint the security state icon.
*/
public static ColorStateList getColorStateList(int securityLevel, ToolbarDataProvider provider,
Resources resources, boolean isOmniboxOpaque) {
ColorStateList list = null;
int color = provider.getPrimaryColor();
boolean needLightIcon = ColorUtils.shouldUseLightForegroundOnBackground(color);
if (provider.isIncognito() || needLightIcon) {
// For a dark theme color, use light icons.
list = ApiCompatibilityUtils.getColorStateList(resources, R.color.light_mode_tint);
} else if (!ColorUtils.isUsingDefaultToolbarColor(resources, color) && !isOmniboxOpaque) {
// For theme colors which are not dark and are also not
// light enough to warrant an opaque URL bar, use dark
// icons.
list = ApiCompatibilityUtils.getColorStateList(resources, R.color.dark_mode_tint);
} else {
// For the default toolbar color, use a green or red icon.
if (securityLevel == ConnectionSecurityLevel.DANGEROUS) {
list = ApiCompatibilityUtils.getColorStateList(resources, R.color.google_red_700);
} else if (securityLevel == ConnectionSecurityLevel.SECURE
|| securityLevel == ConnectionSecurityLevel.EV_SECURE) {
list = ApiCompatibilityUtils.getColorStateList(resources, R.color.google_green_700);
} else {
list = ApiCompatibilityUtils.getColorStateList(resources, R.color.dark_mode_tint);
}
}
assert list != null : "Missing ColorStateList for Security Button.";
return list;
}
/**
* Updates the security icon displayed in the LocationBar.
*/
@Override
public void updateSecurityIcon(int securityLevel) {
boolean isSmallDevice = !DeviceFormFactor.isTablet(getContext());
boolean isOfflinePage = getCurrentTab() != null && getCurrentTab().isOfflinePage();
int id = getSecurityIconResource(securityLevel, isSmallDevice, isOfflinePage);
if (id == 0) {
mSecurityButton.setImageDrawable(null);
} else {
// ImageView#setImageResource is no-op if given resource is the current one.
mSecurityButton.setImageResource(id);
mSecurityButton.setTint(getColorStateList(securityLevel, getToolbarDataProvider(),
getResources(), ColorUtils.shouldUseOpaqueTextboxBackground(
getToolbarDataProvider().getPrimaryColor())));
}
updateVerboseStatusVisibility();
boolean shouldEmphasizeHttpsScheme = shouldEmphasizeHttpsScheme();
if (mSecurityIconResource == id
&& mIsEmphasizingHttpsScheme == shouldEmphasizeHttpsScheme) {
return;
}
mSecurityIconResource = id;
changeLocationBarIcon();
updateLocationBarIconContainerVisibility();
// Since we emphasize the scheme of the URL based on the security type, we need to
// refresh the emphasis.
mUrlBar.deEmphasizeUrl();
emphasizeUrl();
mIsEmphasizingHttpsScheme = shouldEmphasizeHttpsScheme;
}
private void emphasizeUrl() {
mUrlBar.emphasizeUrl();
}
@Override
public boolean shouldEmphasizeHttpsScheme() {
ToolbarDataProvider provider = getToolbarDataProvider();
if (provider.isUsingBrandColor() || provider.isIncognito()) return false;
return true;
}
/**
* @return Whether the security button is currently being displayed.
*/
@VisibleForTesting
public boolean isSecurityButtonShown() {
return mLocationBarButtonType == BUTTON_TYPE_SECURITY_ICON;
}
/**
* 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) {
if (!DeviceFormFactor.isTablet(getContext())) return;
switch (buttonType) {
case PAGE:
Drawable page = ApiCompatibilityUtils.getDrawable(
getResources(), R.drawable.ic_omnibox_page);
page.setColorFilter(mUseDarkColors
? ApiCompatibilityUtils.getColor(getResources(), 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.setImageDrawable(null);
break;
default:
assert false;
}
if (mNavigationButton.getVisibility() != VISIBLE) {
mNavigationButton.setVisibility(VISIBLE);
}
mNavigationButtonType = buttonType;
updateLocationBarIconContainerVisibility();
}
/**
* Update visibility of the verbose status based on the button type and focus state of the
* omnibox.
*/
private void updateVerboseStatusVisibility() {
// Because is offline page is cleared a bit slower, we also ensure that connection security
// level is NONE.
boolean verboseStatusVisible = !mUrlHasFocus && getCurrentTab() != null
&& getCurrentTab().isOfflinePage()
&& getSecurityLevel() == ConnectionSecurityLevel.NONE;
int verboseStatusVisibility = verboseStatusVisible ? VISIBLE : GONE;
mVerboseStatusTextView.setTextColor(ApiCompatibilityUtils.getColor(getResources(),
mUseDarkColors ? R.color.locationbar_status_color
: R.color.locationbar_status_color_light));
mVerboseStatusTextView.setVisibility(verboseStatusVisibility);
View separator = findViewById(R.id.location_bar_verbose_status_separator);
separator.setBackgroundColor(ApiCompatibilityUtils.getColor(getResources(), mUseDarkColors
? R.color.locationbar_status_separator_color
: R.color.locationbar_status_separator_color_light));
separator.setVisibility(verboseStatusVisibility);
findViewById(R.id.location_bar_verbose_status_extra_space)
.setVisibility(verboseStatusVisibility);
}
/**
* Update the visibility of the location bar icon container based on the state of the
* security and navigation icons.
*/
protected void updateLocationBarIconContainerVisibility() {
@LocationBarButtonType
int buttonToShow = getLocationBarButtonToShow();
findViewById(R.id.location_bar_icon)
.setVisibility(buttonToShow != BUTTON_TYPE_NONE ? VISIBLE : GONE);
}
/**
* 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 == mUrlBar) {
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) mUrlBar.getLayoutParams();
if (ApiCompatibilityUtils.getMarginEnd(urlLayoutParams) != urlContainerMarginEnd) {
ApiCompatibilityUtils.setMarginEnd(urlLayoutParams, urlContainerMarginEnd);
mUrlBar.setLayoutParams(urlLayoutParams);
}
}
/**
* @return Whether the delete button should be shown.
*/
protected boolean shouldShowDeleteButton() {
// Show the delete button at the end when the bar has focus and has some text.
boolean hasText = !TextUtils.isEmpty(mUrlBar.getText());
return hasText && (mUrlBar.hasFocus() || mUrlFocusChangeInProgress);
}
/**
* Updates the display of the delete URL content button.
*/
protected void updateDeleteButtonVisibility() {
mDeleteButton.setVisibility(shouldShowDeleteButton() ? VISIBLE : GONE);
}
/**
* @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());
// Ensure the results container is initialized and add the suggestion list to it.
initOmniboxResultsContainer();
mOmniboxResultsContainer.addView(mSuggestionList);
// Start with visibility GONE to ensure that show() is called. http://crbug.com/517438
mSuggestionList.setVisibility(GONE);
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, false);
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(suggestion.getFillIntoEdit(), null);
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() {
int color = mToolbarDataProvider.isIncognito() ? OMNIBOX_INCOGNITO_RESULTS_BG_COLOR
: OMNIBOX_RESULTS_BG_COLOR;
if (!isHardwareAccelerated()) {
// When HW acceleration is disabled, changing mSuggestionList' items somehow erases
// mOmniboxResultsContainer' background from the area not covered by mSuggestionList.
// To make sure mOmniboxResultsContainer is always redrawn, we make list background
// color slightly transparent. This makes mSuggestionList.isOpaque() to return false,
// and forces redraw of the parent view (mOmniboxResultsContainer).
if (Color.alpha(color) == 255) {
color = Color.argb(254, Color.red(color), Color.green(color), Color.blue(color));
}
}
return new ColorDrawable(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.
* @param skipCheck Whether to skip an out of bounds check.
* @return The url to navigate to.
*/
private String updateSuggestionUrlIfNeeded(OmniboxSuggestion suggestion, int selectedIndex,
boolean skipCheck) {
// 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;
if (suggestion.getType() != OmniboxSuggestionType.VOICE_SUGGEST) {
int verifiedIndex = -1;
if (!skipCheck) {
if (mSuggestionItems.size() > selectedIndex
&& mSuggestionItems.get(selectedIndex).getSuggestion() == suggestion) {
verifiedIndex = selectedIndex;
} else {
// Underlying omnibox results may have changed since the selection was made,
// find the suggestion item, if possible.
for (int i = 0; i < mSuggestionItems.size(); i++) {
if (suggestion.equals(mSuggestionItems.get(i).getSuggestion())) {
verifiedIndex = i;
break;
}
}
}
}
// If we do not have the suggestion as part of our results, skip the URL update.
if (verifiedIndex == -1) return suggestion.getUrl();
// 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(
verifiedIndex, 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;
if (mShowSuggestions != null) removeCallbacks(mShowSuggestions);
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.
*/
@VisibleForTesting
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 Tab}. This calls
* {@link TemplateUrlService#getUrlForSearchQuery(String)} to get a url based on {@code query}
* and loads that url in the current {@link Tab}.
* @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(query, null);
mUrlBar.setSelection(0, mUrlBar.getText().length());
setUrlBarFocus(true);
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 view (location icon, verbose status, ...) which can be clicked to
* show the origin info dialog.
*/
private boolean shouldShowPageInfoForView(View v) {
return v == mSecurityButton || v == mNavigationButton || v == mVerboseStatusTextView;
}
@Override
public void onClick(View v) {
if (v == mDeleteButton) {
if (!TextUtils.isEmpty(mUrlBar.getQueryText())) {
setUrlBarText("", null);
hideSuggestions();
updateButtonVisibility();
}
startZeroSuggest();
return;
} else if (!mUrlHasFocus && shouldShowPageInfoForView(v)) {
Tab currentTab = getCurrentTab();
if (currentTab != null && currentTab.getWebContents() != null) {
Activity activity = currentTab.getWindowAndroid().getActivity().get();
if (activity != null) {
WebsiteSettingsPopup.show(
activity, currentTab, null, WebsiteSettingsPopup.OPENED_FROM_TOOLBAR);
}
}
} else if (v == mMicButton) {
RecordUserAction.record("MobileOmniboxVoiceSearch");
startVoiceRecognition();
} else if (v == mOmniboxResultsContainer) {
// This will only be triggered when no suggestion items are selected in the container.
setUrlBarFocus(false);
updateOmniboxResultsContainerBackground(false);
}
}
@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() != OmniboxSuggestionType.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 (mUrlBar.shouldAutocomplete()) {
mUrlBar.setAutocompleteText(userText, inlineAutocompleteText);
}
// Show the suggestion list.
initSuggestionList(); // It may not have been initialized yet.
mSuggestionList.resetMaxTextWidths();
// Handle the case where suggestions (in particular zero suggest) are received without the
// URL focusing happening.
if (mUrlFocusedWithoutAnimations && mUrlHasFocus) {
handleUrlFocusAnimation(mUrlHasFocus);
}
if (itemsChanged) mSuggestionListAdapter.notifySuggestionsChanged();
if (mUrlBar.hasFocus()) {
final boolean updateLayoutParams = itemCountChanged;
mShowSuggestions = new Runnable() {
@Override
public void run() {
setSuggestionsListVisibility(true);
if (updateLayoutParams) {
mSuggestionList.updateLayoutParams();
}
mShowSuggestions = null;
}
};
if (!isUrlFocusChangeInProgress()) {
mShowSuggestions.run();
} else {
postDelayed(mShowSuggestions, ToolbarPhone.URL_FOCUS_CHANGE_ANIMATION_DURATION_MS);
}
}
// Update the navigation button to show the default suggestion's icon.
updateNavigationButton();
if (!CommandLine.getInstance().hasSwitch(ChromeSwitches.DISABLE_INSTANT)
&& PrivacyPreferencesManager.getInstance().shouldPrerender()) {
mOmniboxPrerender.prerenderMaybe(
userText,
getOriginalUrl(),
mAutocomplete.getCurrentNativeAutocompleteResult(),
getCurrentTab().getProfile(),
getCurrentTab());
}
}
private 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() {
String url = getCurrentTabUrl();
// 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()) {
if (mUrlFocusedWithoutAnimations && !NewTabPage.isNTPUrl(url)) {
// If we did not run the focus animations, then the user has not typed any text.
// So, clear the focus and accept whatever URL the page is currently attempting to
// display.
setUrlBarFocus(false);
} else {
return;
}
}
if (getCurrentTab() == null) {
setUrlBarText("", 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);
mOriginalUrl = url;
if (NativePageFactory.isNativePageUrl(url, getCurrentTab().isIncognito())
|| NewTabPage.isNTPUrl(url)) {
// Don't show anything for Chrome URLs.
setUrlBarText(null, "");
return;
}
if (setUrlBarText(url, mToolbarDataProvider.getText())) {
mUrlBar.deEmphasizeUrl();
emphasizeUrl();
}
updateCustomSelectionActionModeCallback();
}
/** Gets the URL of the web page in the tab. */
private String getCurrentTabUrl() {
Tab currentTab = getCurrentTab();
if (currentTab == null) return "";
return currentTab.getUrl().trim();
}
/**
* Changes the text on the url bar
* @param originalText The original text (URL or search terms) for copy/cut.
* @param displayText The text (URL or search terms) for user display.
* @return Whether the URL was changed as a result of this call.
*/
private boolean setUrlBarText(String originalText, String displayText) {
mUrlBar.setIgnoreTextChangesForAutocomplete(true);
boolean urlChanged = mUrlBar.setUrl(originalText, displayText);
mUrlBar.setIgnoreTextChangesForAutocomplete(false);
return urlChanged;
}
private void loadUrlFromOmniboxMatch(String url, int transition, int matchPosition, int 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 = getCurrentTabUrl();
WebContents webContents = currentTab != null ? currentTab.getWebContents() : null;
long elapsedTimeSinceModified = mNewOmniboxEditSessionTimestamp > 0
? (SystemClock.elapsedRealtime() - mNewOmniboxEditSessionTimestamp) : -1;
mAutocomplete.onSuggestionSelected(matchPosition, type, currentPageUrl,
mUrlFocusedFromFakebox, elapsedTimeSinceModified, mUrlBar.getAutocompleteLength(),
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("MobileOmniboxUse");
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 Tab currently showing.
*/
@Override
public Tab getCurrentTab() {
if (mToolbarDataProvider == null) return null;
return mToolbarDataProvider.getTab();
}
private void initOmniboxResultsContainer() {
if (mOmniboxResultsContainer != null) return;
ViewStub overlayStub =
(ViewStub) getRootView().findViewById(R.id.omnibox_results_container_stub);
mOmniboxResultsContainer = (ViewGroup) overlayStub.inflate();
mOmniboxResultsContainer.setBackgroundColor(CONTENT_OVERLAY_COLOR);
mOmniboxResultsContainer.setOnClickListener(this);
}
private void updateOmniboxResultsContainer() {
if (mSuggestionsShown || mUrlHasFocus) {
initOmniboxResultsContainer();
updateOmniboxResultsContainerVisibility(true);
} else if (mOmniboxResultsContainer != null) {
updateOmniboxResultsContainerBackground(false);
}
}
private void updateOmniboxResultsContainerVisibility(boolean visible) {
boolean currentlyVisible = mOmniboxResultsContainer.getVisibility() == VISIBLE;
if (currentlyVisible == visible) return;
ChromeActivity activity = (ChromeActivity) mWindowAndroid.getActivity().get();
if (visible) {
mOmniboxResultsContainer.setVisibility(VISIBLE);
if (activity != null) activity.addViewObscuringAllTabs(mOmniboxResultsContainer);
} else {
mOmniboxResultsContainer.setVisibility(INVISIBLE);
if (activity != null) activity.removeViewObscuringAllTabs(mOmniboxResultsContainer);
}
}
/**
* 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(),
AnimatorProperties.DRAWABLE_ALPHA_PROPERTY, 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(),
AnimatorProperties.DRAWABLE_ALPHA_PROPERTY, 255, 0);
mFadeOutOmniboxBackgroundAnimator.setDuration(OMNIBOX_CONTAINER_BACKGROUND_FADE_MS);
mFadeOutOmniboxBackgroundAnimator.setInterpolator(
BakedBezierInterpolator.FADE_OUT_CURVE);
mFadeOutOmniboxBackgroundAnimator.addListener(new CancelAwareAnimatorListener() {
@Override
public void onEnd(Animator animator) {
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();
}
@Override
public boolean isVoiceSearchEnabled() {
if (mToolbarDataProvider == null) return false;
if (mToolbarDataProvider.isIncognito()) return false;
if (mWindowAndroid == null) return false;
if (!mWindowAndroid.hasPermission(Manifest.permission.RECORD_AUDIO)
&& !mWindowAndroid.canRequestPermission(Manifest.permission.RECORD_AUDIO)) {
return false;
}
return FeatureUtilities.isRecognitionIntentPresent(getContext(), true);
}
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
if (visibility == View.VISIBLE) updateMicButtonState();
}
/**
* Call to update the visibility of the buttons inside the location bar.
*/
protected void updateButtonVisibility() {
}
/**
* Call to notify the location bar that the state of the voice search microphone button may
* need to be updated.
*/
@Override
public void updateMicButtonState() {
mVoiceSearchEnabled = isVoiceSearchEnabled();
updateButtonVisibility();
}
/**
* Updates the display of the mic button.
* @param urlFocusChangePercent The completeion percentage of the URL focus change animation.
*/
protected void updateMicButtonVisiblity(float urlFocusChangePercent) {
boolean visible = !shouldShowDeleteButton();
boolean showMicButton = mVoiceSearchEnabled && visible
&& (mUrlBar.hasFocus() || mUrlFocusChangeInProgress
|| urlFocusChangePercent > 0f);
mMicButton.setVisibility(showMicButton ? VISIBLE : 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() || mIsEmphasizingHttpsScheme != shouldEmphasizeHttpsScheme()) {
updateSecurityIcon(getSecurityLevel());
}
ColorStateList colorStateList = ApiCompatibilityUtils.getColorStateList(getResources(),
mUseDarkColors ? R.color.dark_mode_tint : R.color.light_mode_tint);
mMicButton.setTint(colorStateList);
mDeleteButton.setTint(colorStateList);
setNavigationButtonType(mNavigationButtonType);
mUrlBar.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 =
ColorUtils.shouldUseLightForegroundOnBackground(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;
if (!mWindowAndroid.hasPermission(Manifest.permission.RECORD_AUDIO)) {
if (mWindowAndroid.canRequestPermission(Manifest.permission.RECORD_AUDIO)) {
WindowAndroid.PermissionCallback callback =
new WindowAndroid.PermissionCallback() {
@Override
public void onRequestPermissionsResult(
String[] permissions, int[] grantResults) {
if (grantResults.length != 1) return;
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startVoiceRecognition();
} else {
updateMicButtonState();
}
}
};
mWindowAndroid.requestPermissions(
new String[] {Manifest.permission.RECORD_AUDIO}, callback);
} else {
updateMicButtonState();
}
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);
}
@Override
public void onTabLoadingNTP(NewTabPage ntp) {
ntp.setFakeboxDelegate(this);
}
@Override
public View getContainerView() {
return this;
}
@Override
public void setTitleToPageTitle() { }
@Override
public void setShowTitle(boolean showTitle) { }
}