Bug 942862 - Factor out URL display UI into ToolbarDisplayLayout (r=wesj)
authorLucas Rocha <lucasr@mozilla.com>
Thu, 09 Jan 2014 14:55:28 +0000
changeset 162710 afbc1bece02d2640c6e527dd1f73938d9a9648e3
parent 162709 7463f220cbcc8b571c61740b707c57a20c583ae1
child 162711 f8a1445c80d58b7484958267595bec8c9eba741f
push id4258
push userlrocha@mozilla.com
push dateThu, 09 Jan 2014 14:57:11 +0000
treeherderfx-team@06bc8e68d4fc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerswesj
bugs942862
milestone29.0a1
Bug 942862 - Factor out URL display UI into ToolbarDisplayLayout (r=wesj)
mobile/android/base/BrowserApp.java
mobile/android/base/moz.build
mobile/android/base/resources/layout-large-v11/browser_toolbar.xml
mobile/android/base/resources/layout/browser_toolbar.xml
mobile/android/base/resources/layout/toolbar_display_layout.xml
mobile/android/base/tests/components/ToolbarComponent.java
mobile/android/base/toolbar/BrowserToolbar.java
mobile/android/base/toolbar/ToolbarDisplayLayout.java
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -1413,22 +1413,23 @@ abstract public class BrowserApp extends
         maybeCancelFaviconLoad(tab);
 
         final int tabFaviconSize = getResources().getDimensionPixelSize(R.dimen.browser_toolbar_favicon_size);
 
         int flags = (tab.isPrivate() || tab.getErrorType() != Tab.ErrorType.NONE) ? 0 : LoadFaviconTask.FLAG_PERSIST;
         int id = Favicons.getFaviconForSize(tab.getURL(), tab.getFaviconURL(), tabFaviconSize, flags, sFaviconLoadedListener);
 
         tab.setFaviconLoadId(id);
-        if (id != Favicons.LOADED &&
-            Tabs.getInstance().isSelectedTab(tab)) {
+
+        final Tabs tabs = Tabs.getInstance();
+        if (id != Favicons.LOADED && tabs.isSelectedTab(tab)) {
             // We're loading the current tab's favicon from somewhere
-            // other than the cache.
-            // Display the globe favicon until then.
-            mBrowserToolbar.showDefaultFavicon();
+            // other than the cache. Display the globe favicon until then.
+            tab.updateFavicon(Favicons.sDefaultFavicon);
+            tabs.notifyListeners(tab, Tabs.TabEvents.FAVICON);
         }
     }
 
     private void maybeCancelFaviconLoad(Tab tab) {
         int faviconLoadId = tab.getFaviconLoadId();
 
         if (Favicons.NOT_LOADING == faviconLoadId) {
             return;
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -308,16 +308,17 @@ gbjar.sources += [
     'toolbar/BackButton.java',
     'toolbar/BrowserToolbar.java',
     'toolbar/CanvasDelegate.java',
     'toolbar/ForwardButton.java',
     'toolbar/PageActionLayout.java',
     'toolbar/ShapedButton.java',
     'toolbar/SiteIdentityPopup.java',
     'toolbar/TabCounter.java',
+    'toolbar/ToolbarDisplayLayout.java',
     'toolbar/ToolbarEditLayout.java',
     'toolbar/ToolbarEditText.java',
     'TouchEventInterceptor.java',
     'updater/UpdateService.java',
     'updater/UpdateServiceHelper.java',
     'VideoPlayer.java',
     'WebAppAllocator.java',
     'WebAppImpl.java',
--- a/mobile/android/base/resources/layout-large-v11/browser_toolbar.xml
+++ b/mobile/android/base/resources/layout-large-v11/browser_toolbar.xml
@@ -64,68 +64,20 @@
                   style="@style/UrlBar.Button"
                   android:paddingLeft="12dp"
                   android:paddingRight="4dp"
                   android:visibility="gone"
                   android:orientation="horizontal"
                   android:layout_toRightOf="@id/back"
                   android:layout_toLeftOf="@id/menu_items"/>
 
-    <LinearLayout android:id="@+id/url_display_container"
+    <org.mozilla.gecko.toolbar.ToolbarDisplayLayout android:id="@+id/display_layout"
                   style="@style/UrlBar.Button.Container"
                   android:layout_toRightOf="@id/back"
-                  android:layout_toLeftOf="@id/menu_items">
-
-        <ImageButton android:id="@+id/favicon"
-                     style="@style/UrlBar.ImageButton"
-                     android:layout_width="@dimen/browser_toolbar_favicon_size"
-                     android:layout_height="fill_parent"
-                     android:scaleType="fitCenter"
-                     android:layout_marginLeft="4dip"
-                     android:paddingLeft="4dip"
-                     android:paddingRight="4dip"
-                     android:layout_gravity="center_vertical"
-                     android:src="@drawable/favicon"/>
-
-        <ImageButton android:id="@+id/site_security"
-                     style="@style/UrlBar.ImageButton"
-                     android:layout_width="@dimen/browser_toolbar_lock_width"
-                     android:scaleType="fitCenter"
-                     android:layout_marginLeft="-4dip"
-                     android:src="@drawable/site_security_level"
-                     android:contentDescription="@string/site_security"
-                     android:visibility="gone"/>
-
-        <org.mozilla.gecko.widget.GeckoTextView android:id="@+id/url_bar_title"
-                                                style="@style/UrlBar.Button"
-                                                android:layout_width="fill_parent"
-                                                android:layout_height="fill_parent"
-                                                android:layout_weight="1.0"
-                                                android:singleLine="true"
-                                                android:paddingRight="8dp"
-                                                android:textColor="@color/url_bar_title"
-                                                android:textColorHint="@color/url_bar_title_hint"
-                                                android:gravity="center_vertical|left"
-                                                android:layout_gravity="center_vertical"
-                                                gecko:autoUpdateTheme="false"/>
-
-        <org.mozilla.gecko.toolbar.PageActionLayout android:id="@+id/page_action_layout"
-                                                    android:layout_width="wrap_content"
-                                                    android:layout_height="match_parent"
-                                                    android:layout_marginRight="@dimen/browser_toolbar_button_padding"
-                                                    android:visibility="gone"
-                                                    android:orientation="horizontal"/>
-
-        <ImageButton android:id="@+id/stop"
-                     style="@style/UrlBar.ImageButton.Icon"
-                     android:src="@drawable/urlbar_stop"
-                     android:contentDescription="@string/stop"
-                     android:visibility="gone"/>
-
-    </LinearLayout>
+                  android:layout_toLeftOf="@id/menu_items"/>
 
     <LinearLayout android:id="@+id/menu_items"
                   android:layout_width="wrap_content"
                   android:layout_height="fill_parent"
                   android:layout_marginLeft="3dp"
                   android:orientation="horizontal"
                   android:layout_toLeftOf="@id/menu"
                   android:layout_alignWithParentIfMissing="true"/>
--- a/mobile/android/base/resources/layout/browser_toolbar.xml
+++ b/mobile/android/base/resources/layout/browser_toolbar.xml
@@ -88,69 +88,20 @@
 
     <org.mozilla.gecko.toolbar.ToolbarEditLayout android:id="@+id/edit_layout"
                   style="@style/UrlBar.Button"
                   android:layout_marginLeft="4dp"
                   android:layout_marginRight="4dp"
                   android:paddingLeft="8dp"
                   android:visibility="gone"/>
 
-    <LinearLayout android:id="@+id/url_display_container"
+    <org.mozilla.gecko.toolbar.ToolbarDisplayLayout android:id="@+id/display_layout"
                   style="@style/UrlBar.Button"
                   android:layout_toLeftOf="@id/tabs"
-                  android:layout_marginRight="-24dp"
-                  android:orientation="horizontal">
-
-        <ImageButton android:id="@+id/favicon"
-                     style="@style/UrlBar.ImageButton"
-                     android:layout_width="@dimen/browser_toolbar_favicon_size"
-                     android:layout_height="fill_parent"
-                     android:scaleType="fitCenter"
-                     android:layout_marginLeft="8dip"
-                     android:paddingLeft="4dip"
-                     android:paddingRight="4dip"
-                     android:layout_gravity="center_vertical"/>
-
-        <ImageButton android:id="@+id/site_security"
-                     style="@style/UrlBar.ImageButton"
-                     android:layout_width="@dimen/browser_toolbar_lock_width"
-                     android:scaleType="fitCenter"
-                     android:layout_marginLeft="-4dip"
-                     android:src="@drawable/site_security_level"
-                     android:contentDescription="@string/site_security"
-                     android:visibility="gone"/>
-
-        <org.mozilla.gecko.widget.GeckoTextView android:id="@+id/url_bar_title"
-                                                style="@style/UrlBar.Button"
-                                                android:layout_width="fill_parent"
-                                                android:layout_height="fill_parent"
-                                                android:layout_weight="1.0"
-                                                android:singleLine="true"
-                                                android:paddingRight="8dp"
-                                                android:textColor="@color/url_bar_title"
-                                                android:textColorHint="@color/url_bar_title_hint"
-                                                android:gravity="center_vertical|left"
-                                                android:hint="@string/url_bar_default_text"
-                                                android:layout_gravity="center_vertical"
-                                                gecko:autoUpdateTheme="false"/>
-
-        <org.mozilla.gecko.toolbar.PageActionLayout android:id="@+id/page_action_layout"
-                                                    android:layout_width="wrap_content"
-                                                    android:layout_height="match_parent"
-                                                    android:layout_marginRight="12dp"
-                                                    android:visibility="gone"
-                                                    android:orientation="horizontal"/>
-
-        <ImageButton android:id="@+id/stop"
-                     style="@style/UrlBar.ImageButton.Icon"
-                     android:src="@drawable/urlbar_stop"
-                     android:contentDescription="@string/stop"
-                     android:visibility="gone"/>
-
-    </LinearLayout>
+                  android:layout_marginRight="-24dp"/>
 
     <ImageView android:id="@+id/shadow"
                android:layout_width="fill_parent"
                android:layout_height="2dp"
                android:layout_alignParentBottom="true"
                android:background="@color/url_bar_shadow"
                android:contentDescription="@null"/>
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/toolbar_display_layout.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+       xmlns:gecko="http://schemas.android.com/apk/res-auto">
+
+    <ImageButton android:id="@+id/favicon"
+                 style="@style/UrlBar.ImageButton"
+                 android:layout_width="@dimen/browser_toolbar_favicon_size"
+                 android:layout_height="fill_parent"
+                 android:scaleType="fitCenter"
+                 android:layout_marginLeft="8dip"
+                 android:paddingLeft="4dip"
+                 android:paddingRight="4dip"
+                 android:layout_gravity="center_vertical"/>
+
+    <ImageButton android:id="@+id/site_security"
+                 style="@style/UrlBar.ImageButton"
+                 android:layout_width="@dimen/browser_toolbar_lock_width"
+                 android:scaleType="fitCenter"
+                 android:layout_marginLeft="-4dip"
+                 android:src="@drawable/site_security_level"
+                 android:contentDescription="@string/site_security"
+                 android:visibility="gone"/>
+
+    <org.mozilla.gecko.widget.GeckoTextView android:id="@+id/url_bar_title"
+                                            style="@style/UrlBar.Button"
+                                            android:layout_width="fill_parent"
+                                            android:layout_height="fill_parent"
+                                            android:layout_weight="1.0"
+                                            android:singleLine="true"
+                                            android:paddingRight="8dp"
+                                            android:textColor="@color/url_bar_title"
+                                            android:textColorHint="@color/url_bar_title_hint"
+                                            android:gravity="center_vertical|left"
+                                            android:hint="@string/url_bar_default_text"
+                                            android:layout_gravity="center_vertical"
+                                            gecko:autoUpdateTheme="false"/>
+
+    <org.mozilla.gecko.toolbar.PageActionLayout android:id="@+id/page_action_layout"
+                                                android:layout_width="wrap_content"
+                                                android:layout_height="match_parent"
+                                                android:layout_marginRight="@dimen/browser_toolbar_button_padding"
+                                                android:visibility="gone"
+                                                android:orientation="horizontal"/>
+
+    <ImageButton android:id="@+id/stop"
+                 style="@style/UrlBar.ImageButton.Icon"
+                 android:src="@drawable/urlbar_stop"
+                 android:contentDescription="@string/stop"
+                 android:visibility="gone"/>
+
+</merge>
--- a/mobile/android/base/tests/components/ToolbarComponent.java
+++ b/mobile/android/base/tests/components/ToolbarComponent.java
@@ -54,18 +54,18 @@ public class ToolbarComponent extends Ba
     private View getToolbarView() {
         return mSolo.getView(R.id.browser_toolbar);
     }
 
     private EditText getUrlEditText() {
         return (EditText) getToolbarView().findViewById(R.id.url_edit_text);
     }
 
-    private View getUrlDisplayContainer() {
-        return getToolbarView().findViewById(R.id.url_display_container);
+    private View getUrlDisplayLayout() {
+        return getToolbarView().findViewById(R.id.display_layout);
     }
 
     private TextView getUrlTitleText() {
         return (TextView) getToolbarView().findViewById(R.id.url_bar_title);
     }
 
     /**
      * Returns the View for the go button in the browser toolbar.
@@ -101,17 +101,17 @@ public class ToolbarComponent extends Ba
         if (shouldAssertNotEditing) {
             assertIsNotEditing();
         }
 
         return getUrlTitleText().getText();
     }
 
     private boolean isEditing() {
-        return getUrlDisplayContainer().getVisibility() != View.VISIBLE &&
+        return getUrlDisplayLayout().getVisibility() != View.VISIBLE &&
                 getUrlEditText().getVisibility() == View.VISIBLE;
     }
 
     public ToolbarComponent enterEditingMode() {
         assertIsNotEditing();
 
         mSolo.clickOnView(getUrlTitleText(), true);
 
--- a/mobile/android/base/toolbar/BrowserToolbar.java
+++ b/mobile/android/base/toolbar/BrowserToolbar.java
@@ -7,17 +7,16 @@ package org.mozilla.gecko.toolbar;
 
 import org.mozilla.gecko.AboutPages;
 import org.mozilla.gecko.BrowserApp;
 import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.LightweightTheme;
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.SiteIdentity;
 import org.mozilla.gecko.SiteIdentity.SecurityMode;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.PropertyAnimator.PropertyAnimationListener;
 import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.menu.GeckoMenu;
 import org.mozilla.gecko.menu.MenuPopup;
@@ -25,61 +24,55 @@ import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.widget.GeckoImageButton;
 import org.mozilla.gecko.widget.GeckoImageView;
 import org.mozilla.gecko.widget.GeckoRelativeLayout;
-import org.mozilla.gecko.widget.GeckoTextView;
 
 import org.json.JSONObject;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.StateListDrawable;
 import android.os.Build;
-import android.os.SystemClock;
 import android.text.style.ForegroundColorSpan;
 import android.text.Spannable;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.ContextMenu;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MenuInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup.MarginLayoutParams;
 import android.view.animation.AccelerateInterpolator;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.view.animation.AlphaAnimation;
 import android.view.animation.Interpolator;
-import android.view.animation.TranslateAnimation;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Button;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.PopupWindow;
 
 import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.List;
 
 public class BrowserToolbar extends GeckoRelativeLayout
                             implements Tabs.OnTabsChangedListener,
                                        GeckoMenu.ActionItemBarPresenter,
-                                       Animation.AnimationListener,
                                        GeckoEventListener {
     private static final String LOGTAG = "GeckoToolbar";
     public static final String PREF_TITLEBAR_MODE = "browser.chrome.titlebarMode";
     public static final String PREF_TRIM_URLS = "browser.urlbar.trimURLs";
 
     public interface OnActivateListener {
         public void onActivate();
     }
@@ -99,75 +92,52 @@ public class BrowserToolbar extends Geck
     public interface OnStartEditingListener {
         public void onStartEditing();
     }
 
     public interface OnStopEditingListener {
         public void onStopEditing();
     }
 
-    private enum ForwardButtonAnimation {
+    enum ForwardButtonAnimation {
         SHOW,
         HIDE
     }
 
-    private View mUrlDisplayContainer;
+    private ToolbarDisplayLayout mUrlDisplayLayout;
     private ToolbarEditLayout mUrlEditLayout;
     private View mUrlBarEntry;
     private ImageView mUrlBarRightEdge;
-    private GeckoTextView mTitle;
-    private int mTitlePadding;
-    private boolean mSiteSecurityVisible;
     private boolean mSwitchingTabs;
     private ShapedButton mTabs;
     private ImageButton mBack;
     private ImageButton mForward;
-    private ImageButton mStop;
 
-    // To de-bounce sets.
-    private Bitmap mLastFavicon;
-    private ImageButton mFavicon;
-
-    private ImageButton mSiteSecurity;
-    private PageActionLayout mPageActionLayout;
-    private Animation mProgressSpinner;
     private TabCounter mTabsCounter;
     private GeckoImageButton mMenu;
     private GeckoImageView mMenuIcon;
     private LinearLayout mActionItemBar;
     private MenuPopup mMenuPopup;
-    private List<? extends View> mFocusOrder;
+    private List<View> mFocusOrder;
 
     private OnActivateListener mActivateListener;
     private OnCommitListener mCommitListener;
     private OnDismissListener mDismissListener;
     private OnFilterListener mFilterListener;
     private OnStartEditingListener mStartEditingListener;
     private OnStopEditingListener mStopEditingListener;
 
-    private SiteIdentityPopup mSiteIdentityPopup;
-
     final private BrowserApp mActivity;
     private boolean mHasSoftMenuButton;
 
-    private boolean mShowSiteSecurity;
-    private boolean mSpinnerVisible;
-
     private boolean mIsEditing;
     private boolean mAnimatingEntry;
 
-    private AlphaAnimation mLockFadeIn;
-    private TranslateAnimation mTitleSlideLeft;
-    private TranslateAnimation mTitleSlideRight;
-
     private int mUrlBarViewOffset;
     private int mDefaultForwardMargin;
-    private PropertyAnimator mForwardAnim = null;
-
-    private int mFaviconSize;
 
     private static final Interpolator sButtonsInterpolator = new AccelerateInterpolator();
 
     private static final int TABS_CONTRACTED = 1;
     private static final int TABS_EXPANDED = 2;
 
     private static final int FORWARD_ANIMATION_DURATION = 450;
     private final ForegroundColorSpan mUrlColor;
@@ -255,76 +225,54 @@ public class BrowserToolbar extends Geck
         mUrlColor = new ForegroundColorSpan(res.getColor(R.color.url_bar_urltext));
         mBlockedColor = new ForegroundColorSpan(res.getColor(R.color.url_bar_blockedtext));
         mDomainColor = new ForegroundColorSpan(res.getColor(R.color.url_bar_domaintext));
         mPrivateDomainColor = new ForegroundColorSpan(res.getColor(R.color.url_bar_domaintext_private));
 
         registerEventListener("Reader:Click");
         registerEventListener("Reader:LongClick");
 
-        mShowSiteSecurity = false;
-
         mAnimatingEntry = false;
 
         mUrlBarViewOffset = res.getDimensionPixelSize(R.dimen.url_bar_offset_left);
         mDefaultForwardMargin = res.getDimensionPixelSize(R.dimen.forward_default_offset);
-        mUrlDisplayContainer = findViewById(R.id.url_display_container);
+        mUrlDisplayLayout = (ToolbarDisplayLayout) findViewById(R.id.display_layout);
         mUrlBarEntry = findViewById(R.id.url_bar_entry);
         mUrlEditLayout = (ToolbarEditLayout) findViewById(R.id.edit_layout);
 
         // This will clip the right edge's image at 60% of its width
         mUrlBarRightEdge = (ImageView) findViewById(R.id.url_bar_right_edge);
         if (mUrlBarRightEdge != null) {
             mUrlBarRightEdge.getDrawable().setLevel(6000);
         }
 
-        mTitle = (GeckoTextView) findViewById(R.id.url_bar_title);
-        mTitlePadding = mTitle.getPaddingRight();
-
         mTabs = (ShapedButton) findViewById(R.id.tabs);
         mTabsCounter = (TabCounter) findViewById(R.id.tabs_counter);
 
         mBack = (ImageButton) findViewById(R.id.back);
         setButtonEnabled(mBack, false);
         mForward = (ImageButton) findViewById(R.id.forward);
         setButtonEnabled(mForward, false);
 
-        mFavicon = (ImageButton) findViewById(R.id.favicon);
-        if (Build.VERSION.SDK_INT >= 11) {
-            if (Build.VERSION.SDK_INT >= 16) {
-                mFavicon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
-            }
-            mFavicon.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-        }
-        mFaviconSize = Math.round(res.getDimension(R.dimen.browser_toolbar_favicon_size));
-
-        mSiteSecurity = (ImageButton) findViewById(R.id.site_security);
-        mSiteSecurityVisible = (mSiteSecurity.getVisibility() == View.VISIBLE);
-
-        mSiteIdentityPopup = new SiteIdentityPopup(mActivity);
-        mSiteIdentityPopup.setAnchor(mSiteSecurity);
-
-        mProgressSpinner = AnimationUtils.loadAnimation(mActivity, R.anim.progress_spinner);
-
-        mStop = (ImageButton) findViewById(R.id.stop);
-        mPageActionLayout = (PageActionLayout) findViewById(R.id.page_action_layout);
-
         mMenu = (GeckoImageButton) findViewById(R.id.menu);
         mMenuIcon = (GeckoImageView) findViewById(R.id.menu_icon);
         mActionItemBar = (LinearLayout) findViewById(R.id.menu_items);
         mHasSoftMenuButton = !HardwareUtils.hasMenuButton();
 
         // We use different layouts on phones and tablets, so adjust the focus
         // order appropriately.
+        mFocusOrder = new ArrayList<View>();
         if (HardwareUtils.isTablet()) {
-            mFocusOrder = Arrays.asList(mTabs, mBack, mForward, this,
-                    mSiteSecurity, mPageActionLayout, mStop, mActionItemBar, mMenu);
+            mFocusOrder.addAll(Arrays.asList(mTabs, mBack, mForward, this));
+            mFocusOrder.addAll(mUrlDisplayLayout.getFocusOrder());
+            mFocusOrder.addAll(Arrays.asList(mActionItemBar, mMenu));
         } else {
-            mFocusOrder = Arrays.asList(this, mSiteSecurity, mPageActionLayout, mStop,
-                    mTabs, mMenu);
+            mFocusOrder.add(this);
+            mFocusOrder.addAll(mUrlDisplayLayout.getFocusOrder());
+            mFocusOrder.addAll(Arrays.asList(mTabs, mMenu));
         }
 
         setIsEditing(false);
     }
 
     @Override
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
@@ -415,87 +363,35 @@ public class BrowserToolbar extends Geck
         });
         mForward.setOnLongClickListener(new Button.OnLongClickListener() {
             @Override
             public boolean onLongClick(View view) {
                 return Tabs.getInstance().getSelectedTab().showForwardHistory();
             }
         });
 
-        Button.OnClickListener faviconListener = new Button.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                if (mSiteSecurity.getVisibility() != View.VISIBLE)
-                    return;
-
-                final Tab tab = Tabs.getInstance().getSelectedTab();
-
-                final SiteIdentity siteIdentity = tab.getSiteIdentity();
-                if (siteIdentity.getSecurityMode() == SecurityMode.UNKNOWN) {
-                    Log.e(LOGTAG, "Selected tab has no identity data");
-                    return;
-                }
-
-                mSiteIdentityPopup.updateIdentity(siteIdentity);
-                mSiteIdentityPopup.show();
-            }
-        };
-
-        mFavicon.setOnClickListener(faviconListener);
-        mSiteSecurity.setOnClickListener(faviconListener);
-
-        mStop.setOnClickListener(new Button.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                Tab tab = Tabs.getInstance().getSelectedTab();
-                if (tab != null)
-                    tab.doStop();
-                setProgressVisibility(false);
-            }
-        });
-
-        float slideWidth = getResources().getDimension(R.dimen.browser_toolbar_lock_width);
-
-        LinearLayout.LayoutParams siteSecParams = (LinearLayout.LayoutParams) mSiteSecurity.getLayoutParams();
-        final float scale = getResources().getDisplayMetrics().density;
-        slideWidth += (siteSecParams.leftMargin + siteSecParams.rightMargin) * scale + 0.5f;
-
-        mLockFadeIn = new AlphaAnimation(0.0f, 1.0f);
-        mLockFadeIn.setAnimationListener(this);
-
-        mTitleSlideLeft = new TranslateAnimation(slideWidth, 0, 0, 0);
-        mTitleSlideLeft.setAnimationListener(this);
-
-        mTitleSlideRight = new TranslateAnimation(-slideWidth, 0, 0, 0);
-        mTitleSlideRight.setAnimationListener(this);
-
-        final int lockAnimDuration = 300;
-        mLockFadeIn.setDuration(lockAnimDuration);
-        mTitleSlideLeft.setDuration(lockAnimDuration);
-        mTitleSlideRight.setDuration(lockAnimDuration);
-
         if (mHasSoftMenuButton) {
             mMenu.setVisibility(View.VISIBLE);
             mMenuIcon.setVisibility(View.VISIBLE);
 
             mMenu.setOnClickListener(new Button.OnClickListener() {
                 @Override
                 public void onClick(View view) {
                     mActivity.openOptionsMenu();
                 }
             });
         }
     }
 
     public void refresh() {
-        dismissSiteIdentityPopup();
+        mUrlDisplayLayout.dismissSiteIdentityPopup();
     }
 
     public boolean onBackPressed() {
-        return dismissSiteIdentityPopup();
+        return mUrlDisplayLayout.dismissSiteIdentityPopup();
     }
 
     public boolean onKey(int keyCode, KeyEvent event) {
         if (event.getAction() != KeyEvent.ACTION_DOWN) {
             return false;
         }
 
         // Galaxy Note sends key events for the stylus that are outside of the
@@ -564,17 +460,17 @@ public class BrowserToolbar extends Geck
         switch (msg) {
             case ADDED:
             case CLOSED:
                 updateTabCount(tabs.getDisplayCount());
                 break;
             case RESTORED:
                 // TabCount fixup after OOM
             case SELECTED:
-                dismissSiteIdentityPopup();
+                mUrlDisplayLayout.dismissSiteIdentityPopup();
                 updateTabCount(tabs.getDisplayCount());
                 mSwitchingTabs = true;
                 // Fall through.
         }
 
         if (tabs.isSelectedTab(tab)) {
             switch (msg) {
                 case TITLE:
@@ -583,17 +479,17 @@ public class BrowserToolbar extends Geck
 
                 case START:
                     updateBackButton(tab);
                     updateForwardButton(tab);
                     if (tab.getState() == Tab.STATE_LOADING) {
                         setProgressVisibility(true);
                     }
                     setSecurityMode(tab.getSecurityMode());
-                    setPageActionVisibility(mStop.getVisibility() == View.VISIBLE);
+                    setPageActionVisibility(mUrlDisplayLayout.isShowingProgress());
                     break;
 
                 case STOP:
                     updateBackButton(tab);
                     updateForwardButton(tab);
                     setProgressVisibility(false);
                     // Reset the title in case we haven't navigated to a new page yet.
                     updateTitle();
@@ -619,82 +515,43 @@ public class BrowserToolbar extends Geck
                     setFavicon(tab.getFavicon());
                     break;
 
                 case SECURITY_CHANGE:
                     setSecurityMode(tab.getSecurityMode());
                     break;
 
                 case READER_ENABLED:
-                    setPageActionVisibility(mStop.getVisibility() == View.VISIBLE);
+                    setPageActionVisibility(mUrlDisplayLayout.isShowingProgress());
                     break;
             }
         }
 
         switch (msg) {
             case SELECTED:
             case LOAD_ERROR:
             case LOCATION_CHANGE:
                 mSwitchingTabs = false;
         }
     }
 
     public boolean isVisible() {
         return ViewHelper.getTranslationY(this) == 0;
     }
 
+    @Override
     public void setNextFocusDownId(int nextId) {
         super.setNextFocusDownId(nextId);
         mTabs.setNextFocusDownId(nextId);
         mBack.setNextFocusDownId(nextId);
         mForward.setNextFocusDownId(nextId);
-        mFavicon.setNextFocusDownId(nextId);
-        mStop.setNextFocusDownId(nextId);
-        mSiteSecurity.setNextFocusDownId(nextId);
-        mPageActionLayout.setNextFocusDownId(nextId);
+        mUrlDisplayLayout.setNextFocusDownId(nextId);
         mMenu.setNextFocusDownId(nextId);
     }
 
-    @Override
-    public void onAnimationStart(Animation animation) {
-        if (animation.equals(mLockFadeIn)) {
-            if (mSiteSecurityVisible)
-                mSiteSecurity.setVisibility(View.VISIBLE);
-        } else if (animation.equals(mTitleSlideLeft)) {
-            // These two animations may be scheduled to start while the forward
-            // animation is occurring. If we're showing the site security icon, make
-            // sure it doesn't take any space during the forward transition.
-            mSiteSecurity.setVisibility(View.GONE);
-        } else if (animation.equals(mTitleSlideRight)) {
-            // If we're hiding the icon, make sure that we keep its padding
-            // in place during the forward transition
-            mSiteSecurity.setVisibility(View.INVISIBLE);
-        }
-    }
-
-    @Override
-    public void onAnimationRepeat(Animation animation) {
-    }
-
-    @Override
-    public void onAnimationEnd(Animation animation) {
-        if (animation.equals(mTitleSlideRight)) {
-            mSiteSecurity.startAnimation(mLockFadeIn);
-        }
-    }
-
-    private boolean dismissSiteIdentityPopup() {
-        if (mSiteIdentityPopup != null && mSiteIdentityPopup.isShowing()) {
-            mSiteIdentityPopup.dismiss();
-            return true;
-        }
-
-        return false;
-    }
-
     private int getUrlBarEntryTranslation() {
         return getWidth() - mUrlBarEntry.getRight();
     }
 
     private int getUrlBarCurveTranslation() {
         return getWidth() - mTabs.getLeft();
     }
 
@@ -768,95 +625,24 @@ public class BrowserToolbar extends Geck
 
         // Update A11y information
         mTabs.setContentDescription((count > 1) ?
                                     mActivity.getString(R.string.num_tabs, count) :
                                     mActivity.getString(R.string.one_tab));
     }
 
     public void setProgressVisibility(boolean visible) {
-        Log.d(LOGTAG, "setProgressVisibility: " + visible);
-        // The "Throbber start" and "Throbber stop" log messages in this method
-        // are needed by S1/S2 tests (http://mrcote.info/phonedash/#).
-        // See discussion in Bug 804457. Bug 805124 tracks paring these down.
-        if (visible) {
-            mFavicon.setImageResource(R.drawable.progress_spinner);
-            mLastFavicon = null;
-
-            // To stop the glitch caused by multiple start() calls.
-            if (!mSpinnerVisible) {
-                setPageActionVisibility(true);
-                mFavicon.setAnimation(mProgressSpinner);
-                mProgressSpinner.start();
-                mSpinnerVisible = true;
-            }
-            Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - Throbber start");
-        } else {
-            Tab selectedTab = Tabs.getInstance().getSelectedTab();
-            if (selectedTab != null) {
-                setFavicon(selectedTab.getFavicon());
-            }
-
-            if (mSpinnerVisible) {
-                setPageActionVisibility(false);
-                mFavicon.setAnimation(null);
-                mProgressSpinner.cancel();
-                mSpinnerVisible = false;
-            }
-            Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - Throbber stop");
-        }
+        mUrlDisplayLayout.setProgressVisibility(visible);
     }
 
     public void setPageActionVisibility(boolean isLoading) {
-        // Handle the loading mode page actions
-        mStop.setVisibility(isLoading ? View.VISIBLE : View.GONE);
-
-        // Handle the viewing mode page actions
-        setSiteSecurityVisibility(mShowSiteSecurity && !isLoading);
-        mPageActionLayout.setVisibility(!isLoading ? View.VISIBLE : View.GONE);
-
-        // We want title to fill the whole space available for it when there are icons
-        // being shown on the right side of the toolbar as the icons already have some
-        // padding in them. This is just to avoid wasting space when icons are shown.
-        mTitle.setPadding(0, 0, (!isLoading ? mTitlePadding : 0), 0);
+        mUrlDisplayLayout.setPageActionVisibility(isLoading, !mSwitchingTabs);
         updateFocusOrder();
     }
 
-    private void setSiteSecurityVisibility(final boolean visible) {
-        if (visible == mSiteSecurityVisible)
-            return;
-
-        mSiteSecurityVisible = visible;
-
-        if (mSwitchingTabs) {
-            mSiteSecurity.setVisibility(visible ? View.VISIBLE : View.GONE);
-            return;
-        }
-
-        mTitle.clearAnimation();
-        mSiteSecurity.clearAnimation();
-
-        // If any of these animations were cancelled as a result of the
-        // clearAnimation() calls above, we need to reset them.
-        mLockFadeIn.reset();
-        mTitleSlideLeft.reset();
-        mTitleSlideRight.reset();
-
-        if (mForwardAnim != null) {
-            long delay = mForwardAnim.getRemainingTime();
-            mTitleSlideRight.setStartOffset(delay);
-            mTitleSlideLeft.setStartOffset(delay);
-        } else {
-            mTitleSlideRight.setStartOffset(0);
-            mTitleSlideLeft.setStartOffset(0);
-        }
-
-        mTitle.startAnimation(visible ? mTitleSlideRight : mTitleSlideLeft);
-    }
-
     private void updateFocusOrder() {
         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 : mFocusOrder) {
@@ -895,18 +681,26 @@ public class BrowserToolbar extends Geck
         if (!isEditing()) {
             return;
         }
 
         mUrlEditLayout.onEditSuggestion(suggestion);
     }
 
     public void setTitle(CharSequence title) {
-        mTitle.setText(title);
-        setContentDescription(title != null ? title : mTitle.getHint());
+        mUrlDisplayLayout.setTitle(title);
+
+        final String contentDescription;
+        if (title != null) {
+            contentDescription = title.toString();
+        } else {
+            contentDescription = mActivity.getString(R.string.url_bar_default_text);
+        }
+
+        setContentDescription(contentDescription);
     }
 
     // Sets the toolbar title according to the selected tab, obeying the mShowUrl preference.
     private void updateTitle() {
         final Tab tab = Tabs.getInstance().getSelectedTab();
         // Keep the title unchanged if there's no selected tab, or if the tab is entering reader mode.
         if (tab == null || tab.isEnteringReaderMode()) {
             return;
@@ -953,47 +747,27 @@ public class BrowserToolbar extends Geck
                 builder.setSpan(tab.isPrivate() ? mPrivateDomainColor : mDomainColor, index, index+baseDomain.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
                 title = builder;
             }
         }
 
         setTitle(title);
     }
 
-    public void showDefaultFavicon() {
-        mFavicon.setImageResource(R.drawable.favicon);
-        mLastFavicon = null;
-    }
-
     private void setFavicon(Bitmap image) {
         Log.d(LOGTAG, "setFavicon(" + image + ")");
         if (Tabs.getInstance().getSelectedTab().getState() == Tab.STATE_LOADING) {
             return;
         }
 
-        if (image == mLastFavicon) {
-            Log.d(LOGTAG, "Ignoring favicon set: new favicon is identical to previous favicon.");
-            return;
-        }
-
-        mLastFavicon = image;     // Cache the original so we can debounce without scaling.
+        mUrlDisplayLayout.setFavicon(image);
+    }
 
-        if (image != null) {
-            image = Bitmap.createScaledBitmap(image, mFaviconSize, mFaviconSize, false);
-            mFavicon.setImageBitmap(image);
-        } else {
-            mFavicon.setImageDrawable(null);
-        }
-    }
-    
     private void setSecurityMode(SecurityMode mode) {
-        mSiteSecurity.setImageLevel(mode.ordinal());
-        mShowSiteSecurity = (mode != SecurityMode.UNKNOWN);
-
-        setPageActionVisibility(mStop.getVisibility() == View.VISIBLE);
+        mUrlDisplayLayout.setSecurityMode(mode);
     }
 
     public void prepareTabsAnimation(PropertyAnimator animator, boolean tabsAreShown) {
         if (!tabsAreShown) {
             PropertyAnimator buttonsAnimator =
                     new PropertyAnimator(animator.getDuration(), sButtonsInterpolator);
 
             buttonsAnimator.attach(mTabsCounter,
@@ -1077,18 +851,18 @@ public class BrowserToolbar extends Geck
         setUrlEditLayoutVisibility(false, null);
     }
 
     private void hideUrlEditLayout(PropertyAnimator animator) {
         setUrlEditLayoutVisibility(false, animator);
     }
 
     private void setUrlEditLayoutVisibility(final boolean showEditLayout, PropertyAnimator animator) {
-        final View viewToShow = (showEditLayout ? mUrlEditLayout : mUrlDisplayContainer);
-        final View viewToHide = (showEditLayout ? mUrlDisplayContainer : mUrlEditLayout);
+        final View viewToShow = (showEditLayout ? mUrlEditLayout : mUrlDisplayLayout);
+        final View viewToHide = (showEditLayout ? mUrlDisplayLayout : mUrlEditLayout);
 
         if (showEditLayout) {
             mUrlEditLayout.prepareShowAnimation(animator);
         }
 
         if (animator == null) {
             viewToHide.setVisibility(View.GONE);
             viewToShow.setVisibility(View.VISIBLE);
@@ -1219,19 +993,17 @@ public class BrowserToolbar extends Geck
         }
 
         if (mAnimatingEntry)
             return;
 
         // Highlight the toolbar from the start of the animation.
         setSelected(true);
 
-        // Hide page actions/stop buttons immediately
-        ViewHelper.setAlpha(mPageActionLayout, 0);
-        ViewHelper.setAlpha(mStop, 0);
+        mUrlDisplayLayout.prepareStartEditingAnimation();
 
         // Slide the right side elements of the toolbar
 
         if (mUrlBarRightEdge != null) {
             animator.attach(mUrlBarRightEdge,
                             PropertyAnimator.Property.TRANSLATION_X,
                             entryTranslation);
         }
@@ -1370,26 +1142,17 @@ public class BrowserToolbar extends Geck
 
             @Override
             public void onPropertyAnimationEnd() {
                 if (mUrlBarRightEdge != null) {
                     mUrlBarRightEdge.setVisibility(View.INVISIBLE);
                 }
 
                 PropertyAnimator buttonsAnimator = new PropertyAnimator(300);
-
-                // Fade toolbar buttons (page actions, stop) after the entry
-                // is schrunk back to its original size.
-                buttonsAnimator.attach(mPageActionLayout,
-                                       PropertyAnimator.Property.ALPHA,
-                                       1);
-                buttonsAnimator.attach(mStop,
-                                       PropertyAnimator.Property.ALPHA,
-                                       1);
-
+                mUrlDisplayLayout.prepareStopEditingAnimation(buttonsAnimator);
                 buttonsAnimator.start();
 
                 mAnimatingEntry = false;
 
                 // Trigger animation to update the tabs counter once the
                 // tabs button is back on screen.
                 updateTabCountAndAnimate(Tabs.getInstance().getDisplayCount());
             }
@@ -1429,27 +1192,28 @@ public class BrowserToolbar extends Geck
         // been animated to be visible¬ł and vice-versa.
         MarginLayoutParams fwdParams = (MarginLayoutParams) mForward.getLayoutParams();
         if ((fwdParams.leftMargin > mDefaultForwardMargin && showing) ||
             (fwdParams.leftMargin == mDefaultForwardMargin && !showing)) {
             return;
         }
 
         // We want the forward button to show immediately when switching tabs
-        mForwardAnim = new PropertyAnimator(mSwitchingTabs ? 10 : FORWARD_ANIMATION_DURATION);
+        final PropertyAnimator forwardAnim =
+                new PropertyAnimator(mSwitchingTabs ? 10 : FORWARD_ANIMATION_DURATION);
         final int width = mForward.getWidth() / 2;
 
-        mForwardAnim.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
+        forwardAnim.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
             @Override
             public void onPropertyAnimationStart() {
                 if (!showing) {
                     // Set the margin before the transition when hiding the forward button. We
                     // have to do this so that the favicon isn't clipped during the transition
                     MarginLayoutParams layoutParams =
-                        (MarginLayoutParams) mUrlDisplayContainer.getLayoutParams();
+                        (MarginLayoutParams) mUrlDisplayLayout.getLayoutParams();
                     layoutParams.leftMargin = 0;
 
                     // Do the same on the URL edit container
                     layoutParams = (MarginLayoutParams) mUrlEditLayout.getLayoutParams();
                     layoutParams.leftMargin = 0;
 
                     requestLayout();
                     // Note, we already translated the favicon, site security, and text field
@@ -1457,38 +1221,35 @@ public class BrowserToolbar extends Geck
                     // all at this point.
                 }
             }
 
             @Override
             public void onPropertyAnimationEnd() {
                 if (showing) {
                     MarginLayoutParams layoutParams =
-                        (MarginLayoutParams) mUrlDisplayContainer.getLayoutParams();
+                        (MarginLayoutParams) mUrlDisplayLayout.getLayoutParams();
                     layoutParams.leftMargin = mUrlBarViewOffset;
 
                     layoutParams = (MarginLayoutParams) mUrlEditLayout.getLayoutParams();
                     layoutParams.leftMargin = mUrlBarViewOffset;
+                }
 
-                    ViewHelper.setTranslationX(mTitle, 0);
-                    ViewHelper.setTranslationX(mFavicon, 0);
-                    ViewHelper.setTranslationX(mSiteSecurity, 0);
-                }
+                mUrlDisplayLayout.finishForwardAnimation();
 
                 MarginLayoutParams layoutParams = (MarginLayoutParams) mForward.getLayoutParams();
                 layoutParams.leftMargin = mDefaultForwardMargin + (showing ? width : 0);
                 ViewHelper.setTranslationX(mForward, 0);
 
                 requestLayout();
-                mForwardAnim = null;
             }
         });
 
-        prepareForwardAnimation(mForwardAnim, animation, width);
-        mForwardAnim.start();
+        prepareForwardAnimation(forwardAnim, animation, width);
+        forwardAnim.start();
     }
 
     public void updateForwardButton(Tab tab) {
         final boolean enabled = canDoForward(tab);
         if (mForward.isEnabled() == enabled)
             return;
 
         // Save the state on the forward button so that we can skip animations
@@ -1500,49 +1261,27 @@ public class BrowserToolbar extends Geck
     private void prepareForwardAnimation(PropertyAnimator anim, ForwardButtonAnimation animation, int width) {
         if (animation == ForwardButtonAnimation.HIDE) {
             anim.attach(mForward,
                       PropertyAnimator.Property.TRANSLATION_X,
                       -width);
             anim.attach(mForward,
                       PropertyAnimator.Property.ALPHA,
                       0);
-            anim.attach(mTitle,
-                      PropertyAnimator.Property.TRANSLATION_X,
-                      0);
-            anim.attach(mFavicon,
-                      PropertyAnimator.Property.TRANSLATION_X,
-                      0);
-            anim.attach(mSiteSecurity,
-                      PropertyAnimator.Property.TRANSLATION_X,
-                      0);
 
-            // We're hiding the forward button. We're going to reset the margin before
-            // the animation starts, so we shift these items to the right so that they don't
-            // appear to move initially.
-            ViewHelper.setTranslationX(mTitle, mUrlBarViewOffset);
-            ViewHelper.setTranslationX(mFavicon, mUrlBarViewOffset);
-            ViewHelper.setTranslationX(mSiteSecurity, mUrlBarViewOffset);
         } else {
             anim.attach(mForward,
                       PropertyAnimator.Property.TRANSLATION_X,
                       width);
             anim.attach(mForward,
                       PropertyAnimator.Property.ALPHA,
                       1);
-            anim.attach(mTitle,
-                      PropertyAnimator.Property.TRANSLATION_X,
-                      mUrlBarViewOffset);
-            anim.attach(mFavicon,
-                      PropertyAnimator.Property.TRANSLATION_X,
-                      mUrlBarViewOffset);
-            anim.attach(mSiteSecurity,
-                      PropertyAnimator.Property.TRANSLATION_X,
-                      mUrlBarViewOffset);
         }
+
+        mUrlDisplayLayout.prepareForwardAnimation(anim, animation, width);
     }
 
     @Override
     public boolean addActionItem(View actionItem) {
         mActionItemBar.addView(actionItem);
         return true;
     }
 
@@ -1560,38 +1299,38 @@ public class BrowserToolbar extends Geck
     }
 
     private void refreshState() {
         Tab tab = Tabs.getInstance().getSelectedTab();
         if (tab != null) {
             setFavicon(tab.getFavicon());
             setProgressVisibility(tab.getState() == Tab.STATE_LOADING);
             setSecurityMode(tab.getSecurityMode());
-            setPageActionVisibility(mStop.getVisibility() == View.VISIBLE);
+            setPageActionVisibility(mUrlDisplayLayout.isShowingProgress());
             updateBackButton(tab);
             updateForwardButton(tab);
 
             final boolean isPrivate = tab.isPrivate();
             setPrivateMode(isPrivate);
             mTabs.setPrivateMode(isPrivate);
-            mTitle.setPrivateMode(isPrivate);
             mMenu.setPrivateMode(isPrivate);
             mMenuIcon.setPrivateMode(isPrivate);
+            mUrlDisplayLayout.setPrivateMode(isPrivate);
             mUrlEditLayout.setPrivateMode(isPrivate);
 
             if (mBack instanceof BackButton)
                 ((BackButton) mBack).setPrivateMode(isPrivate);
 
             if (mForward instanceof ForwardButton)
                 ((ForwardButton) mForward).setPrivateMode(isPrivate);
         }
     }
 
     public View getDoorHangerAnchor() {
-        return mFavicon;
+        return mUrlDisplayLayout.getDoorHangerAnchor();
     }
 
     public void onDestroy() {
         if (mPrefObserverId != null) {
              PrefsHelper.removeObserver(mPrefObserverId);
              mPrefObserverId = null;
         }
         Tabs.unregisterOnTabsChangedListener(this);
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/toolbar/ToolbarDisplayLayout.java
@@ -0,0 +1,400 @@
+/* -*- 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 org.mozilla.gecko.animation.PropertyAnimator;
+import org.mozilla.gecko.animation.ViewHelper;
+import org.mozilla.gecko.BrowserApp;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.SiteIdentity;
+import org.mozilla.gecko.SiteIdentity.SecurityMode;
+import org.mozilla.gecko.Tab;
+import org.mozilla.gecko.Tabs;
+import org.mozilla.gecko.toolbar.BrowserToolbar.ForwardButtonAnimation;
+import org.mozilla.gecko.widget.GeckoLinearLayout;
+import org.mozilla.gecko.widget.GeckoTextView;
+
+import org.json.JSONObject;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.os.Build;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.TranslateAnimation;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.LinearLayout.LayoutParams;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class ToolbarDisplayLayout extends GeckoLinearLayout
+                                  implements Animation.AnimationListener {
+
+    private static final String LOGTAG = "GeckoToolbarDisplayLayout";
+
+    final private BrowserApp mActivity;
+
+    private GeckoTextView mTitle;
+    private int mTitlePadding;
+
+    private ImageButton mSiteSecurity;
+    private boolean mSiteSecurityVisible;
+    private boolean mShowSiteSecurity;
+
+    // To de-bounce sets.
+    private Bitmap mLastFavicon;
+    private ImageButton mFavicon;
+    private int mFaviconSize;
+
+    private ImageButton mStop;
+    private PageActionLayout mPageActionLayout;
+
+    private Animation mProgressSpinner;
+    private boolean mSpinnerVisible;
+
+    private AlphaAnimation mLockFadeIn;
+    private TranslateAnimation mTitleSlideLeft;
+    private TranslateAnimation mTitleSlideRight;
+
+    private SiteIdentityPopup mSiteIdentityPopup;
+
+    private PropertyAnimator mForwardAnim;
+
+    public ToolbarDisplayLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setOrientation(HORIZONTAL);
+
+        mActivity = (BrowserApp) context;
+
+        LayoutInflater.from(context).inflate(R.layout.toolbar_display_layout, this);
+
+        mTitle = (GeckoTextView) findViewById(R.id.url_bar_title);
+        mTitlePadding = mTitle.getPaddingRight();
+
+        Resources res = getResources();
+
+        mFavicon = (ImageButton) findViewById(R.id.favicon);
+        if (Build.VERSION.SDK_INT >= 11) {
+            if (Build.VERSION.SDK_INT >= 16) {
+                mFavicon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+            }
+            mFavicon.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+        }
+        mFaviconSize = Math.round(res.getDimension(R.dimen.browser_toolbar_favicon_size));
+
+        mShowSiteSecurity = false;
+
+        mSiteSecurity = (ImageButton) findViewById(R.id.site_security);
+        mSiteSecurityVisible = (mSiteSecurity.getVisibility() == View.VISIBLE);
+
+        mSiteIdentityPopup = new SiteIdentityPopup(mActivity);
+        mSiteIdentityPopup.setAnchor(mSiteSecurity);
+
+        mProgressSpinner = AnimationUtils.loadAnimation(mActivity, R.anim.progress_spinner);
+
+        mStop = (ImageButton) findViewById(R.id.stop);
+        mPageActionLayout = (PageActionLayout) findViewById(R.id.page_action_layout);
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        Button.OnClickListener faviconListener = new Button.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                if (mSiteSecurity.getVisibility() != View.VISIBLE)
+                    return;
+
+                final Tab tab = Tabs.getInstance().getSelectedTab();
+
+                final SiteIdentity siteIdentity = tab.getSiteIdentity();
+                if (siteIdentity.getSecurityMode() == SecurityMode.UNKNOWN) {
+                    Log.e(LOGTAG, "Selected tab has no identity data");
+                    return;
+                }
+
+                mSiteIdentityPopup.updateIdentity(siteIdentity);
+                mSiteIdentityPopup.show();
+            }
+        };
+
+        mFavicon.setOnClickListener(faviconListener);
+        mSiteSecurity.setOnClickListener(faviconListener);
+
+        mStop.setOnClickListener(new Button.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Tab tab = Tabs.getInstance().getSelectedTab();
+                if (tab != null)
+                    tab.doStop();
+                setProgressVisibility(false);
+            }
+        });
+
+        float slideWidth = getResources().getDimension(R.dimen.browser_toolbar_lock_width);
+
+        LayoutParams siteSecParams = (LayoutParams) mSiteSecurity.getLayoutParams();
+        final float scale = getResources().getDisplayMetrics().density;
+        slideWidth += (siteSecParams.leftMargin + siteSecParams.rightMargin) * scale + 0.5f;
+
+        mLockFadeIn = new AlphaAnimation(0.0f, 1.0f);
+        mLockFadeIn.setAnimationListener(this);
+
+        mTitleSlideLeft = new TranslateAnimation(slideWidth, 0, 0, 0);
+        mTitleSlideLeft.setAnimationListener(this);
+
+        mTitleSlideRight = new TranslateAnimation(-slideWidth, 0, 0, 0);
+        mTitleSlideRight.setAnimationListener(this);
+
+        final int lockAnimDuration = 300;
+        mLockFadeIn.setDuration(lockAnimDuration);
+        mTitleSlideLeft.setDuration(lockAnimDuration);
+        mTitleSlideRight.setDuration(lockAnimDuration);
+    }
+
+    @Override
+    public void setPrivateMode(boolean isPrivate) {
+        super.setPrivateMode(isPrivate);
+        mTitle.setPrivateMode(isPrivate);
+    }
+
+    @Override
+    public void onAnimationStart(Animation animation) {
+        if (animation.equals(mLockFadeIn)) {
+            if (mSiteSecurityVisible)
+                mSiteSecurity.setVisibility(View.VISIBLE);
+        } else if (animation.equals(mTitleSlideLeft)) {
+            // These two animations may be scheduled to start while the forward
+            // animation is occurring. If we're showing the site security icon, make
+            // sure it doesn't take any space during the forward transition.
+            mSiteSecurity.setVisibility(View.GONE);
+        } else if (animation.equals(mTitleSlideRight)) {
+            // If we're hiding the icon, make sure that we keep its padding
+            // in place during the forward transition
+            mSiteSecurity.setVisibility(View.INVISIBLE);
+        }
+    }
+
+    @Override
+    public void onAnimationRepeat(Animation animation) {
+    }
+
+    @Override
+    public void onAnimationEnd(Animation animation) {
+        if (animation.equals(mTitleSlideRight)) {
+            mSiteSecurity.startAnimation(mLockFadeIn);
+        }
+    }
+
+    @Override
+    public void setNextFocusDownId(int nextId) {
+        mFavicon.setNextFocusDownId(nextId);
+        mStop.setNextFocusDownId(nextId);
+        mSiteSecurity.setNextFocusDownId(nextId);
+        mPageActionLayout.setNextFocusDownId(nextId);
+    }
+
+    void updateFromTab(Tab tab) {
+    }
+
+    List<View> getFocusOrder() {
+        return Arrays.asList(mSiteSecurity, mPageActionLayout, mStop);
+    }
+
+    void setTitle(CharSequence title) {
+        mTitle.setText(title);
+    }
+
+    void setProgressVisibility(boolean visible) {
+        Log.d(LOGTAG, "setProgressVisibility: " + visible);
+
+        // The "Throbber start" and "Throbber stop" log messages in this method
+        // are needed by S1/S2 tests (http://mrcote.info/phonedash/#).
+        // See discussion in Bug 804457. Bug 805124 tracks paring these down.
+        if (visible) {
+            mFavicon.setImageResource(R.drawable.progress_spinner);
+            mLastFavicon = null;
+
+            //To stop the glitch caused by mutiple start() calls.
+            if (!mSpinnerVisible) {
+                setPageActionVisibility(true);
+                mFavicon.setAnimation(mProgressSpinner);
+                mProgressSpinner.start();
+                mSpinnerVisible = true;
+            }
+            Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - Throbber start");
+        } else {
+            Tab selectedTab = Tabs.getInstance().getSelectedTab();
+            if (selectedTab != null)
+                setFavicon(selectedTab.getFavicon());
+
+            if (mSpinnerVisible) {
+                setPageActionVisibility(false);
+                mFavicon.setAnimation(null);
+                mProgressSpinner.cancel();
+                mSpinnerVisible = false;
+            }
+            Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - Throbber stop");
+        }
+    }
+
+    void setFavicon(Bitmap image) {
+        if (image == mLastFavicon) {
+            Log.d(LOGTAG, "Ignoring favicon set: new favicon is identical to previous favicon.");
+            return;
+        }
+
+        mLastFavicon = image;     // Cache the original so we can debounce without scaling.
+
+        if (image != null) {
+            image = Bitmap.createScaledBitmap(image, mFaviconSize, mFaviconSize, false);
+            mFavicon.setImageBitmap(image);
+        } else {
+            mFavicon.setImageDrawable(null);
+        }
+    }
+
+    private void setSiteSecurityVisibility(final boolean visible, boolean animate) {
+        if (visible == mSiteSecurityVisible)
+            return;
+
+        mSiteSecurityVisible = visible;
+
+        if (animate) {
+            mSiteSecurity.setVisibility(visible ? View.VISIBLE : View.GONE);
+            return;
+        }
+
+        mTitle.clearAnimation();
+        mSiteSecurity.clearAnimation();
+
+        // If any of these animations were cancelled as a result of the
+        // clearAnimation() calls above, we need to reset them.
+        mLockFadeIn.reset();
+        mTitleSlideLeft.reset();
+        mTitleSlideRight.reset();
+
+        if (mForwardAnim != null) {
+            long delay = mForwardAnim.getRemainingTime();
+            mTitleSlideRight.setStartOffset(delay);
+            mTitleSlideLeft.setStartOffset(delay);
+        } else {
+            mTitleSlideRight.setStartOffset(0);
+            mTitleSlideLeft.setStartOffset(0);
+        }
+
+        mTitle.startAnimation(visible ? mTitleSlideRight : mTitleSlideLeft);
+    }
+
+    void setPageActionVisibility(boolean isLoading) {
+        setPageActionVisibility(isLoading, true);
+    }
+
+    void setPageActionVisibility(boolean isLoading, boolean animate) {
+        // Handle the loading mode page actions
+        mStop.setVisibility(isLoading ? View.VISIBLE : View.GONE);
+
+        // Handle the viewing mode page actions
+        setSiteSecurityVisibility(mShowSiteSecurity && !isLoading, animate);
+        mPageActionLayout.setVisibility(!isLoading ? View.VISIBLE : View.GONE);
+
+        // We want title to fill the whole space available for it when there are icons
+        // being shown on the right side of the toolbar as the icons already have some
+        // padding in them. This is just to avoid wasting space when icons are shown.
+        mTitle.setPadding(0, 0, (!isLoading ? mTitlePadding : 0), 0);
+    }
+
+    void setSecurityMode(SecurityMode mode) {
+        mSiteSecurity.setImageLevel(mode.ordinal());
+        mShowSiteSecurity = (mode != SecurityMode.UNKNOWN);
+
+        setPageActionVisibility(mStop.getVisibility() == View.VISIBLE);
+    }
+
+    boolean isShowingProgress() {
+        return mSpinnerVisible;
+    }
+
+    View getDoorHangerAnchor() {
+        return mFavicon;
+    }
+
+    void prepareForwardAnimation(PropertyAnimator anim, ForwardButtonAnimation animation, int width) {
+        mForwardAnim = anim;
+
+        if (animation == ForwardButtonAnimation.HIDE) {
+            anim.attach(mTitle,
+                        PropertyAnimator.Property.TRANSLATION_X,
+                        0);
+            anim.attach(mFavicon,
+                        PropertyAnimator.Property.TRANSLATION_X,
+                        0);
+            anim.attach(mSiteSecurity,
+                        PropertyAnimator.Property.TRANSLATION_X,
+                        0);
+
+            // We're hiding the forward button. We're going to reset the margin before
+            // the animation starts, so we shift these items to the right so that they don't
+            // appear to move initially.
+            ViewHelper.setTranslationX(mTitle, width);
+            ViewHelper.setTranslationX(mFavicon, width);
+            ViewHelper.setTranslationX(mSiteSecurity, width);
+        } else {
+            anim.attach(mTitle,
+                        PropertyAnimator.Property.TRANSLATION_X,
+                        width);
+            anim.attach(mFavicon,
+                        PropertyAnimator.Property.TRANSLATION_X,
+                        width);
+            anim.attach(mSiteSecurity,
+                        PropertyAnimator.Property.TRANSLATION_X,
+                        width);
+        }
+    }
+
+    void finishForwardAnimation() {
+        ViewHelper.setTranslationX(mTitle, 0);
+        ViewHelper.setTranslationX(mFavicon, 0);
+        ViewHelper.setTranslationX(mSiteSecurity, 0);
+
+        mForwardAnim = null;
+    }
+
+    void prepareStartEditingAnimation() {
+        // Hide page actions/stop buttons immediately
+        ViewHelper.setAlpha(mPageActionLayout, 0);
+        ViewHelper.setAlpha(mStop, 0);
+    }
+
+    void prepareStopEditingAnimation(PropertyAnimator anim) {
+        // Fade toolbar buttons (page actions, stop) after the entry
+        // is schrunk back to its original size.
+        anim.attach(mPageActionLayout,
+                    PropertyAnimator.Property.ALPHA,
+                    1);
+
+        anim.attach(mStop,
+                    PropertyAnimator.Property.ALPHA,
+                    1);
+    }
+
+    boolean dismissSiteIdentityPopup() {
+        if (mSiteIdentityPopup != null && mSiteIdentityPopup.isShowing()) {
+            mSiteIdentityPopup.dismiss();
+            return true;
+        }
+
+        return false;
+    }
+}
\ No newline at end of file