summaryrefslogtreecommitdiffstats
path: root/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java')
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java960
1 files changed, 960 insertions, 0 deletions
diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java
new file mode 100644
index 000000000..b24e3b3ea
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java
@@ -0,0 +1,960 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.toolbar;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
+import org.mozilla.gecko.AppConstants.Versions;
+import org.mozilla.gecko.BrowserApp;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.SiteIdentity;
+import org.mozilla.gecko.Tab;
+import org.mozilla.gecko.Tabs;
+import org.mozilla.gecko.Telemetry;
+import org.mozilla.gecko.TelemetryContract;
+import org.mozilla.gecko.TouchEventInterceptor;
+import org.mozilla.gecko.animation.PropertyAnimator;
+import org.mozilla.gecko.animation.PropertyAnimator.PropertyAnimationListener;
+import org.mozilla.gecko.animation.ViewHelper;
+import org.mozilla.gecko.lwt.LightweightTheme;
+import org.mozilla.gecko.lwt.LightweightThemeDrawable;
+import org.mozilla.gecko.menu.GeckoMenu;
+import org.mozilla.gecko.menu.MenuPopup;
+import org.mozilla.gecko.tabs.TabHistoryController;
+import org.mozilla.gecko.toolbar.ToolbarDisplayLayout.OnStopListener;
+import org.mozilla.gecko.toolbar.ToolbarDisplayLayout.OnTitleChangeListener;
+import org.mozilla.gecko.toolbar.ToolbarDisplayLayout.UpdateFlags;
+import org.mozilla.gecko.util.Clipboard;
+import org.mozilla.gecko.util.HardwareUtils;
+import org.mozilla.gecko.util.MenuUtils;
+import org.mozilla.gecko.widget.themed.ThemedFrameLayout;
+import org.mozilla.gecko.widget.themed.ThemedImageButton;
+import org.mozilla.gecko.widget.themed.ThemedImageView;
+import org.mozilla.gecko.widget.themed.ThemedRelativeLayout;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.StateListDrawable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.LayoutInflater;
+import android.view.MenuInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.PopupWindow;
+import android.support.annotation.NonNull;
+
+/**
+* {@code BrowserToolbar} is single entry point for users of the toolbar
+* subsystem i.e. this should be the only import outside the 'toolbar'
+* package.
+*
+* {@code BrowserToolbar} serves at the single event bus for all
+* sub-components in the toolbar. It tracks tab events and gecko messages
+* and update the state of its inner components accordingly.
+*
+* It has two states, display and edit, which are controlled by
+* ToolbarEditLayout and ToolbarDisplayLayout. In display state, the toolbar
+* displays the current state for the selected tab. In edit state, it shows
+* a text entry for searching bookmarks/history. {@code BrowserToolbar}
+* provides public API to enter, cancel, and commit the edit state as well
+* as a set of listeners to allow {@code BrowserToolbar} users to react
+* to state changes accordingly.
+*/
+public abstract class BrowserToolbar extends ThemedRelativeLayout
+ implements Tabs.OnTabsChangedListener,
+ GeckoMenu.ActionItemBarPresenter {
+ private static final String LOGTAG = "GeckoToolbar";
+
+ private static final int LIGHTWEIGHT_THEME_INVERT_ALPHA = 34; // 255 - alpha = invert_alpha
+
+ public interface OnActivateListener {
+ public void onActivate();
+ }
+
+ public interface OnCommitListener {
+ public void onCommit();
+ }
+
+ public interface OnDismissListener {
+ public void onDismiss();
+ }
+
+ public interface OnFilterListener {
+ public void onFilter(String searchText, AutocompleteHandler handler);
+ }
+
+ public interface OnStartEditingListener {
+ public void onStartEditing();
+ }
+
+ public interface OnStopEditingListener {
+ public void onStopEditing();
+ }
+
+ protected enum UIMode {
+ EDIT,
+ DISPLAY
+ }
+
+ protected final ToolbarDisplayLayout urlDisplayLayout;
+ protected final ToolbarEditLayout urlEditLayout;
+ protected final View urlBarEntry;
+ protected boolean isSwitchingTabs;
+ protected final ThemedImageButton tabsButton;
+
+ private ToolbarProgressView progressBar;
+ protected final TabCounter tabsCounter;
+ protected final ThemedFrameLayout menuButton;
+ protected final ThemedImageView menuIcon;
+ private MenuPopup menuPopup;
+ protected final List<View> focusOrder;
+
+ private OnActivateListener activateListener;
+ private OnFocusChangeListener focusChangeListener;
+ private OnStartEditingListener startEditingListener;
+ private OnStopEditingListener stopEditingListener;
+ private TouchEventInterceptor mTouchEventInterceptor;
+
+ protected final BrowserApp activity;
+
+ protected UIMode uiMode;
+ protected TabHistoryController tabHistoryController;
+
+ private final Paint shadowPaint;
+ private final int shadowColor;
+ private final int shadowPrivateColor;
+ private final int shadowSize;
+
+ private final ToolbarPrefs prefs;
+
+ public abstract boolean isAnimating();
+
+ protected abstract boolean isTabsButtonOffscreen();
+
+ protected abstract void updateNavigationButtons(Tab tab);
+
+ protected abstract void triggerStartEditingTransition(PropertyAnimator animator);
+ protected abstract void triggerStopEditingTransition();
+ public abstract void triggerTabsPanelTransition(PropertyAnimator animator, boolean areTabsShown);
+
+ /**
+ * Returns a Drawable overlaid with the theme's bitmap.
+ */
+ protected Drawable getLWTDefaultStateSetDrawable() {
+ return getTheme().getDrawable(this);
+ }
+
+ public static BrowserToolbar create(final Context context, final AttributeSet attrs) {
+ final boolean isLargeResource = context.getResources().getBoolean(R.bool.is_large_resource);
+ final BrowserToolbar toolbar;
+ if (isLargeResource) {
+ toolbar = new BrowserToolbarTablet(context, attrs);
+ } else {
+ toolbar = new BrowserToolbarPhone(context, attrs);
+ }
+ return toolbar;
+ }
+
+ protected BrowserToolbar(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ setWillNotDraw(false);
+
+ // BrowserToolbar is attached to BrowserApp only.
+ activity = (BrowserApp) context;
+
+ LayoutInflater.from(context).inflate(R.layout.browser_toolbar, this);
+
+ Tabs.registerOnTabsChangedListener(this);
+ isSwitchingTabs = true;
+
+ urlDisplayLayout = (ToolbarDisplayLayout) findViewById(R.id.display_layout);
+ urlBarEntry = findViewById(R.id.url_bar_entry);
+ urlEditLayout = (ToolbarEditLayout) findViewById(R.id.edit_layout);
+
+ tabsButton = (ThemedImageButton) findViewById(R.id.tabs);
+ tabsCounter = (TabCounter) findViewById(R.id.tabs_counter);
+ tabsCounter.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+
+ menuButton = (ThemedFrameLayout) findViewById(R.id.menu);
+ menuIcon = (ThemedImageView) findViewById(R.id.menu_icon);
+
+ // The focusOrder List should be filled by sub-classes.
+ focusOrder = new ArrayList<View>();
+
+ final Resources res = getResources();
+ shadowSize = res.getDimensionPixelSize(R.dimen.browser_toolbar_shadow_size);
+
+ shadowPaint = new Paint();
+ shadowColor = ContextCompat.getColor(context, R.color.url_bar_shadow);
+ shadowPrivateColor = ContextCompat.getColor(context, R.color.url_bar_shadow_private);
+ shadowPaint.setColor(shadowColor);
+ shadowPaint.setStrokeWidth(0.0f);
+
+ setUIMode(UIMode.DISPLAY);
+
+ prefs = new ToolbarPrefs();
+ urlDisplayLayout.setToolbarPrefs(prefs);
+ urlEditLayout.setToolbarPrefs(prefs);
+
+ setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+ // Do not show the context menu while editing
+ if (isEditing()) {
+ return;
+ }
+
+ // NOTE: Use MenuUtils.safeSetVisible because some actions might
+ // be on the Page menu
+ MenuInflater inflater = activity.getMenuInflater();
+ inflater.inflate(R.menu.titlebar_contextmenu, menu);
+
+ String clipboard = Clipboard.getText();
+ if (TextUtils.isEmpty(clipboard)) {
+ menu.findItem(R.id.pasteandgo).setVisible(false);
+ menu.findItem(R.id.paste).setVisible(false);
+ }
+
+ Tab tab = Tabs.getInstance().getSelectedTab();
+ if (tab != null) {
+ String url = tab.getURL();
+ if (url == null) {
+ menu.findItem(R.id.copyurl).setVisible(false);
+ menu.findItem(R.id.add_to_launcher).setVisible(false);
+ }
+
+ MenuUtils.safeSetVisible(menu, R.id.subscribe, tab.hasFeeds());
+ MenuUtils.safeSetVisible(menu, R.id.add_search_engine, tab.hasOpenSearch());
+ } else {
+ // if there is no tab, remove anything tab dependent
+ menu.findItem(R.id.copyurl).setVisible(false);
+ menu.findItem(R.id.add_to_launcher).setVisible(false);
+ MenuUtils.safeSetVisible(menu, R.id.subscribe, false);
+ MenuUtils.safeSetVisible(menu, R.id.add_search_engine, false);
+ }
+ }
+ });
+
+ setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (activateListener != null) {
+ activateListener.onActivate();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ prefs.open();
+
+ urlDisplayLayout.setOnStopListener(new OnStopListener() {
+ @Override
+ public Tab onStop() {
+ final Tab tab = Tabs.getInstance().getSelectedTab();
+ if (tab != null) {
+ tab.doStop();
+ return tab;
+ }
+
+ return null;
+ }
+ });
+
+ urlDisplayLayout.setOnTitleChangeListener(new OnTitleChangeListener() {
+ @Override
+ public void onTitleChange(CharSequence title) {
+ final String contentDescription;
+ if (title != null) {
+ contentDescription = title.toString();
+ } else {
+ contentDescription = activity.getString(R.string.url_bar_default_text);
+ }
+
+ // The title and content description should
+ // always be sync.
+ setContentDescription(contentDescription);
+ }
+ });
+
+ urlEditLayout.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ // This will select the url bar when entering editing mode.
+ setSelected(hasFocus);
+ if (focusChangeListener != null) {
+ focusChangeListener.onFocusChange(v, hasFocus);
+ }
+ }
+ });
+
+ tabsButton.setOnClickListener(new Button.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Clear focus so a back press with the tabs
+ // panel open does not go to the editing field.
+ urlEditLayout.clearFocus();
+
+ toggleTabs();
+ }
+ });
+ tabsButton.setImageLevel(0);
+
+ menuButton.setOnClickListener(new Button.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ // Drop the soft keyboard.
+ urlEditLayout.clearFocus();
+ activity.openOptionsMenu();
+ }
+ });
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ prefs.close();
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+
+ final int height = getHeight();
+ canvas.drawRect(0, height - shadowSize, getWidth(), height, shadowPaint);
+ }
+
+ public void onParentFocus() {
+ urlEditLayout.onParentFocus();
+ }
+
+ public void setProgressBar(ToolbarProgressView progressBar) {
+ this.progressBar = progressBar;
+ }
+
+ public void setTabHistoryController(TabHistoryController tabHistoryController) {
+ this.tabHistoryController = tabHistoryController;
+ }
+
+ public void refresh() {
+ urlDisplayLayout.dismissSiteIdentityPopup();
+ }
+
+ public boolean onBackPressed() {
+ // If we exit editing mode during the animation,
+ // we're put into an inconsistent state (bug 1017276).
+ if (isEditing() && !isAnimating()) {
+ Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL,
+ TelemetryContract.Method.BACK);
+ cancelEdit();
+ return true;
+ }
+
+ return urlDisplayLayout.dismissSiteIdentityPopup();
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ if (h != oldh) {
+ // Post this to happen outside of onSizeChanged, as this may cause
+ // a layout change and relayouts within a layout change don't work.
+ post(new Runnable() {
+ @Override
+ public void run() {
+ activity.refreshToolbarHeight();
+ }
+ });
+ }
+ }
+
+ public void saveTabEditingState(final TabEditingState editingState) {
+ urlEditLayout.saveTabEditingState(editingState);
+ }
+
+ public void restoreTabEditingState(final TabEditingState editingState) {
+ if (!isEditing()) {
+ throw new IllegalStateException("Expected to be editing");
+ }
+
+ urlEditLayout.restoreTabEditingState(editingState);
+ }
+
+ @Override
+ public void onTabChanged(@Nullable Tab tab, Tabs.TabEvents msg, String data) {
+ Log.d(LOGTAG, "onTabChanged: " + msg);
+ final Tabs tabs = Tabs.getInstance();
+
+ // These conditions are split into three phases:
+ // * Always do first
+ // * Handling specific to the selected tab
+ // * Always do afterwards.
+
+ switch (msg) {
+ case ADDED:
+ case CLOSED:
+ updateTabCount(tabs.getDisplayCount());
+ break;
+ case RESTORED:
+ // TabCount fixup after OOM
+ case SELECTED:
+ urlDisplayLayout.dismissSiteIdentityPopup();
+ updateTabCount(tabs.getDisplayCount());
+ isSwitchingTabs = true;
+ break;
+ }
+
+ if (tabs.isSelectedTab(tab)) {
+ final EnumSet<UpdateFlags> flags = EnumSet.noneOf(UpdateFlags.class);
+
+ // Progress-related handling
+ switch (msg) {
+ case START:
+ updateProgressVisibility(tab, Tab.LOAD_PROGRESS_INIT);
+ // Fall through.
+ case ADDED:
+ case LOCATION_CHANGE:
+ case LOAD_ERROR:
+ case LOADED:
+ case STOP:
+ flags.add(UpdateFlags.PROGRESS);
+ if (progressBar.getVisibility() == View.VISIBLE) {
+ progressBar.animateProgress(tab.getLoadProgress());
+ }
+ break;
+
+ case SELECTED:
+ flags.add(UpdateFlags.PROGRESS);
+ updateProgressVisibility();
+ break;
+ }
+
+ switch (msg) {
+ case STOP:
+ // Reset the title in case we haven't navigated
+ // to a new page yet.
+ flags.add(UpdateFlags.TITLE);
+ // Fall through.
+ case START:
+ case CLOSED:
+ case ADDED:
+ updateNavigationButtons(tab);
+ break;
+
+ case SELECTED:
+ flags.add(UpdateFlags.PRIVATE_MODE);
+ setPrivateMode(tab.isPrivate());
+ // Fall through.
+ case LOAD_ERROR:
+ case LOCATION_CHANGE:
+ // We're displaying the tab URL in place of the title,
+ // so we always need to update our "title" here as well.
+ flags.add(UpdateFlags.TITLE);
+ flags.add(UpdateFlags.FAVICON);
+ flags.add(UpdateFlags.SITE_IDENTITY);
+
+ updateNavigationButtons(tab);
+ break;
+
+ case TITLE:
+ flags.add(UpdateFlags.TITLE);
+ break;
+
+ case FAVICON:
+ flags.add(UpdateFlags.FAVICON);
+ break;
+
+ case SECURITY_CHANGE:
+ flags.add(UpdateFlags.SITE_IDENTITY);
+ break;
+ }
+
+ if (!flags.isEmpty() && tab != null) {
+ updateDisplayLayout(tab, flags);
+ }
+ }
+
+ switch (msg) {
+ case SELECTED:
+ case LOAD_ERROR:
+ case LOCATION_CHANGE:
+ isSwitchingTabs = false;
+ }
+ }
+
+ private void updateProgressVisibility() {
+ final Tab selectedTab = Tabs.getInstance().getSelectedTab();
+ // The selected tab may be null if GeckoApp (and thus the
+ // selected tab) are not yet initialized (bug 1090287).
+ if (selectedTab != null) {
+ updateProgressVisibility(selectedTab, selectedTab.getLoadProgress());
+ }
+ }
+
+ private void updateProgressVisibility(Tab selectedTab, int progress) {
+ if (!isEditing() && selectedTab.getState() == Tab.STATE_LOADING) {
+ progressBar.setProgress(progress);
+ progressBar.setPrivateMode(selectedTab.isPrivate());
+ progressBar.setVisibility(View.VISIBLE);
+ } else {
+ progressBar.setVisibility(View.GONE);
+ }
+ }
+
+ protected boolean isVisible() {
+ return ViewHelper.getTranslationY(this) == 0;
+ }
+
+ @Override
+ public void setNextFocusDownId(int nextId) {
+ super.setNextFocusDownId(nextId);
+ tabsButton.setNextFocusDownId(nextId);
+ urlDisplayLayout.setNextFocusDownId(nextId);
+ menuButton.setNextFocusDownId(nextId);
+ }
+
+ public boolean hideVirtualKeyboard() {
+ InputMethodManager imm =
+ (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+ return imm.hideSoftInputFromWindow(tabsButton.getWindowToken(), 0);
+ }
+
+ private void showSelectedTabs() {
+ Tab tab = Tabs.getInstance().getSelectedTab();
+ if (tab != null) {
+ if (!tab.isPrivate())
+ activity.showNormalTabs();
+ else
+ activity.showPrivateTabs();
+ }
+ }
+
+ private void toggleTabs() {
+ if (activity.areTabsShown()) {
+ return;
+ }
+
+ if (hideVirtualKeyboard()) {
+ getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ getViewTreeObserver().removeGlobalOnLayoutListener(this);
+ showSelectedTabs();
+ }
+ });
+ } else {
+ showSelectedTabs();
+ }
+ }
+
+ protected void updateTabCount(final int count) {
+ // If toolbar is in edit mode on a phone, this means the entry is expanded
+ // and the tabs button is translated offscreen. Don't trigger tabs counter
+ // updates until the tabs button is back on screen.
+ // See stopEditing()
+ if (isTabsButtonOffscreen()) {
+ return;
+ }
+
+ // Set TabCounter based on visibility
+ if (isVisible() && ViewHelper.getAlpha(tabsCounter) != 0 && !isEditing()) {
+ tabsCounter.setCountWithAnimation(count);
+ } else {
+ tabsCounter.setCount(count);
+ }
+
+ // Update A11y information
+ tabsButton.setContentDescription((count > 1) ?
+ activity.getString(R.string.num_tabs, count) :
+ activity.getString(R.string.one_tab));
+ }
+
+ private void updateDisplayLayout(@NonNull Tab tab, EnumSet<UpdateFlags> flags) {
+ if (isSwitchingTabs) {
+ flags.add(UpdateFlags.DISABLE_ANIMATIONS);
+ }
+
+ urlDisplayLayout.updateFromTab(tab, flags);
+
+ if (flags.contains(UpdateFlags.TITLE)) {
+ if (!isEditing()) {
+ urlEditLayout.setText(tab.getURL());
+ }
+ }
+
+ if (flags.contains(UpdateFlags.PROGRESS)) {
+ updateFocusOrder();
+ }
+ }
+
+ private void updateFocusOrder() {
+ if (focusOrder.size() == 0) {
+ throw new IllegalStateException("Expected focusOrder to be initialized in subclass");
+ }
+
+ View prevView = null;
+
+ // If the element that has focus becomes disabled or invisible, focus
+ // is given to the URL bar.
+ boolean needsNewFocus = false;
+
+ for (View view : focusOrder) {
+ if (view.getVisibility() != View.VISIBLE || !view.isEnabled()) {
+ if (view.hasFocus()) {
+ needsNewFocus = true;
+ }
+ continue;
+ }
+
+ if (view.getId() == R.id.menu_items) {
+ final LinearLayout actionItemBar = (LinearLayout) view;
+ final int childCount = actionItemBar.getChildCount();
+ for (int child = 0; child < childCount; child++) {
+ View childView = actionItemBar.getChildAt(child);
+ if (prevView != null) {
+ childView.setNextFocusLeftId(prevView.getId());
+ prevView.setNextFocusRightId(childView.getId());
+ }
+ prevView = childView;
+ }
+ } else {
+ if (prevView != null) {
+ view.setNextFocusLeftId(prevView.getId());
+ prevView.setNextFocusRightId(view.getId());
+ }
+ prevView = view;
+ }
+ }
+
+ if (needsNewFocus) {
+ requestFocus();
+ }
+ }
+
+ public void setToolBarButtonsAlpha(float alpha) {
+ ViewHelper.setAlpha(tabsCounter, alpha);
+ if (!HardwareUtils.isTablet()) {
+ ViewHelper.setAlpha(menuIcon, alpha);
+ }
+ }
+
+ public void onEditSuggestion(String suggestion) {
+ if (!isEditing()) {
+ return;
+ }
+
+ urlEditLayout.onEditSuggestion(suggestion);
+ }
+
+ public void setTitle(CharSequence title) {
+ urlDisplayLayout.setTitle(title);
+ }
+
+ public void setOnActivateListener(final OnActivateListener listener) {
+ activateListener = listener;
+ }
+
+ public void setOnCommitListener(OnCommitListener listener) {
+ urlEditLayout.setOnCommitListener(listener);
+ }
+
+ public void setOnDismissListener(OnDismissListener listener) {
+ urlEditLayout.setOnDismissListener(listener);
+ }
+
+ public void setOnFilterListener(OnFilterListener listener) {
+ urlEditLayout.setOnFilterListener(listener);
+ }
+
+ @Override
+ public void setOnFocusChangeListener(OnFocusChangeListener listener) {
+ focusChangeListener = listener;
+ }
+
+ public void setOnStartEditingListener(OnStartEditingListener listener) {
+ startEditingListener = listener;
+ }
+
+ public void setOnStopEditingListener(OnStopEditingListener listener) {
+ stopEditingListener = listener;
+ }
+
+ protected void showUrlEditLayout() {
+ setUrlEditLayoutVisibility(true, null);
+ }
+
+ protected void showUrlEditLayout(final PropertyAnimator animator) {
+ setUrlEditLayoutVisibility(true, animator);
+ }
+
+ protected void hideUrlEditLayout() {
+ setUrlEditLayoutVisibility(false, null);
+ }
+
+ protected void hideUrlEditLayout(final PropertyAnimator animator) {
+ setUrlEditLayoutVisibility(false, animator);
+ }
+
+ protected void setUrlEditLayoutVisibility(final boolean showEditLayout, PropertyAnimator animator) {
+ if (showEditLayout) {
+ urlEditLayout.prepareShowAnimation(animator);
+ }
+
+ // If this view is GONE, we trigger a measure pass when setting the view to
+ // VISIBLE. Since this will occur during the toolbar open animation, it causes jank.
+ final int hiddenViewVisibility = View.INVISIBLE;
+
+ if (animator == null) {
+ final View viewToShow = (showEditLayout ? urlEditLayout : urlDisplayLayout);
+ final View viewToHide = (showEditLayout ? urlDisplayLayout : urlEditLayout);
+
+ viewToHide.setVisibility(hiddenViewVisibility);
+ viewToShow.setVisibility(View.VISIBLE);
+ return;
+ }
+
+ animator.addPropertyAnimationListener(new PropertyAnimationListener() {
+ @Override
+ public void onPropertyAnimationStart() {
+ if (!showEditLayout) {
+ urlEditLayout.setVisibility(hiddenViewVisibility);
+ urlDisplayLayout.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void onPropertyAnimationEnd() {
+ if (showEditLayout) {
+ urlDisplayLayout.setVisibility(hiddenViewVisibility);
+ urlEditLayout.setVisibility(View.VISIBLE);
+ }
+ }
+ });
+ }
+
+ private void setUIMode(final UIMode uiMode) {
+ this.uiMode = uiMode;
+ urlEditLayout.setEnabled(uiMode == UIMode.EDIT);
+ }
+
+ /**
+ * Returns whether or not the URL bar is in editing mode (url bar is expanded, hiding the new
+ * tab button). Note that selection state is independent of editing mode.
+ */
+ public boolean isEditing() {
+ return (uiMode == UIMode.EDIT);
+ }
+
+ public void startEditing(String url, PropertyAnimator animator) {
+ if (isEditing()) {
+ return;
+ }
+
+ urlEditLayout.setText(url != null ? url : "");
+
+ setUIMode(UIMode.EDIT);
+
+ updateProgressVisibility();
+
+ if (startEditingListener != null) {
+ startEditingListener.onStartEditing();
+ }
+
+ triggerStartEditingTransition(animator);
+ }
+
+ /**
+ * Exits edit mode without updating the toolbar title.
+ *
+ * @return the url that was entered
+ */
+ public String cancelEdit() {
+ Telemetry.stopUISession(TelemetryContract.Session.AWESOMESCREEN);
+ return stopEditing();
+ }
+
+ /**
+ * Exits edit mode, updating the toolbar title with the url that was just entered.
+ *
+ * @return the url that was entered
+ */
+ public String commitEdit() {
+ Tab tab = Tabs.getInstance().getSelectedTab();
+ if (tab != null) {
+ tab.resetSiteIdentity();
+ }
+
+ final String url = stopEditing();
+ if (!TextUtils.isEmpty(url)) {
+ setTitle(url);
+ }
+ return url;
+ }
+
+ private String stopEditing() {
+ final String url = urlEditLayout.getText();
+ if (!isEditing()) {
+ return url;
+ }
+ setUIMode(UIMode.DISPLAY);
+
+ if (stopEditingListener != null) {
+ stopEditingListener.onStopEditing();
+ }
+
+ updateProgressVisibility();
+ triggerStopEditingTransition();
+
+ return url;
+ }
+
+ @Override
+ public void setPrivateMode(boolean isPrivate) {
+ super.setPrivateMode(isPrivate);
+
+ tabsButton.setPrivateMode(isPrivate);
+ menuButton.setPrivateMode(isPrivate);
+ urlEditLayout.setPrivateMode(isPrivate);
+
+ shadowPaint.setColor(isPrivate ? shadowPrivateColor : shadowColor);
+ }
+
+ public void show() {
+ setVisibility(View.VISIBLE);
+ }
+
+ public void hide() {
+ setVisibility(View.GONE);
+ }
+
+ public View getDoorHangerAnchor() {
+ return urlDisplayLayout;
+ }
+
+ public void onDestroy() {
+ Tabs.unregisterOnTabsChangedListener(this);
+ urlDisplayLayout.destroy();
+ }
+
+ public boolean openOptionsMenu() {
+ // Initialize the popup.
+ if (menuPopup == null) {
+ View panel = activity.getMenuPanel();
+ menuPopup = new MenuPopup(activity);
+ menuPopup.setPanelView(panel);
+
+ menuPopup.setOnDismissListener(new PopupWindow.OnDismissListener() {
+ @Override
+ public void onDismiss() {
+ activity.onOptionsMenuClosed(null);
+ }
+ });
+ }
+
+ GeckoAppShell.getGeckoInterface().invalidateOptionsMenu();
+ if (!menuPopup.isShowing()) {
+ menuPopup.showAsDropDown(menuButton);
+ }
+
+ return true;
+ }
+
+ public boolean closeOptionsMenu() {
+ if (menuPopup != null && menuPopup.isShowing()) {
+ menuPopup.dismiss();
+ }
+
+ return true;
+ }
+
+ @Override
+ public void onLightweightThemeChanged() {
+ final Drawable drawable = getLWTDefaultStateSetDrawable();
+ if (drawable == null) {
+ return;
+ }
+
+ final StateListDrawable stateList = new StateListDrawable();
+ stateList.addState(PRIVATE_STATE_SET, getColorDrawable(R.color.tabs_tray_grey_pressed));
+ stateList.addState(EMPTY_STATE_SET, drawable);
+
+ setBackgroundDrawable(stateList);
+ }
+
+ public void setTouchEventInterceptor(TouchEventInterceptor interceptor) {
+ mTouchEventInterceptor = interceptor;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ if (mTouchEventInterceptor != null && mTouchEventInterceptor.onInterceptTouchEvent(this, event)) {
+ return true;
+ }
+ return super.onInterceptTouchEvent(event);
+ }
+
+ @Override
+ public void onLightweightThemeReset() {
+ setBackgroundResource(R.drawable.url_bar_bg);
+ }
+
+ public static LightweightThemeDrawable getLightweightThemeDrawable(final View view,
+ final LightweightTheme theme, final int colorResID) {
+ final int color = ContextCompat.getColor(view.getContext(), colorResID);
+
+ final LightweightThemeDrawable drawable = theme.getColorDrawable(view, color);
+ if (drawable != null) {
+ drawable.setAlpha(LIGHTWEIGHT_THEME_INVERT_ALPHA, LIGHTWEIGHT_THEME_INVERT_ALPHA);
+ }
+
+ return drawable;
+ }
+
+ public static class TabEditingState {
+ // The edited text from the most recent time this tab was unselected.
+ protected String lastEditingText;
+ protected int selectionStart;
+ protected int selectionEnd;
+
+ public boolean isBrowserSearchShown;
+
+ public void copyFrom(final TabEditingState s2) {
+ lastEditingText = s2.lastEditingText;
+ selectionStart = s2.selectionStart;
+ selectionEnd = s2.selectionEnd;
+
+ isBrowserSearchShown = s2.isBrowserSearchShown;
+ }
+
+ public boolean isBrowserSearchShown() {
+ return isBrowserSearchShown;
+ }
+
+ public void setIsBrowserSearchShown(final boolean isShown) {
+ isBrowserSearchShown = isShown;
+ }
+ }
+}