Fork navbar layout for quickstep

Different navbar layouts are loaded via config.xml depending
on if Quickstep is enabled. A contextual button frame is added
to replace the right nav buttons so different styling can be
applied. New back, rotate and keyboard icons are added.

Maintains support for custom navbar layouts. Adds support for
nested reversable elements, needed for contextual button frame.
Fixes navbar gravity support by using RelativeLayout's gravity
and top/bottom switching.

Change-Id: I5e24b2392c377313d421c0f95c3a7a0b99f32590
Fixes: 79930722
Fixes: 79930974
Fixes: 80164476
Test: manual, crash sysui, test against a11y, gb menu, rotate
diff --git a/packages/SystemUI/res/drawable/ic_ime_switcher_default.xml b/packages/SystemUI/res/drawable/ic_ime_switcher_default.xml
index 6a7f18b..d5f8a2a 100644
--- a/packages/SystemUI/res/drawable/ic_ime_switcher_default.xml
+++ b/packages/SystemUI/res/drawable/ic_ime_switcher_default.xml
@@ -15,11 +15,11 @@
   -->
 
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+        android:width="20dp"
+        android:height="20dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
     <path
-        android:pathData="M21,4H3C1.9,4 1,4.9 1,6v13c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2V6C23,4.9 22.1,4 21,4zM21,19H3V6h18V19zM9,8h2v2H9V8zM5,8h2v2H5V8zM8,16h8v1H8V16zM13,8h2v2h-2V8zM9,12h2v2H9V12zM5,12h2v2H5V12zM13,12h2v2h-2V12zM17,8h2v2h-2V8zM17,12h2v2h-2V12z"
+        android:pathData="M19,7h2v2h-2V7zM15,7h2v2h-2V7zM3,7h2v2H3V7zM7,7h2v2H7V7zM11,7h2v2h-2V7zM19,11h2v2h-2V11zM15,11h2v2h-2V11zM3,11h2v2H3V11zM7,11h2v2H7V11zM11,11h2v2h-2V11zM7,15h10v2H7V15z"
         android:fillColor="?attr/singleToneColor"/>
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_sysbar_accessibility_button.xml b/packages/SystemUI/res/drawable/ic_sysbar_accessibility_button.xml
index 6fbc404..8d569b2 100644
--- a/packages/SystemUI/res/drawable/ic_sysbar_accessibility_button.xml
+++ b/packages/SystemUI/res/drawable/ic_sysbar_accessibility_button.xml
@@ -15,8 +15,8 @@
      limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
+    android:width="21dp"
+    android:height="21dp"
     android:viewportWidth="24"
     android:viewportHeight="24">
     <path
diff --git a/packages/SystemUI/res/drawable/ic_sysbar_back_quick_step.xml b/packages/SystemUI/res/drawable/ic_sysbar_back_quick_step.xml
index b02a252..93b2f9c8 100644
--- a/packages/SystemUI/res/drawable/ic_sysbar_back_quick_step.xml
+++ b/packages/SystemUI/res/drawable/ic_sysbar_back_quick_step.xml
@@ -19,10 +19,7 @@
     android:height="28dp"
     android:viewportWidth="28"
     android:viewportHeight="28">
-
     <path
-        android:fillColor="?attr/singleToneColor"
-        android:pathData="M17.25,10.15V14v3.85L14,15.95L10.69,14L14,12.05L17.25,10.15 M18.16,7.71c-0.14,0-0.28,0.04-0.42,0.12 l-4.63,2.72l-4.73,2.78c-0.52,0.3-0.52,1.05,0,1.36l4.73,2.78l4.63,2.72c0.13,0.08,0.28,0.12,0.42,0.12c0.44,0,0.84-0.36,0.84-0.86 V14V8.58C19,8.08,18.6,7.71,18.16,7.71L18.16,7.71z" />
-    <path
-        android:pathData="M 0 0 H 28 V 28 H 0 V 0 Z" />
+        android:pathData="M16.78,10.03l-3.97,3.97l3.97,3.97l-1.06,1.06l-5.03,-5.03l5.03,-5.03z"
+        android:fillColor="?attr/singleToneColor" />
 </vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_sysbar_menu.xml b/packages/SystemUI/res/drawable/ic_sysbar_menu.xml
index 5cc1791..d53c95b 100644
--- a/packages/SystemUI/res/drawable/ic_sysbar_menu.xml
+++ b/packages/SystemUI/res/drawable/ic_sysbar_menu.xml
@@ -15,8 +15,8 @@
      limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="28dp"
-    android:height="28dp"
+    android:width="21dp"
+    android:height="21dp"
     android:viewportWidth="28"
     android:viewportHeight="28">
 
diff --git a/packages/SystemUI/res/drawable/ic_sysbar_rotate_button.xml b/packages/SystemUI/res/drawable/ic_sysbar_rotate_button.xml
index 6c1ae99..907be01 100644
--- a/packages/SystemUI/res/drawable/ic_sysbar_rotate_button.xml
+++ b/packages/SystemUI/res/drawable/ic_sysbar_rotate_button.xml
@@ -17,14 +17,14 @@
 <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt">
     <aapt:attr name="android:drawable">
         <vector android:name="root"
-                android:width="24dp"
-                android:height="24dp"
+                android:width="21dp"
+                android:height="21dp"
                 android:viewportWidth="24.0"
                 android:viewportHeight="24.0">
             <group android:name="icon" android:pivotX="12" android:pivotY="12">
                 <!-- Tint color to be set directly -->
                 <path android:fillColor="#FFFFFFFF"
-                    android:pathData="M17,1.01L7,1C5.9,1 5,1.9 5,3v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V3C19,1.9 18.1,1.01 17,1.01zM17,21H7l0,-1h10V21zM17,18H7V6h10V18zM7,4V3h10v1H7z"/>
+                      android:pathData="M12,4c-0.06,0 -0.12,0.01 -0.18,0.01l1.09,-1.09L11.5,1.5L8,5l3.5,3.5l1.41,-1.41l-1.08,-1.08C11.89,6.01 11.95,6 12,6c3.31,0 6,2.69 6,6c0,1.7 -0.71,3.23 -1.85,4.32l1.41,1.41C19.06,16.28 20,14.25 20,12C20,7.59 16.41,4 12,4zM16,19l-3.5,3.5l-1.41,-1.41l1.1,-1.1C12.13,19.98 12.06,20 12,20c-4.41,0 -8,-3.59 -8,-8c0,-2.25 0.94,-4.28 2.43,-5.73l1.41,1.41C6.71,8.77 6,10.3 6,12c0,3.31 2.69,6 6,6c0.05,0 0.11,-0.01 0.16,-0.01l-1.07,-1.07l1.41,-1.41L16,19z"/>
             </group>
         </vector>
     </aapt:attr>
diff --git a/packages/SystemUI/res/layout/contextual.xml b/packages/SystemUI/res/layout/contextual.xml
new file mode 100644
index 0000000..94591e9
--- /dev/null
+++ b/packages/SystemUI/res/layout/contextual.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             xmlns:systemui="http://schemas.android.com/apk/res-auto"
+             android:id="@+id/menu_container"
+             android:layout_width="@dimen/navigation_key_width"
+             android:layout_height="match_parent"
+             android:importantForAccessibility="no"
+             android:clipChildren="false"
+             android:clipToPadding="false"
+             >
+    <com.android.systemui.statusbar.policy.KeyButtonView
+        android:id="@+id/menu"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+        android:layout_weight="0"
+        android:scaleType="center"
+        systemui:keyCode="82"
+        systemui:playSound="false"
+        android:visibility="invisible"
+        android:contentDescription="@string/accessibility_menu"
+        android:paddingStart="@dimen/navigation_key_padding"
+        android:paddingEnd="@dimen/navigation_key_padding"
+    />
+    <com.android.systemui.statusbar.policy.KeyButtonView
+        android:id="@+id/ime_switcher"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_weight="0"
+        android:scaleType="center"
+        android:visibility="invisible"
+        android:contentDescription="@string/accessibility_ime_switch_button"
+        android:paddingStart="@dimen/navigation_key_padding"
+        android:paddingEnd="@dimen/navigation_key_padding"
+    />
+    <com.android.systemui.statusbar.policy.KeyButtonView
+        android:id="@+id/rotate_suggestion"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_weight="0"
+        android:scaleType="center"
+        android:visibility="invisible"
+        android:contentDescription="@string/accessibility_rotate_button"
+        android:paddingStart="@dimen/navigation_key_padding"
+        android:paddingEnd="@dimen/navigation_key_padding"
+    />
+    <com.android.systemui.statusbar.policy.KeyButtonView
+        android:id="@+id/accessibility_button"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_weight="0"
+        android:scaleType="center"
+        android:visibility="invisible"
+        android:contentDescription="@string/accessibility_accessibility_button"
+        android:paddingStart="@dimen/navigation_key_padding"
+        android:paddingEnd="@dimen/navigation_key_padding"
+    />
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/navigation_layout.xml b/packages/SystemUI/res/layout/navigation_layout.xml
index d72021e..baaf699 100644
--- a/packages/SystemUI/res/layout/navigation_layout.xml
+++ b/packages/SystemUI/res/layout/navigation_layout.xml
@@ -18,16 +18,14 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:layout_marginStart="@dimen/rounded_corner_content_padding"
-    android:layout_marginEnd="@dimen/rounded_corner_content_padding"
-    android:paddingStart="@dimen/nav_content_padding"
-    android:paddingEnd="@dimen/nav_content_padding">
+    android:layout_height="match_parent">
 
     <com.android.systemui.statusbar.phone.NearestTouchFrame
         android:id="@+id/nav_buttons"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
+        android:paddingStart="@dimen/rounded_corner_content_padding"
+        android:paddingEnd="@dimen/rounded_corner_content_padding"
         android:clipChildren="false"
         android:clipToPadding="false">
 
@@ -36,6 +34,8 @@
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:orientation="horizontal"
+            android:paddingStart="@dimen/nav_content_padding"
+            android:paddingEnd="@dimen/nav_content_padding"
             android:clipToPadding="false"
             android:clipChildren="false" />
 
@@ -46,6 +46,8 @@
             android:layout_gravity="center"
             android:gravity="center"
             android:orientation="horizontal"
+            android:paddingStart="@dimen/nav_content_padding"
+            android:paddingEnd="@dimen/nav_content_padding"
             android:clipToPadding="false"
             android:clipChildren="false" />
 
diff --git a/packages/SystemUI/res/layout/navigation_layout_rot90.xml b/packages/SystemUI/res/layout/navigation_layout_rot90.xml
index 0e17e5b5..6d5b7788 100644
--- a/packages/SystemUI/res/layout/navigation_layout_rot90.xml
+++ b/packages/SystemUI/res/layout/navigation_layout_rot90.xml
@@ -18,23 +18,26 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:layout_marginTop="@dimen/rounded_corner_content_padding"
-    android:layout_marginBottom="@dimen/rounded_corner_content_padding"
-    android:paddingTop="8dp"
-    android:paddingBottom="8dp">
+    android:layout_height="match_parent">
 
     <com.android.systemui.statusbar.phone.NearestTouchFrame
         android:id="@+id/nav_buttons"
         android:layout_width="match_parent"
-        android:layout_height="match_parent">
+        android:layout_height="match_parent"
+        android:paddingTop="@dimen/rounded_corner_content_padding"
+        android:paddingBottom="@dimen/rounded_corner_content_padding"
+        android:clipChildren="false"
+        android:clipToPadding="false">
 
         <com.android.systemui.statusbar.phone.ReverseLinearLayout
             android:id="@+id/ends_group"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:orientation="vertical"
-            android:clipChildren="false" />
+            android:paddingTop="@dimen/nav_content_padding"
+            android:paddingBottom="@dimen/nav_content_padding"
+            android:clipChildren="false"
+            android:clipToPadding="false" />
 
         <com.android.systemui.statusbar.phone.ReverseLinearLayout
             android:id="@+id/center_group"
@@ -42,7 +45,10 @@
             android:layout_height="match_parent"
             android:gravity="center"
             android:orientation="vertical"
-            android:clipChildren="false" />
+            android:paddingTop="@dimen/nav_content_padding"
+            android:paddingBottom="@dimen/nav_content_padding"
+            android:clipChildren="false"
+            android:clipToPadding="false" />
 
     </com.android.systemui.statusbar.phone.NearestTouchFrame>
 
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 251589b..a68ba9b 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -364,6 +364,7 @@
 
     <!-- Nav bar button default ordering/layout -->
     <string name="config_navBarLayout" translatable="false">left[.5W],back[1WC];home;recent[1WC],right[.5W]</string>
+    <string name="config_navBarLayoutQuickstep" translatable="false">back[1.7WC];home;contextual[1.7WC]</string>
 
     <bool name="quick_settings_show_full_alarm">false</bool>
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index 4885c2f..e6f2c33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -34,11 +34,12 @@
 import android.widget.Space;
 
 import com.android.systemui.Dependency;
+import com.android.systemui.OverviewProxyService;
 import com.android.systemui.R;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider;
-import com.android.systemui.statusbar.phone.ReverseLinearLayout.ReverseFrameLayout;
+import com.android.systemui.statusbar.phone.ReverseLinearLayout.ReverseRelativeLayout;
 import com.android.systemui.statusbar.policy.KeyButtonView;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
@@ -67,6 +68,7 @@
     public static final String KEY = "key";
     public static final String LEFT = "left";
     public static final String RIGHT = "right";
+    public static final String CONTEXTUAL = "contextual";
 
     public static final String GRAVITY_SEPARATOR = ";";
     public static final String BUTTON_SEPARATOR = ",";
@@ -97,6 +99,9 @@
     private View mLastLandscape;
 
     private boolean mAlternativeOrder;
+    private boolean mUsingCustomLayout;
+
+    private OverviewProxyService mOverviewProxyService;
 
     public NavigationBarInflaterView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -105,6 +110,7 @@
                 context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
         Mode displayMode = mDisplay.getMode();
         isRot0Landscape = displayMode.getPhysicalWidth() > displayMode.getPhysicalHeight();
+        mOverviewProxyService = Dependency.get(OverviewProxyService.class);
     }
 
     private void createInflaters() {
@@ -136,7 +142,10 @@
     }
 
     protected String getDefaultLayout() {
-        return mContext.getString(R.string.config_navBarLayout);
+        final int defaultResource = mOverviewProxyService.shouldShowSwipeUpUI()
+                ? R.string.config_navBarLayoutQuickstep
+                : R.string.config_navBarLayout;
+        return mContext.getString(defaultResource);
     }
 
     @Override
@@ -159,6 +168,7 @@
     public void onTuningChanged(String key, String newValue) {
         if (NAV_BAR_VIEWS.equals(key)) {
             if (!Objects.equals(mCurrentLayout, newValue)) {
+                mUsingCustomLayout = newValue != null;
                 clearViews();
                 inflateLayout(newValue);
             }
@@ -168,6 +178,18 @@
         }
     }
 
+    public void onLikelyDefaultLayoutChange() {
+        // Don't override custom layouts
+        if (mUsingCustomLayout) return;
+
+        // Reevaluate new layout
+        final String newValue = getDefaultLayout();
+        if (!Objects.equals(mCurrentLayout, newValue)) {
+            clearViews();
+            inflateLayout(newValue);
+        }
+    }
+
     public void setButtonDispatchers(SparseArray<ButtonDispatcher> buttonDispatchers) {
         mButtonDispatchers = buttonDispatchers;
         for (int i = 0; i < buttonDispatchers.size(); i++) {
@@ -178,10 +200,12 @@
     public void updateButtonDispatchersCurrentView() {
         if (mButtonDispatchers != null) {
             final int rotation = mDisplay.getRotation();
-            final View view = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
-                    ? mRot0 : mRot90;
+            final boolean portrait = rotation == Surface.ROTATION_0
+                    || rotation == Surface.ROTATION_180;
+            final View view = portrait ? mRot0 : mRot90;
             for (int i = 0; i < mButtonDispatchers.size(); i++) {
-                mButtonDispatchers.valueAt(i).setCurrentView(view);
+                final ButtonDispatcher dispatcher = mButtonDispatchers.valueAt(i);
+                dispatcher.setCurrentView(view);
             }
         }
     }
@@ -288,8 +312,8 @@
         addToDispatchers(v);
         View lastView = landscape ? mLastLandscape : mLastPortrait;
         View accessibilityView = v;
-        if (v instanceof ReverseFrameLayout) {
-            accessibilityView = ((ReverseFrameLayout) v).getChildAt(0);
+        if (v instanceof ReverseRelativeLayout) {
+            accessibilityView = ((ReverseRelativeLayout) v).getChildAt(0);
         }
         if (lastView != null) {
             accessibilityView.setAccessibilityTraversalAfter(lastView.getId());
@@ -307,21 +331,33 @@
         if (sizeStr == null) return v;
 
         if (sizeStr.contains(WEIGHT_SUFFIX)) {
+            // To support gravity, wrap in RelativeLayout and apply gravity to it.
+            // Children wanting to use gravity must be smaller then the frame.
             float weight = Float.parseFloat(sizeStr.substring(0, sizeStr.indexOf(WEIGHT_SUFFIX)));
-            FrameLayout frame = new ReverseFrameLayout(mContext);
+            ReverseRelativeLayout frame = new ReverseRelativeLayout(mContext);
             LayoutParams childParams = new LayoutParams(v.getLayoutParams());
-            if (sizeStr.endsWith(WEIGHT_CENTERED_SUFFIX)) {
-                childParams.gravity = Gravity.CENTER;
-            } else {
-                childParams.gravity = landscape ? (start ? Gravity.BOTTOM : Gravity.TOP)
-                        : (start ? Gravity.START : Gravity.END);
-            }
+
+            // Compute gravity to apply
+            int gravity = (landscape) ? (start ? Gravity.TOP : Gravity.BOTTOM)
+                    : (start ? Gravity.START : Gravity.END);
+            if (sizeStr.endsWith(WEIGHT_CENTERED_SUFFIX)) gravity = Gravity.CENTER;
+
+            // Set default gravity, flipped if needed in reversed layouts (270 RTL and 90 LTR)
+            frame.setDefaultGravity(gravity);
+            frame.setGravity(gravity); // Apply gravity to root
+
             frame.addView(v, childParams);
+
+            // Use weighting to set the width of the frame
             frame.setLayoutParams(new LinearLayout.LayoutParams(0, MATCH_PARENT, weight));
+
+            // Ensure ripples can be drawn outside bounds
             frame.setClipChildren(false);
             frame.setClipToPadding(false);
+
             return frame;
         }
+
         float size = Float.parseFloat(sizeStr);
         ViewGroup.LayoutParams params = v.getLayoutParams();
         params.width = (int) (params.width * size);
@@ -355,6 +391,8 @@
             v = inflater.inflate(R.layout.nav_key_space, parent, false);
         } else if (CLIPBOARD.equals(button)) {
             v = inflater.inflate(R.layout.clipboard, parent, false);
+        } else if (CONTEXTUAL.equals(button)) {
+            v = inflater.inflate(R.layout.contextual, parent, false);
         } else if (button.startsWith(KEY)) {
             String uri = extractImage(button);
             int code = extractKeycode(button);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index a907bdd..6011712 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -745,6 +745,12 @@
 
     public void updateStates() {
         final boolean showSwipeUpUI = mOverviewProxyService.shouldShowSwipeUpUI();
+
+        if (mNavigationInflaterView != null) {
+            // Reinflate the navbar if needed, no-op unless the swipe up state changes
+            mNavigationInflaterView.onLikelyDefaultLayoutChange();
+        }
+
         updateSlippery();
         reloadNavIcons();
         updateNavButtonIcons();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java
index bcbc345..d3ec187 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java
@@ -17,10 +17,11 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.util.AttributeSet;
+import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.FrameLayout;
 import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
 
 import java.util.ArrayList;
 
@@ -48,7 +49,7 @@
 
     @Override
     public void addView(View child) {
-        reverseParams(child.getLayoutParams(), child);
+        reverseParams(child.getLayoutParams(), child, mIsLayoutReverse);
         if (mIsLayoutReverse) {
             super.addView(child, 0);
         } else {
@@ -58,7 +59,7 @@
 
     @Override
     public void addView(View child, ViewGroup.LayoutParams params) {
-        reverseParams(params, child);
+        reverseParams(params, child, mIsLayoutReverse);
         if (mIsLayoutReverse) {
             super.addView(child, 0, params);
         } else {
@@ -94,15 +95,17 @@
             }
             removeAllViews();
             for (int i = childCount - 1; i >= 0; i--) {
-                super.addView(childList.get(i));
+                final View child = childList.get(i);
+                super.addView(child);
             }
             mIsLayoutReverse = isLayoutReverse;
         }
     }
 
-    private static void reverseParams(ViewGroup.LayoutParams params, View child) {
+    private static void reverseParams(ViewGroup.LayoutParams params, View child,
+            boolean isLayoutReverse) {
         if (child instanceof Reversable) {
-            ((Reversable) child).reverse();
+            ((Reversable) child).reverse(isLayoutReverse);
         }
         if (child.getPaddingLeft() == child.getPaddingRight()
                 && child.getPaddingTop() == child.getPaddingBottom()) {
@@ -118,20 +121,48 @@
     }
 
     public interface Reversable {
-        void reverse();
+        void reverse(boolean isLayoutReverse);
     }
 
-    public static class ReverseFrameLayout extends FrameLayout implements Reversable {
+    public static class ReverseRelativeLayout extends RelativeLayout implements Reversable {
 
-        public ReverseFrameLayout(Context context) {
+        public ReverseRelativeLayout(Context context) {
             super(context);
         }
 
         @Override
-        public void reverse() {
-            for (int i = 0; i < getChildCount(); i++) {
-                View child = getChildAt(i);
-                reverseParams(child.getLayoutParams(), child);
+        public void reverse(boolean isLayoutReverse) {
+            updateGravity(isLayoutReverse);
+            reverseGroup(this, isLayoutReverse);
+        }
+
+        private int mDefaultGravity = Gravity.NO_GRAVITY;
+        public void setDefaultGravity(int gravity) {
+            mDefaultGravity = gravity;
+        }
+
+        public void updateGravity(boolean isLayoutReverse) {
+            // Flip gravity if top of bottom is used
+            if (mDefaultGravity != Gravity.TOP && mDefaultGravity != Gravity.BOTTOM) return;
+
+            // Use the default (intended for 270 LTR and 90 RTL) unless layout is otherwise
+            int gravityToApply = mDefaultGravity;
+            if (isLayoutReverse) {
+                gravityToApply = mDefaultGravity == Gravity.TOP ? Gravity.BOTTOM : Gravity.TOP;
+            }
+
+            if (getGravity() != gravityToApply) setGravity(gravityToApply);
+        }
+    }
+    
+    private static void reverseGroup(ViewGroup group, boolean isLayoutReverse) {
+        for (int i = 0; i < group.getChildCount(); i++) {
+            final View child = group.getChildAt(i);
+            reverseParams(child.getLayoutParams(), child, isLayoutReverse);
+
+            // Recursively reverse all children
+            if (child instanceof ViewGroup) {
+                reverseGroup((ViewGroup) child, isLayoutReverse);
             }
         }
     }