Merge "Adds additional sanitization for Zygote command arguments." into qt-dev
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 7c4a171..204012f 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -3059,10 +3059,9 @@
             int yDiff = (int) ((oldy - y) * DIRECT_SHARE_EXPANSION_RATE);
 
             int prevHeight = mDirectShareCurrHeight;
-            mDirectShareCurrHeight = Math.min(mDirectShareCurrHeight + yDiff,
-                    mDirectShareMaxHeight);
-            mDirectShareCurrHeight = Math.max(mDirectShareCurrHeight, mDirectShareMinHeight);
-            yDiff = mDirectShareCurrHeight - prevHeight;
+            int newHeight = Math.min(prevHeight + yDiff, mDirectShareMaxHeight);
+            newHeight = Math.max(newHeight, mDirectShareMinHeight);
+            yDiff = newHeight - prevHeight;
 
             if (view == null || view.getChildCount() == 0 || yDiff == 0) {
                 return;
@@ -3079,7 +3078,7 @@
                     if (child.getTag() != null && child.getTag() instanceof DirectShareViewHolder) {
                         int widthSpec = MeasureSpec.makeMeasureSpec(child.getWidth(),
                                 MeasureSpec.EXACTLY);
-                        int heightSpec = MeasureSpec.makeMeasureSpec(mDirectShareCurrHeight,
+                        int heightSpec = MeasureSpec.makeMeasureSpec(newHeight,
                                 MeasureSpec.EXACTLY);
                         child.measure(widthSpec, heightSpec);
                         child.getLayoutParams().height = child.getMeasuredHeight();
@@ -3090,6 +3089,10 @@
                     }
                 }
             }
+
+            if (foundExpansion) {
+                mDirectShareCurrHeight = newHeight;
+            }
         }
     }
 
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index 66bbe22..6b0d85e 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -114,6 +114,8 @@
 
     private final Rect mTempRect = new Rect();
 
+    private AbsListView mNestedScrollingChild;
+
     private final ViewTreeObserver.OnTouchModeChangeListener mTouchModeChangeListener =
             new ViewTreeObserver.OnTouchModeChangeListener() {
                 @Override
@@ -317,6 +319,13 @@
         return mIsDragging || mOpenOnClick;
     }
 
+    private boolean isNestedChildScrolled() {
+        return mNestedScrollingChild != null
+                && mNestedScrollingChild.getChildCount() > 0
+                && (mNestedScrollingChild.getFirstVisiblePosition() > 0
+                        || mNestedScrollingChild.getChildAt(0).getTop() < 0);
+    }
+
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
         final int action = ev.getActionMasked();
@@ -359,7 +368,11 @@
                 }
                 if (mIsDragging) {
                     final float dy = y - mLastTouchY;
-                    performDrag(dy);
+                    if (dy > 0 && isNestedChildScrolled()) {
+                        mNestedScrollingChild.smoothScrollBy((int) -dy, 0);
+                    } else {
+                        performDrag(dy);
+                    }
                 }
                 mLastTouchY = y;
             }
@@ -411,6 +424,9 @@
                             smoothScrollTo(mCollapsibleHeight + mUncollapsibleHeight, yvel);
                             mDismissOnScrollerFinished = true;
                         } else {
+                            if (isNestedChildScrolled()) {
+                                mNestedScrollingChild.smoothScrollToPosition(0);
+                            }
                             smoothScrollTo(yvel < 0 ? 0 : mCollapsibleHeight, yvel);
                         }
                     }
@@ -680,7 +696,13 @@
 
     @Override
     public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
-        return (nestedScrollAxes & View.SCROLL_AXIS_VERTICAL) != 0;
+        if ((nestedScrollAxes & View.SCROLL_AXIS_VERTICAL) != 0) {
+            if (child instanceof AbsListView) {
+                mNestedScrollingChild = (AbsListView) child;
+            }
+            return true;
+        }
+        return false;
     }
 
     @Override
diff --git a/packages/SystemUI/res/layout-land/global_actions_grid.xml b/packages/SystemUI/res/layout-land/global_actions_grid.xml
index c51e71b..511910e 100644
--- a/packages/SystemUI/res/layout-land/global_actions_grid.xml
+++ b/packages/SystemUI/res/layout-land/global_actions_grid.xml
@@ -8,7 +8,7 @@
     android:clipToPadding="false"
     android:theme="@style/qs_theme"
     android:paddingLeft="@dimen/global_actions_top_padding"
-    android:gravity="top|left"
+    android:gravity="right"
     android:clipChildren="false"
 >
     <LinearLayout
diff --git a/packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml b/packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml
index de853c7..ff2a7d8 100644
--- a/packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml
+++ b/packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml
@@ -7,7 +7,7 @@
     android:orientation="horizontal"
     android:clipToPadding="false"
     android:theme="@style/qs_theme"
-    android:gravity="top|right"
+    android:gravity="left"
     android:paddingRight="@dimen/global_actions_top_padding"
     android:clipChildren="false"
 >
diff --git a/packages/SystemUI/res/layout/global_actions_grid.xml b/packages/SystemUI/res/layout/global_actions_grid.xml
index 8651e5a..3f10b38 100644
--- a/packages/SystemUI/res/layout/global_actions_grid.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid.xml
@@ -6,9 +6,8 @@
     android:layout_height="match_parent"
     android:orientation="horizontal"
     android:clipToPadding="false"
-    android:paddingTop="@dimen/global_actions_top_padding"
     android:theme="@style/qs_theme"
-    android:gravity="top|center"
+    android:gravity="bottom"
     android:clipChildren="false"
 >
     <LinearLayout
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 52b766d..6f58159 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -38,6 +38,8 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.Dependency;
+import com.android.systemui.SystemUIFactory;
+import com.android.systemui.util.InjectionInflationController;
 
 public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSecurityView {
     private static final boolean DEBUG = KeyguardConstants.DEBUG;
@@ -67,6 +69,7 @@
     private KeyguardSecurityView mCurrentSecurityView;
     private SecurityCallback mSecurityCallback;
     private AlertDialog mAlertDialog;
+    private InjectionInflationController mInjectionInflationController;
 
     private final KeyguardUpdateMonitor mUpdateMonitor;
 
@@ -101,6 +104,9 @@
         mSecurityModel = new KeyguardSecurityModel(context);
         mLockPatternUtils = new LockPatternUtils(context);
         mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+
+        mInjectionInflationController =  new InjectionInflationController(
+            SystemUIFactory.getInstance().getRootComponent());
     }
 
     public void setSecurityCallback(SecurityCallback callback) {
@@ -157,7 +163,8 @@
         if (view == null && layoutId != 0) {
             final LayoutInflater inflater = LayoutInflater.from(mContext);
             if (DEBUG) Log.v(TAG, "inflating id = " + layoutId);
-            View v = inflater.inflate(layoutId, mSecurityViewFlipper, false);
+            View v = mInjectionInflationController.injectable(inflater)
+                    .inflate(layoutId, mSecurityViewFlipper, false);
             mSecurityViewFlipper.addView(v);
             updateSecurityView(v);
             view = (KeyguardSecurityView)v;
diff --git a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
index 822538b..802903d 100644
--- a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
@@ -32,13 +32,10 @@
 import android.view.ViewTreeObserver;
 import android.widget.LinearLayout;
 
-import com.android.systemui.globalactions.GlobalActionsDialog;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
 import com.android.systemui.util.leak.RotationUtils;
 
-import java.util.ArrayList;
-
 /**
  * Layout for placing two containers at a specific physical position on the device, relative to the
  * device's hardware, regardless of screen rotation.
@@ -258,24 +255,16 @@
     @Override
     public void onUpdateList() {
         super.onUpdateList();
-        ArrayList<GlobalActionsDialog.Action> separatedActions =
-                mAdapter.getSeparatedItems();
-        ArrayList<GlobalActionsDialog.Action> listActions = mAdapter.getListItems();
 
         for (int i = 0; i < mAdapter.getCount(); i++) {
-            Object action = mAdapter.getItem(i);
-            int separatedIndex = separatedActions.indexOf(action);
             ViewGroup parent;
-            if (separatedIndex != -1) {
+            boolean separated = mAdapter.shouldBeSeparated(i);
+            if (separated) {
                 parent = getSeparatedView();
             } else {
-                int listIndex = listActions.indexOf(action);
                 parent = getListView();
             }
             View v = mAdapter.getView(i, null, parent);
-            final int pos = i;
-            v.setOnClickListener(view -> mAdapter.onClickItem(pos));
-            v.setOnLongClickListener(view -> mAdapter.onLongClickItem(pos));
             parent.addView(v);
         }
     }
@@ -421,7 +410,9 @@
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
+
         post(() -> updatePosition());
+
     }
 
     private void animateChild(int oldHeight, int newHeight) {
diff --git a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
index a30b681..f8287a4 100644
--- a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
@@ -26,16 +26,12 @@
 
 import com.android.systemui.util.leak.RotationUtils;
 
-import java.util.ArrayList;
-
 /**
  * Layout class representing the Global Actions menu which appears when the power button is held.
  */
 public abstract class MultiListLayout extends LinearLayout {
     protected boolean mHasOutsideTouch;
     protected MultiListAdapter mAdapter;
-    protected boolean mSnapToEdge;
-
     protected int mRotation;
     protected RotationListener mRotationListener;
 
@@ -51,7 +47,7 @@
     /**
      * Removes all child items from the separated and list views, if they exist.
      */
-    public abstract void removeAllItems();
+    protected abstract void removeAllItems();
 
     /**
      * Sets the divided view, which may have a differently-colored background.
@@ -70,13 +66,6 @@
     }
 
     /**
-     * Sets whether the GlobalActions view should snap to the edge of the screen.
-     */
-    public void setSnapToEdge(boolean snap) {
-        mSnapToEdge = snap;
-    }
-
-    /**
      * Sets the adapter used to inflate items.
      */
     public void setAdapter(MultiListAdapter adapter) {
@@ -122,6 +111,7 @@
     }
 
     protected void onUpdateList() {
+        removeAllItems();
         setSeparatedViewVisibility(mAdapter.hasSeparatedItems());
     }
 
@@ -163,16 +153,14 @@
      */
     public abstract static class MultiListAdapter extends BaseAdapter {
         /**
-         * Creates an ArrayList of items which should be rendered in the separated view.
-         * @param useSeparatedView is true if the separated view will be used, false otherwise.
+         * Counts the number of items to be rendered in the separated view.
          */
-        public abstract ArrayList getSeparatedItems();
+        public abstract int countSeparatedItems();
 
         /**
-         * Creates an ArrayList of items which should be rendered in the list view.
-         * @param useSeparatedView True if the separated view will be used, false otherwise.
+         * Counts the number of items be rendered in the list view.
          */
-        public abstract ArrayList getListItems();
+        public abstract int countListItems();
 
         /**
          * Callback to run when an individual item is clicked or pressed.
@@ -192,7 +180,13 @@
          * or not to hide the separated list from view.
          */
         public boolean hasSeparatedItems() {
-            return getSeparatedItems().size() > 0;
+            return countSeparatedItems() > 0;
         }
+
+        /**
+         * Determines whether the item at the given index should be rendered in the separarted view.
+         * @param position The index of the item.
+         */
+        public abstract boolean shouldBeSeparated(int position);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index e128531..64511a0 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -919,73 +919,59 @@
      * via {@link com.android.systemui.globalactions.GlobalActionsDialog#mDeviceProvisioned}.
      */
     public class MyAdapter extends MultiListAdapter {
-        @Override
-        public int getCount() {
+        private int countItems(boolean separated) {
             int count = 0;
             for (int i = 0; i < mItems.size(); i++) {
                 final Action action = mItems.get(i);
 
-                if (mKeyguardShowing && !action.showDuringKeyguard()) {
-                    continue;
+                if (shouldBeShown(action) && action.shouldBeSeparated() == separated) {
+                    count++;
                 }
-                if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
-                    continue;
-                }
-                count++;
             }
             return count;
         }
 
+        private boolean shouldBeShown(Action action) {
+            if (mKeyguardShowing && !action.showDuringKeyguard()) {
+                return false;
+            }
+            if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public int countSeparatedItems() {
+            return countItems(true);
+        }
+
+        @Override
+        public int countListItems() {
+            return countItems(false);
+        }
+
+        @Override
+        public int getCount() {
+            return countSeparatedItems() + countListItems();
+        }
+
         @Override
         public boolean isEnabled(int position) {
             return getItem(position).isEnabled();
         }
 
         @Override
-        public ArrayList<Action> getSeparatedItems() {
-            ArrayList<Action> separatedActions = new ArrayList<Action>();
-            if (!shouldUseSeparatedView()) {
-                return separatedActions;
-            }
-            for (int i = 0; i < mItems.size(); i++) {
-                final Action action = mItems.get(i);
-                if (action.shouldBeSeparated()) {
-                    separatedActions.add(action);
-                }
-            }
-            return separatedActions;
-        }
-
-        @Override
-        public ArrayList<Action> getListItems() {
-            if (!shouldUseSeparatedView()) {
-                return new ArrayList<Action>(mItems);
-            }
-            ArrayList<Action> listActions = new ArrayList<Action>();
-            for (int i = 0; i < mItems.size(); i++) {
-                final Action action = mItems.get(i);
-                if (!action.shouldBeSeparated()) {
-                    listActions.add(action);
-                }
-            }
-            return listActions;
-        }
-
-        @Override
         public boolean areAllItemsEnabled() {
             return false;
         }
 
         @Override
         public Action getItem(int position) {
-
             int filteredPos = 0;
             for (int i = 0; i < mItems.size(); i++) {
                 final Action action = mItems.get(i);
-                if (mKeyguardShowing && !action.showDuringKeyguard()) {
-                    continue;
-                }
-                if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
+                if (!shouldBeShown(action)) {
                     continue;
                 }
                 if (filteredPos == position) {
@@ -1010,10 +996,8 @@
         public View getView(int position, View convertView, ViewGroup parent) {
             Action action = getItem(position);
             View view = action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
-            // Everything but screenshot, the last item, gets white background.
-            if (position == getCount() - 1) {
-                MultiListLayout.get(parent).setDivisionView(view);
-            }
+            view.setOnClickListener(v -> onClickItem(position));
+            view.setOnLongClickListener(v -> onLongClickItem(position));
             return view;
         }
 
@@ -1035,6 +1019,11 @@
             }
             item.onPress();
         }
+
+        @Override
+        public boolean shouldBeSeparated(int position) {
+            return getItem(position).shouldBeSeparated();
+        }
     }
 
     // note: the scheme below made more sense when we were planning on having
@@ -1590,7 +1579,6 @@
                 mScrimAlpha = 1f;
                 initializePanel();
             }
-            mGlobalActionsLayout.setSnapToEdge(true);
             getWindow().setBackgroundDrawable(mBackgroundDrawable);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java
index 669348e..554ed73 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java
@@ -21,19 +21,16 @@
 import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE;
 
 import android.content.Context;
-import android.text.TextUtils;
 import android.util.AttributeSet;
-import android.view.Gravity;
+import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.HardwareBgDrawable;
 import com.android.systemui.MultiListLayout;
 import com.android.systemui.util.leak.RotationUtils;
 
-import java.util.ArrayList;
-import java.util.Locale;
-
 /**
  * Grid-based implementation of the button layout created by the global actions dialog.
  */
@@ -69,67 +66,65 @@
         }
     }
 
-    /**
-     * Sets the number of items expected to be rendered in the list container. This allows the
-     * layout to correctly determine which parent containers will be used for items before they have
-     * beenadded to the layout.
-     * @param count The number of items expected.
-     */
-    public void setExpectedListItemCount(int count) {
-        getListView().setExpectedCount(count);
+    @VisibleForTesting
+    protected int getCurrentRotation() {
+        return RotationUtils.getRotation(mContext);
+    }
+
+    @VisibleForTesting
+    protected void setupListView(ListGridLayout listView, int itemCount) {
+        listView.setExpectedCount(itemCount);
+        listView.setReverseSublists(shouldReverseSublists());
+        listView.setReverseItems(shouldReverseListItems());
+        listView.setSwapRowsAndColumns(shouldSwapRowsAndColumns());
     }
 
     @Override
     public void onUpdateList() {
         super.onUpdateList();
-        ArrayList<GlobalActionsDialog.Action> separatedActions =
-                mAdapter.getSeparatedItems();
-        ArrayList<GlobalActionsDialog.Action> listActions = mAdapter.getListItems();
-        setExpectedListItemCount(listActions.size());
-        int rotation = RotationUtils.getRotation(mContext);
 
-        boolean reverse = false; // should we add items to parents in the reverse order?
-        if (rotation == ROTATION_NONE
-                || rotation == ROTATION_SEASCAPE) {
-            reverse = !reverse; // if we're in portrait or seascape, reverse items
-        }
-        if (TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
-                == View.LAYOUT_DIRECTION_RTL) {
-            reverse = !reverse; // if we're in an RTL language, reverse items (again)
-        }
+        ViewGroup separatedView = getSeparatedView();
+        ListGridLayout listView = getListView();
+        setupListView(listView, mAdapter.countListItems());
 
         for (int i = 0; i < mAdapter.getCount(); i++) {
-            Object action = mAdapter.getItem(i);
-            int separatedIndex = separatedActions.indexOf(action);
-            ViewGroup parent;
-            if (separatedIndex != -1) {
-                parent = getParentView(true, separatedIndex, rotation);
+            // generate the view item
+            View v;
+            boolean separated = mAdapter.shouldBeSeparated(i);
+            if (separated) {
+                v = mAdapter.getView(i, null, separatedView);
             } else {
-                int listIndex = listActions.indexOf(action);
-                parent = getParentView(false, listIndex, rotation);
+                v = mAdapter.getView(i, null, listView);
             }
-            View v = mAdapter.getView(i, null, parent);
-            final int pos = i;
-            v.setOnClickListener(view -> mAdapter.onClickItem(pos));
-            v.setOnLongClickListener(view -> mAdapter.onLongClickItem(pos));
-            if (reverse) {
-                parent.addView(v, 0); // reverse order of items
+            Log.d("GlobalActionsGridLayout", "View: " + v);
+
+            if (separated) {
+                separatedView.addView(v);
             } else {
-                parent.addView(v);
+                listView.addItem(v);
             }
-            parent.setVisibility(View.VISIBLE);
         }
-        updateSnapPosition();
-        updateSeparatedButtonSize();
+        updateSeparatedItemSize();
     }
 
-    private void updateSeparatedButtonSize() {
+    /**
+     * If the separated view contains only one item, expand the bounds of that item to take up the
+     * entire view, so that the whole thing is touch-able.
+     */
+    private void updateSeparatedItemSize() {
         ViewGroup separated = getSeparatedView();
+        if (separated.getChildCount() == 0) {
+            return;
+        }
+        View firstChild = separated.getChildAt(0);
+        ViewGroup.LayoutParams childParams = firstChild.getLayoutParams();
+
         if (separated.getChildCount() == 1) {
-            View onlyChild = separated.getChildAt(0);
-            ViewGroup.LayoutParams childParams = onlyChild.getLayoutParams();
             childParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
             childParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
+        } else {
+            childParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+            childParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
         }
     }
 
@@ -138,19 +133,6 @@
         return findViewById(com.android.systemui.R.id.separated_button);
     }
 
-    private void updateSnapPosition() {
-        if (mSnapToEdge) {
-            setPadding(0, 0, 0, 0);
-            if (mRotation == ROTATION_LANDSCAPE) {
-                setGravity(Gravity.RIGHT);
-            } else if (mRotation == ROTATION_SEASCAPE) {
-                setGravity(Gravity.LEFT);
-            } else {
-                setGravity(Gravity.BOTTOM);
-            }
-        }
-    }
-
     @Override
     protected ListGridLayout getListView() {
         return findViewById(android.R.id.list);
@@ -168,19 +150,47 @@
         }
     }
 
-    public ViewGroup getParentView(boolean separated, int index, int rotation) {
-        if (separated) {
-            return getSeparatedView();
-        } else {
-            switch (rotation) {
-                case ROTATION_LANDSCAPE:
-                    return getListView().getParentView(index, false, true);
-                case ROTATION_SEASCAPE:
-                    return getListView().getParentView(index, true, true);
-                default:
-                    return getListView().getParentView(index, false, false);
-            }
+    /**
+     * Determines whether the ListGridLayout should fill sublists in the reverse order.
+     * Used to account for sublist ordering changing between landscape and seascape views.
+     */
+    @VisibleForTesting
+    protected boolean shouldReverseSublists() {
+        if (getCurrentRotation() == ROTATION_SEASCAPE) {
+            return true;
         }
+        return false;
+    }
+
+    /**
+     * Determines whether the ListGridLayout should fill rows first instead of columns.
+     * Used to account for vertical/horizontal changes due to landscape or seascape rotations.
+     */
+    @VisibleForTesting
+    protected boolean shouldSwapRowsAndColumns() {
+        if (getCurrentRotation() == ROTATION_NONE) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Determines whether the ListGridLayout should reverse the ordering of items within sublists.
+     * Used for RTL languages to ensure that items appear in the same positions, without having to
+     * override layoutDirection, which breaks Talkback ordering.
+     */
+    @VisibleForTesting
+    protected boolean shouldReverseListItems() {
+        int rotation = getCurrentRotation();
+        boolean reverse = false; // should we add items to parents in the reverse order?
+        if (rotation == ROTATION_NONE
+                || rotation == ROTATION_SEASCAPE) {
+            reverse = !reverse; // if we're in portrait or seascape, reverse items
+        }
+        if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+            reverse = !reverse; // if we're in an RTL language, reverse items (again)
+        }
+        return reverse;
     }
 
     /**
@@ -191,7 +201,7 @@
         // do nothing
     }
 
-    private float getAnimationDistance() {
+    protected float getAnimationDistance() {
         int rows = getListView().getRowCount();
         float gridItemSize = getContext().getResources().getDimension(
                 com.android.systemui.R.dimen.global_actions_grid_item_height);
@@ -200,7 +210,7 @@
 
     @Override
     public float getAnimationOffsetX() {
-        switch (RotationUtils.getRotation(getContext())) {
+        switch (getCurrentRotation()) {
             case ROTATION_LANDSCAPE:
                 return getAnimationDistance();
             case ROTATION_SEASCAPE:
@@ -212,7 +222,7 @@
 
     @Override
     public float getAnimationOffsetY() {
-        if (RotationUtils.getRotation(mContext) == ROTATION_NONE) {
+        if (getCurrentRotation() == ROTATION_NONE) {
             return getAnimationDistance();
         }
         return 0;
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java
index 6bc975a..8c93b13 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java
@@ -22,6 +22,8 @@
 import android.view.ViewGroup;
 import android.widget.LinearLayout;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 /**
  * Layout which uses nested LinearLayouts to create a grid with the following behavior:
  *
@@ -40,6 +42,10 @@
 public class ListGridLayout extends LinearLayout {
     private static final String TAG = "ListGridLayout";
     private int mExpectedCount;
+    private int mCurrentCount = 0;
+    private boolean mSwapRowsAndColumns;
+    private boolean mReverseSublists;
+    private boolean mReverseItems;
 
     // number of rows and columns to use for different numbers of items
     private final int[][] mConfigs = {
@@ -61,16 +67,60 @@
     }
 
     /**
+     * Sets whether this grid should prioritize filling rows or columns first.
+     */
+    public void setSwapRowsAndColumns(boolean swap) {
+        mSwapRowsAndColumns = swap;
+    }
+
+    /**
+     * Sets whether this grid should fill sublists in reverse order.
+     */
+    public void setReverseSublists(boolean reverse) {
+        mReverseSublists = reverse;
+    }
+
+    /**
+     * Sets whether this grid should add items to sublists in reverse order.
+     * @param reverse
+     */
+    public void setReverseItems(boolean reverse) {
+        mReverseItems = reverse;
+    }
+
+    /**
      * Remove all items from this grid.
      */
     public void removeAllItems() {
         for (int i = 0; i < getChildCount(); i++) {
-            ViewGroup subList = (ViewGroup) getChildAt(i);
+            ViewGroup subList = getSublist(i);
             if (subList != null) {
                 subList.removeAllViews();
                 subList.setVisibility(View.GONE);
             }
         }
+        mCurrentCount = 0;
+    }
+
+    /**
+     * Adds a view item to this grid, placing it in the correct sublist and ensuring that the
+     * sublist is visible.
+     *
+     * This function is stateful, since it tracks how many items have been added thus far, to
+     * determine which sublist they should be added to. To ensure that this works correctly, call
+     * removeAllItems() instead of removing views individually with removeView() to ensure that the
+     * counter gets reset correctly.
+     * @param item
+     */
+    public void addItem(View item) {
+        ViewGroup parent = getParentView(mCurrentCount, mReverseSublists, mSwapRowsAndColumns);
+        if (mReverseItems) {
+            parent.addView(item, 0);
+        } else {
+            parent.addView(item);
+        }
+        parent.setVisibility(View.VISIBLE);
+        mCurrentCount++;
     }
 
     /**
@@ -83,13 +133,20 @@
      *                           true will cause rows to fill first, adding one item to each column.
      * @return
      */
-    public ViewGroup getParentView(int index, boolean reverseSublists, boolean swapRowsAndColumns) {
+    @VisibleForTesting
+    protected ViewGroup getParentView(int index, boolean reverseSublists,
+            boolean swapRowsAndColumns) {
         if (getRowCount() == 0 || index < 0) {
             return null;
         }
         int targetIndex = Math.min(index, getMaxElementCount() - 1);
         int row = getParentViewIndex(targetIndex, reverseSublists, swapRowsAndColumns);
-        return (ViewGroup) getChildAt(row);
+        return getSublist(row);
+    }
+
+    @VisibleForTesting
+    protected ViewGroup getSublist(int index) {
+        return (ViewGroup) getChildAt(index);
     }
 
     private int reverseSublistIndex(int index) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java
new file mode 100644
index 0000000..3c52e9d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+package com.android.systemui.globalactions;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.MultiListLayout;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.leak.RotationUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link ListGridLayout}.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class GlobalActionsGridLayoutTest extends SysuiTestCase {
+
+    private GlobalActionsGridLayout mGridLayout;
+    private TestAdapter mAdapter;
+    private ListGridLayout mListGrid;
+
+    private class TestAdapter extends MultiListLayout.MultiListAdapter {
+        @Override
+        public void onClickItem(int index) { }
+
+        @Override
+        public boolean onLongClickItem(int index) {
+            return true;
+        }
+
+        @Override
+        public int countSeparatedItems() {
+            return -1;
+        }
+
+        @Override
+        public int countListItems() {
+            return -1;
+        }
+
+        @Override
+        public boolean shouldBeSeparated(int position) {
+            return false;
+        }
+
+        @Override
+        public int getCount() {
+            return countSeparatedItems() + countListItems();
+        }
+
+        @Override
+        public Object getItem(int position) {
+            return null;
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return -1;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            return null;
+        }
+    }
+
+
+    @Before
+    public void setUp() throws Exception {
+        mGridLayout = spy((GlobalActionsGridLayout)
+                LayoutInflater.from(mContext).inflate(R.layout.global_actions_grid, null));
+        mAdapter = spy(new TestAdapter());
+        mGridLayout.setAdapter(mAdapter);
+        mListGrid = spy(mGridLayout.getListView());
+        doReturn(mListGrid).when(mGridLayout).getListView();
+    }
+
+    @Test
+    public void testShouldSwapRowsAndColumns() {
+        doReturn(RotationUtils.ROTATION_NONE).when(mGridLayout).getCurrentRotation();
+        assertEquals(false, mGridLayout.shouldSwapRowsAndColumns());
+
+        doReturn(RotationUtils.ROTATION_LANDSCAPE).when(mGridLayout).getCurrentRotation();
+        assertEquals(true, mGridLayout.shouldSwapRowsAndColumns());
+
+        doReturn(RotationUtils.ROTATION_SEASCAPE).when(mGridLayout).getCurrentRotation();
+        assertEquals(true, mGridLayout.shouldSwapRowsAndColumns());
+    }
+
+    @Test
+    public void testShouldReverseListItems() {
+        doReturn(View.LAYOUT_DIRECTION_LTR).when(mGridLayout).getLayoutDirection();
+
+        doReturn(RotationUtils.ROTATION_LANDSCAPE).when(mGridLayout).getCurrentRotation();
+        assertEquals(false, mGridLayout.shouldReverseListItems());
+
+        doReturn(RotationUtils.ROTATION_NONE).when(mGridLayout).getCurrentRotation();
+        assertEquals(true, mGridLayout.shouldReverseListItems());
+
+        doReturn(RotationUtils.ROTATION_SEASCAPE).when(mGridLayout).getCurrentRotation();
+        assertEquals(true, mGridLayout.shouldReverseListItems());
+
+        doReturn(View.LAYOUT_DIRECTION_RTL).when(mGridLayout).getLayoutDirection();
+
+        doReturn(RotationUtils.ROTATION_LANDSCAPE).when(mGridLayout).getCurrentRotation();
+        assertEquals(true, mGridLayout.shouldReverseListItems());
+
+        doReturn(RotationUtils.ROTATION_NONE).when(mGridLayout).getCurrentRotation();
+        assertEquals(false, mGridLayout.shouldReverseListItems());
+
+        doReturn(RotationUtils.ROTATION_SEASCAPE).when(mGridLayout).getCurrentRotation();
+        assertEquals(false, mGridLayout.shouldReverseListItems());
+    }
+
+    @Test
+    public void testShouldReverseSublists() {
+        doReturn(RotationUtils.ROTATION_LANDSCAPE).when(mGridLayout).getCurrentRotation();
+        assertEquals(false, mGridLayout.shouldReverseSublists());
+
+        doReturn(RotationUtils.ROTATION_NONE).when(mGridLayout).getCurrentRotation();
+        assertEquals(false, mGridLayout.shouldReverseSublists());
+
+        doReturn(RotationUtils.ROTATION_SEASCAPE).when(mGridLayout).getCurrentRotation();
+        assertEquals(true, mGridLayout.shouldReverseSublists());
+    }
+
+    @Test
+    public void testGetAnimationOffsetX() {
+        doReturn(50f).when(mGridLayout).getAnimationDistance();
+
+        doReturn(RotationUtils.ROTATION_NONE).when(mGridLayout).getCurrentRotation();
+        assertEquals(0f, mGridLayout.getAnimationOffsetX(), .01);
+
+        doReturn(RotationUtils.ROTATION_LANDSCAPE).when(mGridLayout).getCurrentRotation();
+        assertEquals(50f, mGridLayout.getAnimationOffsetX(), .01);
+
+        doReturn(RotationUtils.ROTATION_SEASCAPE).when(mGridLayout).getCurrentRotation();
+        assertEquals(-50f, mGridLayout.getAnimationOffsetX(), .01);
+    }
+
+    @Test
+    public void testGetAnimationOffsetY() {
+        doReturn(50f).when(mGridLayout).getAnimationDistance();
+
+        doReturn(RotationUtils.ROTATION_NONE).when(mGridLayout).getCurrentRotation();
+        assertEquals(50f, mGridLayout.getAnimationOffsetY(), .01);
+
+        doReturn(RotationUtils.ROTATION_LANDSCAPE).when(mGridLayout).getCurrentRotation();
+        assertEquals(0f, mGridLayout.getAnimationOffsetY(), .01);
+
+        doReturn(RotationUtils.ROTATION_SEASCAPE).when(mGridLayout).getCurrentRotation();
+        assertEquals(0f, mGridLayout.getAnimationOffsetY(), .01);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testOnUpdateList_noAdapter() {
+        mGridLayout.setAdapter(null);
+        mGridLayout.updateList();
+    }
+
+    @Test
+    public void testOnUpdateList_noItems() {
+        doReturn(0).when(mAdapter).countSeparatedItems();
+        doReturn(0).when(mAdapter).countListItems();
+        mGridLayout.updateList();
+
+        ViewGroup separatedView = mGridLayout.getSeparatedView();
+        ListGridLayout listView = mGridLayout.getListView();
+
+        assertEquals(0, separatedView.getChildCount());
+        assertEquals(View.GONE, separatedView.getVisibility());
+
+        verify(mListGrid, times(0)).addItem(any());
+    }
+
+    @Test
+    public void testOnUpdateList_resizesFirstSeparatedItem() {
+        doReturn(1).when(mAdapter).countSeparatedItems();
+        doReturn(0).when(mAdapter).countListItems();
+        View firstView = new View(mContext, null);
+        View secondView = new View(mContext, null);
+
+        doReturn(firstView).when(mAdapter).getView(eq(0), any(), any());
+        doReturn(true).when(mAdapter).shouldBeSeparated(0);
+
+        mGridLayout.updateList();
+
+        ViewGroup.LayoutParams childParams = firstView.getLayoutParams();
+        assertEquals(ViewGroup.LayoutParams.MATCH_PARENT, childParams.width);
+        assertEquals(ViewGroup.LayoutParams.MATCH_PARENT, childParams.height);
+
+        doReturn(2).when(mAdapter).countSeparatedItems();
+        doReturn(secondView).when(mAdapter).getView(eq(1), any(), any());
+        doReturn(true).when(mAdapter).shouldBeSeparated(1);
+
+        mGridLayout.updateList();
+
+        childParams = firstView.getLayoutParams();
+        assertEquals(ViewGroup.LayoutParams.WRAP_CONTENT, childParams.width);
+        assertEquals(ViewGroup.LayoutParams.WRAP_CONTENT, childParams.height);
+
+
+    }
+
+    @Test
+    public void testOnUpdateList_onlySeparatedItems() {
+        doReturn(1).when(mAdapter).countSeparatedItems();
+        doReturn(0).when(mAdapter).countListItems();
+        View testView = new View(mContext, null);
+        doReturn(testView).when(mAdapter).getView(eq(0), any(), any());
+        doReturn(true).when(mAdapter).shouldBeSeparated(0);
+
+        mGridLayout.updateList();
+
+        verify(mListGrid, times(0)).addItem(any());
+    }
+
+    @Test
+    public void testOnUpdateList_oneSeparatedOneList() {
+        doReturn(1).when(mAdapter).countSeparatedItems();
+        doReturn(1).when(mAdapter).countListItems();
+        View view1 = new View(mContext, null);
+        View view2 = new View(mContext, null);
+
+        doReturn(view1).when(mAdapter).getView(eq(0), any(), any());
+        doReturn(true).when(mAdapter).shouldBeSeparated(0);
+
+        doReturn(view2).when(mAdapter).getView(eq(1), any(), any());
+        doReturn(false).when(mAdapter).shouldBeSeparated(1);
+
+        mGridLayout.updateList();
+
+        ViewGroup separatedView = mGridLayout.getSeparatedView();
+
+        assertEquals(1, separatedView.getChildCount());
+        assertEquals(View.VISIBLE, separatedView.getVisibility());
+        assertEquals(view1, separatedView.getChildAt(0));
+
+        verify(mListGrid, times(1)).addItem(view2);
+    }
+
+    @Test
+    public void testOnUpdateList_fourInList() {
+        doReturn(0).when(mAdapter).countSeparatedItems();
+        doReturn(4).when(mAdapter).countListItems();
+        View view1 = new View(mContext, null);
+        View view2 = new View(mContext, null);
+        View view3 = new View(mContext, null);
+        View view4 = new View(mContext, null);
+
+        doReturn(view1).when(mAdapter).getView(eq(0), any(), any());
+        doReturn(false).when(mAdapter).shouldBeSeparated(0);
+
+        doReturn(view2).when(mAdapter).getView(eq(1), any(), any());
+        doReturn(false).when(mAdapter).shouldBeSeparated(1);
+
+        doReturn(view3).when(mAdapter).getView(eq(2), any(), any());
+        doReturn(false).when(mAdapter).shouldBeSeparated(2);
+
+        doReturn(view4).when(mAdapter).getView(eq(3), any(), any());
+        doReturn(false).when(mAdapter).shouldBeSeparated(3);
+
+        mGridLayout.updateList();
+
+        ViewGroup separatedView = mGridLayout.getSeparatedView();
+        assertEquals(0, separatedView.getChildCount());
+        assertEquals(View.GONE, separatedView.getVisibility());
+
+        verify(mListGrid, times(1)).addItem(view1);
+        verify(mListGrid, times(1)).addItem(view2);
+        verify(mListGrid, times(1)).addItem(view3);
+        verify(mListGrid, times(1)).addItem(view4);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/ListGridLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ListGridLayoutTest.java
index 26f1de8..746140f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/ListGridLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ListGridLayoutTest.java
@@ -125,27 +125,27 @@
         assertEquals(null,
                 mListGridLayout.getParentView(-1, false, false));
 
-        assertEquals(mListGridLayout.getChildAt(0),
+        assertEquals(mListGridLayout.getSublist(0),
                 mListGridLayout.getParentView(0, false, false));
-        assertEquals(mListGridLayout.getChildAt(1),
+        assertEquals(mListGridLayout.getSublist(1),
                 mListGridLayout.getParentView(1, false, false));
-        assertEquals(mListGridLayout.getChildAt(2),
+        assertEquals(mListGridLayout.getSublist(2),
                 mListGridLayout.getParentView(2, false, false));
-        assertEquals(mListGridLayout.getChildAt(0),
+        assertEquals(mListGridLayout.getSublist(0),
                 mListGridLayout.getParentView(3, false, false));
-        assertEquals(mListGridLayout.getChildAt(1),
+        assertEquals(mListGridLayout.getSublist(1),
                 mListGridLayout.getParentView(4, false, false));
-        assertEquals(mListGridLayout.getChildAt(2),
+        assertEquals(mListGridLayout.getSublist(2),
                 mListGridLayout.getParentView(5, false, false));
-        assertEquals(mListGridLayout.getChildAt(0),
+        assertEquals(mListGridLayout.getSublist(0),
                 mListGridLayout.getParentView(6, false, false));
-        assertEquals(mListGridLayout.getChildAt(1),
+        assertEquals(mListGridLayout.getSublist(1),
                 mListGridLayout.getParentView(7, false, false));
-        assertEquals(mListGridLayout.getChildAt(2),
+        assertEquals(mListGridLayout.getSublist(2),
                 mListGridLayout.getParentView(8, false, false));
 
         // above valid range
-        assertEquals(mListGridLayout.getChildAt(2),
+        assertEquals(mListGridLayout.getSublist(2),
                 mListGridLayout.getParentView(9, false, false));
     }
 
@@ -157,27 +157,27 @@
         assertEquals(null,
                 mListGridLayout.getParentView(-1, true, false));
 
-        assertEquals(mListGridLayout.getChildAt(2),
+        assertEquals(mListGridLayout.getSublist(2),
                 mListGridLayout.getParentView(0, true, false));
-        assertEquals(mListGridLayout.getChildAt(1),
+        assertEquals(mListGridLayout.getSublist(1),
                 mListGridLayout.getParentView(1, true, false));
-        assertEquals(mListGridLayout.getChildAt(0),
+        assertEquals(mListGridLayout.getSublist(0),
                 mListGridLayout.getParentView(2, true, false));
-        assertEquals(mListGridLayout.getChildAt(2),
+        assertEquals(mListGridLayout.getSublist(2),
                 mListGridLayout.getParentView(3, true, false));
-        assertEquals(mListGridLayout.getChildAt(1),
+        assertEquals(mListGridLayout.getSublist(1),
                 mListGridLayout.getParentView(4, true, false));
-        assertEquals(mListGridLayout.getChildAt(0),
+        assertEquals(mListGridLayout.getSublist(0),
                 mListGridLayout.getParentView(5, true, false));
-        assertEquals(mListGridLayout.getChildAt(2),
+        assertEquals(mListGridLayout.getSublist(2),
                 mListGridLayout.getParentView(6, true, false));
-        assertEquals(mListGridLayout.getChildAt(1),
+        assertEquals(mListGridLayout.getSublist(1),
                 mListGridLayout.getParentView(7, true, false));
-        assertEquals(mListGridLayout.getChildAt(0),
+        assertEquals(mListGridLayout.getSublist(0),
                 mListGridLayout.getParentView(8, true, false));
 
         // above valid range
-        assertEquals(mListGridLayout.getChildAt(0),
+        assertEquals(mListGridLayout.getSublist(0),
                 mListGridLayout.getParentView(9, true, false));
     }
 
@@ -189,27 +189,27 @@
         assertEquals(null,
                 mListGridLayout.getParentView(-1, false, true));
 
-        assertEquals(mListGridLayout.getChildAt(0),
+        assertEquals(mListGridLayout.getSublist(0),
                 mListGridLayout.getParentView(0, false, true));
-        assertEquals(mListGridLayout.getChildAt(0),
+        assertEquals(mListGridLayout.getSublist(0),
                 mListGridLayout.getParentView(1, false, true));
-        assertEquals(mListGridLayout.getChildAt(0),
+        assertEquals(mListGridLayout.getSublist(0),
                 mListGridLayout.getParentView(2, false, true));
-        assertEquals(mListGridLayout.getChildAt(1),
+        assertEquals(mListGridLayout.getSublist(1),
                 mListGridLayout.getParentView(3, false, true));
-        assertEquals(mListGridLayout.getChildAt(1),
+        assertEquals(mListGridLayout.getSublist(1),
                 mListGridLayout.getParentView(4, false, true));
-        assertEquals(mListGridLayout.getChildAt(1),
+        assertEquals(mListGridLayout.getSublist(1),
                 mListGridLayout.getParentView(5, false, true));
-        assertEquals(mListGridLayout.getChildAt(2),
+        assertEquals(mListGridLayout.getSublist(2),
                 mListGridLayout.getParentView(6, false, true));
-        assertEquals(mListGridLayout.getChildAt(2),
+        assertEquals(mListGridLayout.getSublist(2),
                 mListGridLayout.getParentView(7, false, true));
-        assertEquals(mListGridLayout.getChildAt(2),
+        assertEquals(mListGridLayout.getSublist(2),
                 mListGridLayout.getParentView(8, false, true));
 
         // above valid range
-        assertEquals(mListGridLayout.getChildAt(2),
+        assertEquals(mListGridLayout.getSublist(2),
                 mListGridLayout.getParentView(9, false, true));
     }
 
@@ -221,35 +221,38 @@
         assertEquals(null,
                 mListGridLayout.getParentView(-1, true, true));
 
-        assertEquals(mListGridLayout.getChildAt(2),
+        assertEquals(mListGridLayout.getSublist(2),
                 mListGridLayout.getParentView(0, true, true));
-        assertEquals(mListGridLayout.getChildAt(2),
+        assertEquals(mListGridLayout.getSublist(2),
                 mListGridLayout.getParentView(1, true, true));
-        assertEquals(mListGridLayout.getChildAt(2),
+        assertEquals(mListGridLayout.getSublist(2),
                 mListGridLayout.getParentView(2, true, true));
-        assertEquals(mListGridLayout.getChildAt(1),
+        assertEquals(mListGridLayout.getSublist(1),
                 mListGridLayout.getParentView(3, true, true));
-        assertEquals(mListGridLayout.getChildAt(1),
+        assertEquals(mListGridLayout.getSublist(1),
                 mListGridLayout.getParentView(4, true, true));
-        assertEquals(mListGridLayout.getChildAt(1),
+        assertEquals(mListGridLayout.getSublist(1),
                 mListGridLayout.getParentView(5, true, true));
-        assertEquals(mListGridLayout.getChildAt(0),
+        assertEquals(mListGridLayout.getSublist(0),
                 mListGridLayout.getParentView(6, true, true));
-        assertEquals(mListGridLayout.getChildAt(0),
+        assertEquals(mListGridLayout.getSublist(0),
                 mListGridLayout.getParentView(7, true, true));
-        assertEquals(mListGridLayout.getChildAt(0),
+        assertEquals(mListGridLayout.getSublist(0),
                 mListGridLayout.getParentView(8, true, true));
 
         // above valid range
-        assertEquals(mListGridLayout.getChildAt(0),
+        assertEquals(mListGridLayout.getSublist(0),
                 mListGridLayout.getParentView(9, true, true));
     }
 
     @Test
     public void testRemoveAllItems() {
-        ViewGroup row1 = (ViewGroup) mListGridLayout.getChildAt(0);
-        ViewGroup row2 = (ViewGroup) mListGridLayout.getChildAt(1);
-        ViewGroup row3 = (ViewGroup) mListGridLayout.getChildAt(2);
+        ViewGroup row1 = mListGridLayout.getSublist(0);
+        row1.setVisibility(View.VISIBLE);
+        ViewGroup row2 = mListGridLayout.getSublist(1);
+        row2.setVisibility(View.VISIBLE);
+        ViewGroup row3 = mListGridLayout.getSublist(2);
+        row3.setVisibility(View.VISIBLE);
         View item1 = new View(mContext, null);
         View item2 = new View(mContext, null);
         View item3 = new View(mContext, null);
@@ -265,7 +268,66 @@
         mListGridLayout.removeAllItems();
 
         assertEquals(0, row1.getChildCount());
+        assertEquals(View.GONE, row1.getVisibility());
         assertEquals(0, row2.getChildCount());
-        assertEquals(0, row2.getChildCount());
+        assertEquals(View.GONE, row2.getVisibility());
+        assertEquals(0, row3.getChildCount());
+        assertEquals(View.GONE, row3.getVisibility());
+    }
+
+    @Test
+    public void testAddItem() {
+        mListGridLayout.setExpectedCount(4);
+
+        View item1 = new View(mContext, null);
+        View item2 = new View(mContext, null);
+        View item3 = new View(mContext, null);
+        View item4 = new View(mContext, null);
+
+        mListGridLayout.addItem(item1);
+        mListGridLayout.addItem(item2);
+        mListGridLayout.addItem(item3);
+        mListGridLayout.addItem(item4);
+        assertEquals(2, mListGridLayout.getSublist(0).getChildCount());
+        assertEquals(2, mListGridLayout.getSublist(1).getChildCount());
+        assertEquals(0, mListGridLayout.getSublist(2).getChildCount());
+
+        mListGridLayout.removeAllItems();
+        mListGridLayout.addItem(item1);
+
+        assertEquals(1, mListGridLayout.getSublist(0).getChildCount());
+        assertEquals(0, mListGridLayout.getSublist(1).getChildCount());
+        assertEquals(0, mListGridLayout.getSublist(2).getChildCount());
+    }
+
+    @Test
+    public void testAddItem_reverseItems() {
+        mListGridLayout.setExpectedCount(3);
+
+        View item1 = new View(mContext, null);
+        View item2 = new View(mContext, null);
+        View item3 = new View(mContext, null);
+
+        mListGridLayout.addItem(item1);
+        mListGridLayout.addItem(item2);
+        mListGridLayout.addItem(item3);
+
+        ViewGroup sublist = mListGridLayout.getSublist(0);
+
+        assertEquals(item1, sublist.getChildAt(0));
+        assertEquals(item2, sublist.getChildAt(1));
+        assertEquals(item3, sublist.getChildAt(2));
+
+
+        mListGridLayout.removeAllItems();
+        mListGridLayout.setReverseItems(true);
+
+        mListGridLayout.addItem(item1);
+        mListGridLayout.addItem(item2);
+        mListGridLayout.addItem(item3);
+
+        assertEquals(item3, sublist.getChildAt(0));
+        assertEquals(item2, sublist.getChildAt(1));
+        assertEquals(item1, sublist.getChildAt(2));
     }
 }
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index c2b35c18..f9aaf11 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -18,7 +18,6 @@
 
 import android.app.IActivityController;
 import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -122,7 +121,6 @@
     /* This handler will be used to post message back onto the main thread */
     final ArrayList<HandlerChecker> mHandlerCheckers = new ArrayList<>();
     final HandlerChecker mMonitorChecker;
-    ContentResolver mResolver;
     ActivityManagerService mActivity;
 
     int mPhonePid;
@@ -138,6 +136,7 @@
         private final String mName;
         private final long mWaitMax;
         private final ArrayList<Monitor> mMonitors = new ArrayList<Monitor>();
+        private final ArrayList<Monitor> mMonitorQueue = new ArrayList<Monitor>();
         private boolean mCompleted;
         private Monitor mCurrentMonitor;
         private long mStartTime;
@@ -150,10 +149,17 @@
         }
 
         void addMonitorLocked(Monitor monitor) {
-            mMonitors.add(monitor);
+            // We don't want to update mMonitors when the Handler is in the middle of checking
+            // all monitors. We will update mMonitors on the next schedule if it is safe
+            mMonitorQueue.add(monitor);
         }
 
         public void scheduleCheckLocked() {
+            if (mCompleted) {
+                // Safe to update monitors in queue, Handler is not in the middle of work
+                mMonitors.addAll(mMonitorQueue);
+                mMonitorQueue.clear();
+            }
             if (mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling()) {
                 // If the target looper has recently been polling, then
                 // there is no reason to enqueue our checker on it since that
@@ -213,6 +219,10 @@
 
         @Override
         public void run() {
+            // Once we get here, we ensure that mMonitors does not change even if we call
+            // #addMonitorLocked because we first add the new monitors to mMonitorQueue and
+            // move them to mMonitors on the next schedule when mCompleted is true, at which
+            // point we have completed execution of this method.
             final int size = mMonitors.size();
             for (int i = 0 ; i < size ; i++) {
                 synchronized (Watchdog.this) {
@@ -304,10 +314,13 @@
                 DEFAULT_TIMEOUT > ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS;
     }
 
+    /**
+     * Registers a {@link BroadcastReceiver} to listen to reboot broadcasts and trigger reboot.
+     * Should be called during boot after the ActivityManagerService is up and registered
+     * as a system service so it can handle registration of a {@link BroadcastReceiver}.
+     */
     public void init(Context context, ActivityManagerService activity) {
-        mResolver = context.getContentResolver();
         mActivity = activity;
-
         context.registerReceiver(new RebootRequestReceiver(),
                 new IntentFilter(Intent.ACTION_REBOOT),
                 android.Manifest.permission.REBOOT, null);
@@ -335,9 +348,6 @@
 
     public void addMonitor(Monitor monitor) {
         synchronized (this) {
-            if (isAlive()) {
-                throw new RuntimeException("Monitors can't be added once the Watchdog is running");
-            }
             mMonitorChecker.addMonitorLocked(monitor);
         }
     }
@@ -348,9 +358,6 @@
 
     public void addThread(Handler thread, long timeoutMillis) {
         synchronized (this) {
-            if (isAlive()) {
-                throw new RuntimeException("Threads can't be added once the Watchdog is running");
-            }
             final String name = thread.getLooper().getThread().getName();
             mHandlerCheckers.add(new HandlerChecker(thread, name, timeoutMillis));
         }
@@ -468,6 +475,7 @@
                     }
                     try {
                         wait(timeout);
+                        // Note: mHandlerCheckers and mMonitorChecker may have changed after waiting
                     } catch (InterruptedException e) {
                         Log.wtf(TAG, e);
                     }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 05be100..668af85 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -416,8 +416,9 @@
         mAudioService.checkMusicActive(deviceType, caller);
     }
 
-    /*package*/ void checkVolumeCecOnHdmiConnection(int state, String caller) {
-        mAudioService.checkVolumeCecOnHdmiConnection(state, caller);
+    /*package*/ void checkVolumeCecOnHdmiConnection(
+            @AudioService.ConnectionState  int state, String caller) {
+        mAudioService.postCheckVolumeCecOnHdmiConnection(state, caller);
     }
 
     /*package*/ boolean hasAudioFocusUsers() {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index f9dbdd5..3948bd8 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -363,8 +363,8 @@
                         "onSetWiredDeviceConnectionState state DISCONNECTED");
             }
 
-            if (!handleDeviceConnection(wdcs.mState == 1, wdcs.mType, wdcs.mAddress,
-                    wdcs.mName)) {
+            if (!handleDeviceConnection(wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED,
+                    wdcs.mType, wdcs.mAddress, wdcs.mName)) {
                 // change of connection state failed, bailout
                 return;
             }
@@ -375,7 +375,9 @@
                 }
                 mDeviceBroker.checkMusicActive(wdcs.mType, wdcs.mCaller);
             }
-            mDeviceBroker.checkVolumeCecOnHdmiConnection(wdcs.mState, wdcs.mCaller);
+            if (wdcs.mType == AudioSystem.DEVICE_OUT_HDMI) {
+                mDeviceBroker.checkVolumeCecOnHdmiConnection(wdcs.mState, wdcs.mCaller);
+            }
             sendDeviceConnectionIntent(wdcs.mType, wdcs.mState, wdcs.mAddress, wdcs.mName);
             updateAudioRoutes(wdcs.mType, wdcs.mState);
         }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index e38defd..c34425f 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -260,6 +260,7 @@
     private static final int MSG_UPDATE_RINGER_MODE = 25;
     private static final int MSG_SET_DEVICE_STREAM_VOLUME = 26;
     private static final int MSG_OBSERVE_DEVICES_FOR_ALL_STREAMS = 27;
+    private static final int MSG_HDMI_VOLUME_CHECK = 28;
     // start of messages handled under wakelock
     //   these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
     //   and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
@@ -1060,11 +1061,19 @@
         }
     }
 
+
     /**
      * Called from AudioDeviceBroker when DEVICE_OUT_HDMI is connected or disconnected.
      */
-    /*package*/ void checkVolumeCecOnHdmiConnection(int state, String caller) {
-        if (state != 0) {
+    /*package*/ void postCheckVolumeCecOnHdmiConnection(
+            @AudioService.ConnectionState  int state, String caller) {
+        sendMsg(mAudioHandler, MSG_HDMI_VOLUME_CHECK, SENDMSG_REPLACE,
+                state /*arg1*/, 0 /*arg2 ignored*/, caller /*obj*/, 0 /*delay*/);
+    }
+
+    private void onCheckVolumeCecOnHdmiConnection(
+            @AudioService.ConnectionState int state, String caller) {
+        if (state == AudioService.CONNECTION_STATE_CONNECTED) {
             // DEVICE_OUT_HDMI is now connected
             if ((AudioSystem.DEVICE_OUT_HDMI & mSafeMediaVolumeDevices) != 0) {
                 sendMsg(mAudioHandler,
@@ -1077,7 +1086,7 @@
             }
 
             if (isPlatformTelevision()) {
-                checkAllFixedVolumeDevices();
+                checkAddAllFixedVolumeDevices(AudioSystem.DEVICE_OUT_HDMI, caller);
                 synchronized (mHdmiClientLock) {
                     if (mHdmiManager != null && mHdmiPlaybackClient != null) {
                         mHdmiCecSink = false;
@@ -1098,6 +1107,21 @@
         }
     }
 
+    private void checkAddAllFixedVolumeDevices(int device, String caller) {
+        final int numStreamTypes = AudioSystem.getNumStreamTypes();
+        for (int streamType = 0; streamType < numStreamTypes; streamType++) {
+            if (!mStreamStates[streamType].hasIndexForDevice(device)) {
+                // set the default value, if device is affected by a full/fix/abs volume rule, it
+                // will taken into account in checkFixedVolumeDevices()
+                mStreamStates[streamType].setIndex(
+                        mStreamStates[mStreamVolumeAlias[streamType]]
+                                .getIndex(AudioSystem.DEVICE_OUT_DEFAULT),
+                        device, caller);
+            }
+            mStreamStates[streamType].checkFixedVolumeDevices();
+        }
+    }
+
     private void checkAllFixedVolumeDevices()
     {
         int numStreamTypes = AudioSystem.getNumStreamTypes();
@@ -5329,6 +5353,9 @@
                 case MSG_OBSERVE_DEVICES_FOR_ALL_STREAMS:
                     onObserveDevicesForAllStreams();
                     break;
+
+                case MSG_HDMI_VOLUME_CHECK:
+                    onCheckVolumeCecOnHdmiConnection(msg.arg1, (String) msg.obj);
             }
         }
     }
@@ -6004,7 +6031,8 @@
                         // HDMI output
                         mFullVolumeDevices &= ~AudioSystem.DEVICE_OUT_HDMI;
                     }
-                    checkAllFixedVolumeDevices();
+                    checkAddAllFixedVolumeDevices(AudioSystem.DEVICE_OUT_HDMI,
+                            "HdmiPlaybackClient.DisplayStatusCallback");
                 }
             }
         }
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index 890456a..476a273 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -81,8 +81,11 @@
     void prepareWebViewInSystemServer() {
         migrateFallbackStateOnBoot();
         mWebViewUpdater.prepareWebViewInSystemServer();
-        mSystemInterface.notifyZygote(isMultiProcessEnabled());
-        AsyncTask.THREAD_POOL_EXECUTOR.execute(this::startZygoteWhenReady);
+        boolean multiProcessEnabled = isMultiProcessEnabled();
+        mSystemInterface.notifyZygote(multiProcessEnabled);
+        if (multiProcessEnabled) {
+            AsyncTask.THREAD_POOL_EXECUTOR.execute(this::startZygoteWhenReady);
+        }
     }
 
     void startZygoteWhenReady() {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 07dddb1..be7dd31 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -620,6 +620,13 @@
      * initialized in one of the other functions.
      */
     private void startBootstrapServices() {
+        // Start the watchdog as early as possible so we can crash the system server
+        // if we deadlock during early boot
+        traceBeginAndSlog("StartWatchdog");
+        final Watchdog watchdog = Watchdog.getInstance();
+        watchdog.start();
+        traceEnd();
+
         Slog.i(TAG, "Reading configuration...");
         final String TAG_SYSTEM_CONFIG = "ReadingSystemConfig";
         traceBeginAndSlog(TAG_SYSTEM_CONFIG);
@@ -764,6 +771,12 @@
         mActivityManagerService.setSystemProcess();
         traceEnd();
 
+        // Complete the watchdog setup with an ActivityManager instance and listen for reboots
+        // Do this only after the ActivityManagerService is properly started as a system process
+        traceBeginAndSlog("InitWatchdog");
+        watchdog.init(mSystemContext, mActivityManagerService);
+        traceEnd();
+
         // DisplayManagerService needs to setup android.display scheduling related policies
         // since setSystemProcess() would have overridden policies due to setProcessGroup
         mDisplayManagerService.setupSchedulerPolicies();
@@ -983,12 +996,6 @@
 
             traceBeginAndSlog("StartAlarmManagerService");
             mSystemServiceManager.startService(new AlarmManagerService(context));
-
-            traceEnd();
-
-            traceBeginAndSlog("InitWatchdog");
-            final Watchdog watchdog = Watchdog.getInstance();
-            watchdog.init(context, mActivityManagerService);
             traceEnd();
 
             traceBeginAndSlog("StartInputManagerService");
@@ -2117,10 +2124,6 @@
             }
             traceEnd();
 
-            traceBeginAndSlog("StartWatchdog");
-            Watchdog.getInstance().start();
-            traceEnd();
-
             // Wait for all packages to be prepared
             mPackageManagerService.waitForAppDataPrepared();