Bug 767980 - Reimplement PropertyAnimator in terms of view proxies (r=mfinkle)
authorLucas Rocha <lucasr@mozilla.com>
Fri, 12 Oct 2012 12:57:07 +0100
changeset 110199 9990a1074771f94c9f8a31e890022725f23ae002
parent 110198 913e25c339f80428031686c4d33000182c8e6104
child 110200 eeba6e1170813924da9464290b2f3900262e0542
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
reviewersmfinkle
bugs767980
milestone19.0a1
Bug 767980 - Reimplement PropertyAnimator in terms of view proxies (r=mfinkle)
mobile/android/base/AnimatorProxy.java
mobile/android/base/BrowserApp.java
mobile/android/base/BrowserToolbar.java
mobile/android/base/GeckoApp.java
mobile/android/base/Makefile.in
mobile/android/base/PropertyAnimator.java
mobile/android/base/TabsTray.java
mobile/android/base/gfx/LayerView.java
mobile/android/base/resources/layout-xlarge-v11/browser_toolbar_menu.xml.in
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/AnimatorProxy.java
@@ -0,0 +1,303 @@
+/* -*- 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;
+
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.os.Build;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Transformation;
+
+import java.lang.ref.WeakReference;
+import java.util.WeakHashMap;
+
+public class AnimatorProxy {
+    private static final WeakHashMap<View, AnimatorProxy> PROXIES =
+            new WeakHashMap<View, AnimatorProxy>();
+
+    private static interface AnimatorProxyImpl {
+        public int getScrollX();
+        public int getScrollY();
+        public void scrollTo(int scrollX, int scrollY);
+
+        public float getTranslationX();
+        public void setTranslationX(float translationX);
+
+        public float getTranslationY();
+        public void setTranslationY(float translationY);
+    }
+
+    private AnimatorProxyImpl mImpl;
+
+    private AnimatorProxy(AnimatorProxyImpl impl) {
+        mImpl = impl;
+    }
+
+    public static AnimatorProxy create(View view) {
+        AnimatorProxy proxy = PROXIES.get(view);
+        boolean needsAnimationProxy = (Build.VERSION.SDK_INT < 11);
+
+        // If the view's animation proxy has been overridden from somewhere else, we need to
+        // create a new AnimatorProxy for the view.
+        if (proxy == null || (needsAnimationProxy && proxy.mImpl != view.getAnimation())) {
+            AnimatorProxyImpl impl = (needsAnimationProxy ? new AnimatorProxyPreHC(view) :
+                                                            new AnimatorProxyPostHC(view));
+
+            proxy = new AnimatorProxy(impl);
+            PROXIES.put(view, proxy);
+        }
+
+        return proxy;
+    }
+
+    public int getScrollX() {
+        return mImpl.getScrollX();
+    }
+
+    public int getScrollY() {
+        return mImpl.getScrollY();
+    }
+
+    public void scrollTo(int scrollX, int scrollY) {
+        mImpl.scrollTo(scrollX, scrollY);
+    }
+
+    public float getTranslationX() {
+        return mImpl.getTranslationX();
+    }
+
+    public void setTranslationX(float translationX) {
+        mImpl.setTranslationX(translationX);
+    }
+
+    public float getTranslationY() {
+        return mImpl.getTranslationY();
+    }
+
+    public void setTranslationY(float translationY) {
+        mImpl.setTranslationY(translationY);
+    }
+
+    /*
+     * AnimatorProxyPreHC uses the technique used by the NineOldAndroids described here:
+     * http://jakewharton.com/advanced-pre-honeycomb-animation/
+     *
+     * Some of this code is based on Jake Wharton's AnimatorProxy released as part of
+     * the NineOldAndroids library under the Apache License 2.0.
+     */
+    private static class AnimatorProxyPreHC extends Animation implements AnimatorProxyImpl {
+        private WeakReference<View> mViewRef;
+
+        private final RectF mBefore;
+        private final RectF mAfter;
+        private final Matrix mTempMatrix;
+
+        private float mTranslationX;
+        private float mTranslationY;
+
+        public AnimatorProxyPreHC(View view) {
+            mBefore = new RectF();
+            mAfter = new RectF();
+            mTempMatrix = new Matrix();
+
+            mTranslationX = 0;
+            mTranslationY = 0;
+
+            loadCurrentTransformation(view);
+
+            setDuration(0);
+            setFillAfter(true);
+            view.setAnimation(this);
+
+            mViewRef = new WeakReference<View>(view);
+        }
+
+        private void loadCurrentTransformation(View view) {
+            Animation animation = view.getAnimation();
+            if (animation == null)
+                return;
+
+            Transformation transformation = new Transformation();
+            float[] matrix = new float[9];
+
+            animation.getTransformation(AnimationUtils.currentAnimationTimeMillis(), transformation);
+            transformation.getMatrix().getValues(matrix);
+
+            mTranslationX = matrix[Matrix.MTRANS_X];
+            mTranslationY = matrix[Matrix.MTRANS_Y];
+        }
+
+        private void prepareForUpdate() {
+            View view = mViewRef.get();
+            if (view != null)
+                computeRect(mBefore, view);
+        }
+
+        private void computeRect(final RectF r, View view) {
+            final float w = view.getWidth();
+            final float h = view.getHeight();
+
+            r.set(0, 0, w, h);
+
+            final Matrix m = mTempMatrix;
+            m.reset();
+            transformMatrix(m, view);
+            mTempMatrix.mapRect(r);
+
+            r.offset(view.getLeft(), view.getTop());
+        }
+
+        private void transformMatrix(Matrix m, View view) {
+            m.postTranslate(mTranslationX, mTranslationY);
+        }
+
+        private void invalidateAfterUpdate() {
+            View view = mViewRef.get();
+            if (view == null || view.getParent() == null)
+                return;
+
+            final RectF after = mAfter;
+            computeRect(after, view);
+            after.union(mBefore);
+
+            ((View)view.getParent()).invalidate(
+                    (int) Math.floor(after.left),
+                    (int) Math.floor(after.top),
+                    (int) Math.ceil(after.right),
+                    (int) Math.ceil(after.bottom));
+        }
+
+        @Override
+        public int getScrollX() {
+            View view = mViewRef.get();
+            if (view != null)
+                return view.getScrollX();
+
+            return 0;
+        }
+
+        @Override
+        public int getScrollY() {
+            View view = mViewRef.get();
+            if (view != null)
+                return view.getScrollY();
+
+            return 0;
+        }
+
+        @Override
+        public void scrollTo(int scrollX, int scrollY) {
+            View view = mViewRef.get();
+            if (view != null)
+                view.scrollTo(scrollX, scrollY);
+        }
+
+        @Override
+        public float getTranslationX() {
+            return mTranslationX;
+        }
+
+        @Override
+        public void setTranslationX(float translationX) {
+            if (mTranslationX == translationX)
+                return;
+
+            prepareForUpdate();
+            mTranslationX = translationX;
+            invalidateAfterUpdate();
+        }
+
+        @Override
+        public float getTranslationY() {
+            return mTranslationY;
+        }
+
+        @Override
+        public void setTranslationY(float translationY) {
+            if (mTranslationY == translationY)
+                return;
+
+            prepareForUpdate();
+            mTranslationY = translationY;
+            invalidateAfterUpdate();
+        }
+
+        @Override
+        protected void applyTransformation(float interpolatedTime, Transformation t) {
+            View view = mViewRef.get();
+            if (view != null)
+                transformMatrix(t.getMatrix(), view);
+        }
+    }
+
+    private static class AnimatorProxyPostHC implements AnimatorProxyImpl {
+        private WeakReference<View> mViewRef;
+
+        public AnimatorProxyPostHC(View view) {
+            mViewRef = new WeakReference<View>(view);
+        }
+
+        @Override
+        public int getScrollX() {
+            View view = mViewRef.get();
+            if (view != null)
+                return view.getScrollX();
+
+            return 0;
+        }
+
+        @Override
+        public int getScrollY() {
+            View view = mViewRef.get();
+            if (view != null)
+                return view.getScrollY();
+
+            return 0;
+        }
+
+        @Override
+        public void scrollTo(int scrollX, int scrollY) {
+            View view = mViewRef.get();
+            if (view != null)
+                view.scrollTo(scrollX, scrollY);
+        }
+
+        @Override
+        public float getTranslationX() {
+            View view = mViewRef.get();
+            if (view != null)
+                return view.getTranslationX();
+
+            return 0;
+        }
+
+        @Override
+        public void setTranslationX(float translationX) {
+            View view = mViewRef.get();
+            if (view != null)
+                view.setTranslationX(translationX);
+        }
+
+        @Override
+        public float getTranslationY() {
+            View view = mViewRef.get();
+            if (view != null)
+                return view.getTranslationY();
+
+            return 0;
+        }
+
+        @Override
+        public void setTranslationY(float translationY) {
+            View view = mViewRef.get();
+            if (view != null)
+                view.setTranslationY(translationY);
+        }
+    }
+}
+
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -506,67 +506,79 @@ abstract public class BrowserApp extends
             mMainLayoutAnimator.stop();
 
         if (mTabsPanel.isShown())
             mTabsPanel.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
 
         mMainLayoutAnimator = new PropertyAnimator(450, sTabsInterpolator);
         mMainLayoutAnimator.setPropertyAnimationListener(this);
 
+        boolean usingTextureView = mLayerView.shouldUseTextureView();
+        mMainLayoutAnimator.setUseHardwareLayer(usingTextureView);
+
         if (hasTabsSideBar()) {
-            mMainLayoutAnimator.attach(mBrowserToolbar.getLayout(),
-                                       PropertyAnimator.Property.SHRINK_LEFT,
-                                       width);
+            mBrowserToolbar.prepareTabsAnimation(mMainLayoutAnimator, width);
 
             // Set the gecko layout for sliding.
             if (!mTabsPanel.isShown()) {
                 ((LinearLayout.LayoutParams) mGeckoLayout.getLayoutParams()).setMargins(0, 0, 0, 0);
-                mGeckoLayout.scrollTo(mTabsPanel.getWidth() * -1, 0);
+                if (!usingTextureView)
+                    mGeckoLayout.scrollTo(mTabsPanel.getWidth() * -1, 0);
                 mGeckoLayout.requestLayout();
             }
 
             mMainLayoutAnimator.attach(mGeckoLayout,
-                                       PropertyAnimator.Property.SLIDE_LEFT,
-                                       width);
-
+                                       usingTextureView ? PropertyAnimator.Property.TRANSLATION_X :
+                                                          PropertyAnimator.Property.SCROLL_X,
+                                       usingTextureView ? width : -width);
         } else {
             mMainLayoutAnimator.attach(mMainLayout,
-                                       PropertyAnimator.Property.SLIDE_TOP,
-                                       height);
+                                       usingTextureView ? PropertyAnimator.Property.TRANSLATION_Y :
+                                                          PropertyAnimator.Property.SCROLL_Y,
+                                       usingTextureView ? height : -height);
         }
 
         mMainLayoutAnimator.start();
     }
 
     @Override
     public void onPropertyAnimationStart() {
-        mMainHandler.post(new Runnable() {
-            public void run() {
-                mBrowserToolbar.updateTabs(true);
-            }
-        });
+        mBrowserToolbar.updateTabs(true);
+
+        // Although the tabs panel is not animating per se, it will be re-drawn several
+        // times while the main/gecko layout slides to left/top. Adding a hardware layer
+        // here considerably improves the frame rate of the animation.
+        if (Build.VERSION.SDK_INT >= 11)
+            mTabsPanel.setLayerType(View.LAYER_TYPE_HARDWARE, null);
     }
 
     @Override
     public void onPropertyAnimationEnd() {
-        mMainHandler.post(new Runnable() {
-            public void run() {
-                if (hasTabsSideBar() && mTabsPanel.isShown()) {
-                    // Fake the gecko layout to have been shrunk, instead of sliding.
-                    ((LinearLayout.LayoutParams) mGeckoLayout.getLayoutParams()).setMargins(mTabsPanel.getWidth(), 0, 0, 0);
-                    mGeckoLayout.scrollTo(0, 0);
-                    mGeckoLayout.requestLayout();
-                }
+        // Destroy the hardware layer used during the animation
+        if (Build.VERSION.SDK_INT >= 11)
+            mTabsPanel.setLayerType(View.LAYER_TYPE_NONE, null);
+
+        if (hasTabsSideBar() && mTabsPanel.isShown()) {
+            boolean usingTextureView = mLayerView.shouldUseTextureView();
+
+            int leftMargin = (usingTextureView ? 0 : mTabsPanel.getWidth());
+            int rightMargin = (usingTextureView ? mTabsPanel.getWidth() : 0);
+            ((LinearLayout.LayoutParams) mGeckoLayout.getLayoutParams()).setMargins(leftMargin, 0, rightMargin, 0);
 
-                if (!mTabsPanel.isShown()) {
-                    mBrowserToolbar.updateTabs(false);
-                    mTabsPanel.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
-                }
-            }
-        });
+            if (!usingTextureView)
+                mGeckoLayout.scrollTo(0, 0);
+
+            mGeckoLayout.requestLayout();
+        }
+
+        if (!mTabsPanel.isShown()) {
+            mBrowserToolbar.updateTabs(false);
+            mBrowserToolbar.finishTabsAnimation();
+            mTabsPanel.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+        }
     }
 
     /* Favicon methods */
     private void loadFavicon(final Tab tab) {
         maybeCancelFaviconLoad(tab);
 
         long id = getFavicons().loadFavicon(tab.getURL(), tab.getFaviconURL(), !tab.isPrivate(),
                         new Favicons.OnFaviconLoadedListener() {
--- a/mobile/android/base/BrowserToolbar.java
+++ b/mobile/android/base/BrowserToolbar.java
@@ -46,22 +46,25 @@ import java.util.Arrays;
 import java.util.List;
 
 public class BrowserToolbar implements ViewSwitcher.ViewFactory,
                                        Tabs.OnTabsChangedListener,
                                        GeckoMenu.ActionItemBarPresenter,
                                        Animation.AnimationListener {
     private static final String LOGTAG = "GeckoToolbar";
     private LinearLayout mLayout;
-    private Button mAwesomeBar;
+    private View mAwesomeBar;
+    private View mAwesomeBarRightEdge;
+    private View mAddressBarBg;
     private TextView mTitle;
     private int mTitlePadding;
     private boolean mSiteSecurityVisible;
     private boolean mAnimateSiteSecurity;
     private ImageButton mTabs;
+    private int mTabsPaneWidth;
     private ImageView mBack;
     private ImageView mForward;
     public ImageButton mFavicon;
     public ImageButton mStop;
     public ImageButton mSiteSecurity;
     public ImageButton mReader;
     private AnimationDrawable mProgressSpinner;
     private TextSwitcher mTabsCount;
@@ -107,22 +110,39 @@ public class BrowserToolbar implements V
     }
 
     public void from(LinearLayout layout) {
         mLayout = layout;
 
         mShowSiteSecurity = false;
         mShowReader = false;
 
+        // Only used on tablet layout. We need a separate view for the background
+        // because we need to slide it left/right for hiding/shoing the tabs sidebar
+        // See prepareTabsAnimation().
+        mAddressBarBg = mLayout.findViewById(R.id.address_bar_bg);
+
+        // Only used on tablet layout. The tabs sidebar slide animation is implemented
+        // in terms of translating the inner elements of the tablet toolbar to give the
+        // impression of resizing. In order to do this, This "fake" right edge  is kept
+        // in the same position during the animation while the elements on the left
+        // (favicon, back, forware, lock icon, title, ...) slide behind it.
+        // See prepareTabsAnimation().
+        mAwesomeBarRightEdge = mLayout.findViewById(R.id.awesome_bar_right_edge);
+
+        // This will hold the translation width inside the toolbar when the tabs
+        // pane is visible. It will affect the padding applied to the title TextView.
+        mTabsPaneWidth = 0;
+
         mTitle = (TextView) mLayout.findViewById(R.id.awesome_bar_title);
         mTitlePadding = mTitle.getPaddingRight();
         if (Build.VERSION.SDK_INT >= 16)
             mTitle.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
 
-        mAwesomeBar = (Button) mLayout.findViewById(R.id.awesome_bar);
+        mAwesomeBar = mLayout.findViewById(R.id.awesome_bar);
         mAwesomeBar.setOnClickListener(new Button.OnClickListener() {
             public void onClick(View v) {
                 mActivity.autoHideTabs();
                 onAwesomeBarSearch();
             }
         });
         mAwesomeBar.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
             public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
@@ -455,16 +475,64 @@ public class BrowserToolbar implements V
         mTabsCount.setCurrentText(String.valueOf(count));
         mTabs.setContentDescription((count > 1) ?
                                     mActivity.getString(R.string.num_tabs, count) :
                                     mActivity.getString(R.string.one_tab));
         mCount = count;
         updateTabs(mActivity.areTabsShown());
     }
 
+    public void prepareTabsAnimation(PropertyAnimator animator, int width) {
+        // This is negative before we want to keep the right edge in the same
+        // position while animating the left-most elements below.
+        animator.attach(mAwesomeBarRightEdge,
+                        PropertyAnimator.Property.TRANSLATION_X,
+                        -width);
+
+        animator.attach(mAwesomeBar,
+                        PropertyAnimator.Property.TRANSLATION_X,
+                        width);
+        animator.attach(mAddressBarBg,
+                        PropertyAnimator.Property.TRANSLATION_X,
+                        width);
+        animator.attach(mTabs,
+                        PropertyAnimator.Property.TRANSLATION_X,
+                        width);
+        animator.attach(mTabsCount,
+                        PropertyAnimator.Property.TRANSLATION_X,
+                        width);
+        animator.attach(mBack,
+                        PropertyAnimator.Property.TRANSLATION_X,
+                        width);
+        animator.attach(mForward,
+                        PropertyAnimator.Property.TRANSLATION_X,
+                        width);
+        animator.attach(mTitle,
+                        PropertyAnimator.Property.TRANSLATION_X,
+                        width);
+        animator.attach(mFavicon,
+                        PropertyAnimator.Property.TRANSLATION_X,
+                        width);
+        animator.attach(mSiteSecurity,
+                        PropertyAnimator.Property.TRANSLATION_X,
+                        width);
+
+        mTabsPaneWidth = width;
+
+        // Only update title padding immediatelly when shrinking the browser
+        // toolbar. Leave the padding update to the end of the animation when
+        // expanding (see finishTabsAnimation()).
+        if (mTabsPaneWidth > 0)
+            setPageActionVisibility(mStop.getVisibility() == View.VISIBLE);
+    }
+
+    public void finishTabsAnimation() {
+        setPageActionVisibility(mStop.getVisibility() == View.VISIBLE);
+    }
+
     public void updateTabs(boolean areTabsShown) {
         if (areTabsShown) {
             mTabs.getBackground().setLevel(TABS_EXPANDED);
 
             if (!mActivity.hasTabsSideBar()) {
                 mTabs.setImageLevel(0);
                 mTabsCount.setVisibility(View.GONE);
                 mMenu.setImageLevel(TABS_EXPANDED);
@@ -506,17 +574,17 @@ public class BrowserToolbar implements V
 
         // Handle the viewing mode page actions
         setSiteSecurityVisibility(mShowSiteSecurity && !isLoading);
         mReader.setVisibility(mShowReader && !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, (!mShowReader && !isLoading ? mTitlePadding : 0), 0);
+        mTitle.setPadding(0, 0, (!mShowReader && !isLoading ? mTitlePadding : 0) + mTabsPaneWidth, 0);
 
         updateFocusOrder();
     }
 
     private void setSiteSecurityVisibility(final boolean visible) {
         if (visible == mSiteSecurityVisible)
             return;
 
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -170,17 +170,17 @@ abstract public class GeckoApp
     private PromptService mPromptService;
     private Favicons mFavicons;
     private TextSelection mTextSelection;
 
     protected DoorHangerPopup mDoorHangerPopup;
     protected FormAssistPopup mFormAssistPopup;
     protected TabsPanel mTabsPanel;
 
-    private LayerView mLayerView;
+    protected LayerView mLayerView;
     private AbsoluteLayout mPluginContainer;
 
     private FullScreenHolder mFullScreenPluginContainer;
     private View mFullScreenPluginView;
 
     private HashMap<String, PowerManager.WakeLock> mWakeLocks = new HashMap<String, PowerManager.WakeLock>();
 
     protected int mRestoreMode = GeckoAppShell.RESTORE_NONE;
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -35,16 +35,17 @@ UTIL_JAVA_FILES := \
   $(NULL)
 
 FENNEC_JAVA_FILES = \
   AboutHomePromoBox.java \
   AboutHomeSection.java \
   ActivityHandlerHelper.java \
   AndroidImport.java \
   AndroidImportPreference.java \
+  AnimatorProxy.java \
   AlertNotification.java \
   AnimatedHeightLayout.java \
   AwesomeBar.java \
   AwesomebarResultHandler.java \
   AwesomeBarTabs.java \
   awesomebar/AwesomeBarTab.java \
   awesomebar/AllPagesTab.java \
   awesomebar/BookmarksTab.java \
--- a/mobile/android/base/PropertyAnimator.java
+++ b/mobile/android/base/PropertyAnimator.java
@@ -12,78 +12,73 @@ import android.view.Choreographer;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.AnimationUtils;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Timer;
-import java.util.TimerTask;
 
 public class PropertyAnimator implements Runnable {
     private static final String LOGTAG = "GeckoPropertyAnimator";
 
     public static enum Property {
-        SHRINK_LEFT,
-        SHRINK_TOP,
-        SLIDE_TOP,
-        SLIDE_LEFT
+        TRANSLATION_X,
+        TRANSLATION_Y,
+        SCROLL_X,
+        SCROLL_Y
     }
 
     private class ElementHolder {
         View view;
+        AnimatorProxy proxy;
         Property property;
-        int from;
-        int to;
+        float from;
+        float to;
     }
 
     public static interface PropertyAnimationListener {
         public void onPropertyAnimationStart();
         public void onPropertyAnimationEnd();
     }
 
     private Interpolator mInterpolator;
     private long mStartTime;
     private long mDuration;
     private float mDurationReciprocal;
     private List<ElementHolder> mElementsList;
     private PropertyAnimationListener mListener;
     private FramePoster mFramePoster;
+    private boolean mUseHardwareLayer;
 
     public PropertyAnimator(int duration) {
         this(duration, new DecelerateInterpolator());
     }
 
     public PropertyAnimator(int duration, Interpolator interpolator) {
         mDuration = duration;
         mDurationReciprocal = 1.0f / (float) mDuration;
         mInterpolator = interpolator;
         mElementsList = new ArrayList<ElementHolder>();
         mFramePoster = FramePoster.create(this);
+        mUseHardwareLayer = true;
+    }
+
+    public void setUseHardwareLayer(boolean useHardwareLayer) {
+        mUseHardwareLayer = useHardwareLayer;
     }
 
     public void attach(View view, Property property, int to) {
-        if (!(view instanceof ViewGroup) && (property == Property.SHRINK_LEFT ||
-                                             property == Property.SHRINK_TOP)) {
-            Log.i(LOGTAG, "Margin can only be animated on Viewgroups");
-            return;
-        }
-
         ElementHolder element = new ElementHolder();
 
         element.view = view;
+        element.proxy = AnimatorProxy.create(view);
         element.property = property;
-
-        // Sliding should happen in the negative.
-        if (property == Property.SLIDE_TOP || property == Property.SLIDE_LEFT)
-            element.to = to * -1;
-        else
-            element.to = to;
+        element.to = to;
 
         mElementsList.add(element);
     }
 
     public void setPropertyAnimationListener(PropertyAnimationListener listener) {
         mListener = listener;
     }
 
@@ -93,89 +88,99 @@ public class PropertyAnimator implements
         if (timePassed >= mDuration) {
             stop();
             return;
         }
 
         float interpolation = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
 
         for (ElementHolder element : mElementsList) { 
-            int delta = element.from + (int) ((element.to - element.from) * interpolation);
+            float delta = element.from + ((element.to - element.from) * interpolation);
             invalidate(element, delta);
         }
 
         mFramePoster.postNextAnimationFrame();
     }
 
     public void start() {
         mStartTime = AnimationUtils.currentAnimationTimeMillis();
 
         // Fix the from value based on current position and property
         for (ElementHolder element : mElementsList) {
-            if (element.property == Property.SLIDE_TOP)
-                element.from = element.view.getScrollY();
-            else if (element.property == Property.SLIDE_LEFT)
-                element.from = element.view.getScrollX();
-            else {
-                ViewGroup.MarginLayoutParams params = ((ViewGroup.MarginLayoutParams) element.view.getLayoutParams());
-                if (element.property == Property.SHRINK_TOP)
-                    element.from = params.topMargin;
-                else if (element.property == Property.SHRINK_LEFT)
-                    element.from = params.leftMargin;
-            }
+            if (element.property == Property.TRANSLATION_Y)
+                element.from = element.proxy.getTranslationY();
+            else if (element.property == Property.TRANSLATION_X)
+                element.from = element.proxy.getTranslationX();
+            else if (element.property == Property.SCROLL_Y)
+                element.from = element.proxy.getScrollY();
+            else if (element.property == Property.SCROLL_X)
+                element.from = element.proxy.getScrollX();
+
+            if (shouldEnableHardwareLayer(element))
+                element.view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
         }
 
         if (mDuration != 0) {
             mFramePoster.postFirstAnimationFrame();
 
             if (mListener != null)
                 mListener.onPropertyAnimationStart();
         }
     }
 
     public void stop() {
         mFramePoster.cancelAnimationFrame();
 
         // Make sure to snap to the end position.
         for (ElementHolder element : mElementsList) { 
             invalidate(element, element.to);
+            if (shouldEnableHardwareLayer(element))
+                element.view.setLayerType(View.LAYER_TYPE_NONE, null);
         }
 
         mElementsList.clear();
 
         if (mListener != null) {
             mListener.onPropertyAnimationEnd();
             mListener = null;
         }
     }
 
-    private void invalidate(final ElementHolder element, final int delta) {
+    private boolean shouldEnableHardwareLayer(ElementHolder element) {
+        if (!mUseHardwareLayer)
+            return false;
+
+        if (Build.VERSION.SDK_INT < 11)
+            return false;
+
+        if (!(element.view instanceof ViewGroup))
+            return false;
+
+        if (element.property == Property.TRANSLATION_Y || element.property == Property.TRANSLATION_X)
+            return true;
+
+        return false;
+    }
+
+    private void invalidate(final ElementHolder element, final float delta) {
         final View view = element.view;
 
         // check to see if the view was detached between the check above and this code
         // getting run on the UI thread.
         if (view.getHandler() == null)
             return;
 
-        if (element.property == Property.SLIDE_TOP) {
-            view.scrollTo(view.getScrollX(), delta);
-            return;
-        } else if (element.property == Property.SLIDE_LEFT) {
-            view.scrollTo(delta, view.getScrollY());
-            return;
-        }
-
-        ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
-
-        if (element.property == Property.SHRINK_TOP)
-            params.setMargins(params.leftMargin, delta, params.rightMargin, params.bottomMargin);
-        else if (element.property == Property.SHRINK_LEFT)
-            params.setMargins(delta, params.topMargin, params.rightMargin, params.bottomMargin);
-
-        view.requestLayout();
+        if (element.property == Property.TRANSLATION_Y)
+            element.proxy.setTranslationY(delta);
+        else if (element.property == Property.TRANSLATION_X)
+            element.proxy.setTranslationX(delta);
+        else if (element.property == Property.SCROLL_Y)
+            element.proxy.scrollTo(element.proxy.getScrollX(), (int) delta);
+        else if (element.property == Property.SCROLL_X)
+            element.proxy.scrollTo((int) delta, element.proxy.getScrollY());
     }
 
     private static abstract class FramePoster {
         public static FramePoster create(Runnable r) {
             if (Build.VERSION.SDK_INT >= 16)
                 return new FramePosterPostJB(r);
             else
                 return new FramePosterPreJB(r);
--- a/mobile/android/base/TabsTray.java
+++ b/mobile/android/base/TabsTray.java
@@ -292,17 +292,17 @@ public class TabsTray extends LinearLayo
             assignValues(row, tab);
 
             return convertView;
         }
     }
 
     private void animateTo(final View view, int x, int duration) {
         PropertyAnimator pa = new PropertyAnimator(duration);
-        pa.attach(view, Property.SLIDE_LEFT, x);
+        pa.attach(view, Property.SCROLL_X, -x);
         if (x != 0 && !mWaitingForClose) {
             mWaitingForClose = true;
 
             TabRow tab = (TabRow)view.getTag();
             final int tabId = tab.id;
 
             pa.setPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
                 public void onPropertyAnimationStart() { }
--- a/mobile/android/base/gfx/LayerView.java
+++ b/mobile/android/base/gfx/LayerView.java
@@ -58,17 +58,17 @@ public class LayerView extends FrameLayo
     private TextureView mTextureView;
 
     private Listener mListener;
 
     /* Flags used to determine when to show the painted surface. */
     public static final int PAINT_BEFORE_FIRST = 0;
     public static final int PAINT_AFTER_FIRST = 1;
 
-    boolean shouldUseTextureView() {
+    public boolean shouldUseTextureView() {
         // Disable TextureView support for now as it causes panning/zooming
         // performance regressions (see bug 792259). Uncomment the code below
         // once this bug is fixed.
         return false;
 
         /*
         // we can only use TextureView on ICS or higher
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
--- a/mobile/android/base/resources/layout-xlarge-v11/browser_toolbar_menu.xml.in
+++ b/mobile/android/base/resources/layout-xlarge-v11/browser_toolbar_menu.xml.in
@@ -5,18 +5,22 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
               xmlns:gecko="http://schemas.android.com/apk/res/@ANDROID_PACKAGE_NAME@"
               android:id="@+id/browser_toolbar"
               style="@style/BrowserToolbar">
 
     <RelativeLayout android:id="@+id/address_bar"
-                    style="@style/AddressBar"
-                    android:background="@drawable/address_bar_bg">
+                    style="@style/AddressBar">
+
+        <ImageView android:id="@+id/address_bar_bg"
+                   android:layout_width="fill_parent"
+                   android:layout_height="fill_parent"
+                   android:background="@drawable/address_bar_bg"/>
 
         <org.mozilla.gecko.TabsButton android:id="@+id/tabs"
                                       style="@style/AddressBar.ImageButton"
                                       android:layout_width="84dip"
                                       android:layout_alignParentLeft="true"
                                       gecko:curveTowards="left"
                                       gecko:cropped="true"
                                       android:background="@drawable/tabs_button"
@@ -28,66 +32,97 @@
         <TextSwitcher android:id="@+id/tabs_count"
                      style="@style/AddressBar.ImageButton"
                      android:layout_width="52dip"
                      android:layout_height="wrap_content"
                      android:layout_marginTop="8dp"
                      android:layout_alignLeft="@id/tabs"
                      android:gravity="center_horizontal"/>
 
-        <org.mozilla.gecko.MenuButton android:id="@+id/menu"
-                                      style="@style/AddressBar.ImageButton"
-                                      android:layout_width="56dip"
-                                      android:layout_alignParentRight="true"
-                                      gecko:curveTowards="none"
-                                      android:gravity="center_vertical"
-                                      android:src="@drawable/menu"
-                                      android:contentDescription="@string/menu"
-                                      android:background="@drawable/action_bar_button"
-                                      android:paddingLeft="14dip"
-                                      android:paddingRight="14dip"
-                                      android:visibility="gone"/>
+        <ImageButton android:id="@+id/menu"
+                     style="@style/AddressBar.ImageButton"
+                     android:layout_width="56dip"
+                     android:layout_alignParentRight="true"
+                     android:gravity="center_vertical"
+                     android:src="@drawable/menu"
+                     android:contentDescription="@string/menu"
+                     android:background="@drawable/action_bar_button"
+                     android:paddingLeft="14dip"
+                     android:paddingRight="14dip"
+                     android:visibility="gone"/>
 
         <LinearLayout android:id="@+id/menu_items"
                       android:layout_width="wrap_content"
                       android:layout_height="fill_parent"
                       android:orientation="horizontal"
                       android:layout_toLeftOf="@id/menu"/>
 
-        <FrameLayout style="@style/AddressBar.Button"
-                     android:layout_toRightOf="@id/tabs"
-                     android:layout_toLeftOf="@id/menu_items"
-                     android:layout_marginLeft="-28dp"
-                     android:layout_alignParentBottom="true"
-                     android:layout_centerVertical="true">
+        <RelativeLayout style="@style/AddressBar.Button"
+                        android:layout_toRightOf="@id/tabs"
+                        android:layout_toLeftOf="@id/menu_items"
+                        android:layout_marginLeft="-28dp"
+                        android:layout_alignParentBottom="true"
+                        android:layout_centerVertical="true">
+
+            <RelativeLayout android:id="@+id/awesome_bar"
+                            style="@style/AddressBar.Button"
+                            android:layout_centerVertical="true"
+                            android:clickable="true"
+                            android:focusable="true">
+
+                <ImageView style="@style/AddressBar.Button"
+                           android:layout_marginLeft="20dp"
+                           android:layout_marginRight="0dp"
+                           android:layout_marginTop="6dp"
+                           android:layout_marginBottom="6dp"
+                           android:duplicateParentState="true"
+                           android:clickable="false"
+                           android:focusable="false"
+                           android:background="@drawable/address_bar_url"/>
 
-            <Button android:id="@+id/awesome_bar"
-                    style="@style/AddressBar.Button"
-                    android:layout_marginLeft="20dp"
-                    android:layout_marginTop="6dp"
-                    android:layout_marginBottom="6dp"
-                    android:layout_marginRight="0dp"
-                    android:background="@drawable/address_bar_url_level"/>
+                <FrameLayout android:id="@+id/awesome_bar_right_edge"
+                             style="@style/AddressBar.ImageButton"
+                             android:layout_width="25dp"
+                             android:layout_height="fill_parent"
+                             android:paddingTop="6dp"
+                             android:paddingBottom="6dp"
+                             android:layout_centerVertical="true"
+                             android:layout_alignParentRight="true"
+                             android:duplicateParentState="true"
+                             android:background="@drawable/address_bar_bg">
+
+                    <ImageView android:layout_width="50dp"
+                               android:layout_height="fill_parent"
+                               android:scaleType="fitXY"
+                               android:layout_marginLeft="-26dp"
+                               android:duplicateParentState="true"
+                               android:clickable="false"
+                               android:focusable="false"
+                               android:src="@drawable/address_bar_url"/>
+
+                </FrameLayout>
+
+            </RelativeLayout>
 
             <ImageButton android:id="@+id/forward"
                          style="@style/AddressBar.ImageButton"
                          android:layout_width="64dip"
                          android:layout_height="40dip"
                          android:layout_marginLeft="22dp"
                          android:paddingLeft="22dp"
-                         android:layout_gravity="center_vertical"
+                         android:layout_centerVertical="true"
                          android:src="@drawable/ic_menu_forward"
                          android:contentDescription="@string/forward"
                          android:background="@drawable/address_bar_forward_button"/>
 
             <ImageButton android:id="@+id/back"
                          style="@style/AddressBar.ImageButton"
                          android:layout_width="50dip"
                          android:layout_height="50dip"
-                         android:layout_gravity="center_vertical"
+                         android:layout_centerVertical="true"
                          android:src="@drawable/ic_menu_back"
                          android:contentDescription="@string/back"
                          android:background="@drawable/address_bar_back_button"/>
 
             <LinearLayout style="@style/AddressBar.Button"
                           android:layout_marginLeft="84dp"
                           android:layout_marginTop="6dp"
                           android:layout_marginBottom="6dp"
@@ -134,17 +169,17 @@
                 <ImageButton android:id="@+id/stop"
                              style="@style/AddressBar.ImageButton.Icon"
                              android:src="@drawable/urlbar_stop"
                              android:contentDescription="@string/stop"
                              android:visibility="gone"/>
 
             </LinearLayout>
 
-        </FrameLayout>
+        </RelativeLayout>
 
          <ImageView android:id="@+id/shadow"
                     android:layout_width="fill_parent"
                     android:layout_height="2dp"
                     android:layout_alignParentBottom="true"
                     android:background="@drawable/address_bar_bg_shadow_repeat"
                     android:visibility="gone"/>