QS: Make user switcher expand when users don't fit

Also updates the switcher to match the latest redlines.

Bug: 16406694
Change-Id: Ibf44ed9ea2ef4e3c467724eb4c79f1df5b3e49f4
diff --git a/packages/SystemUI/res/layout/data_usage.xml b/packages/SystemUI/res/layout/data_usage.xml
index 63d22b2..8831a05 100644
--- a/packages/SystemUI/res/layout/data_usage.xml
+++ b/packages/SystemUI/res/layout/data_usage.xml
@@ -17,6 +17,9 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:paddingTop="16dp"
+    android:paddingStart="16dp"
+    android:paddingEnd="16dp"
     android:orientation="vertical" >
 
     <TextView
diff --git a/packages/SystemUI/res/layout/qs_detail.xml b/packages/SystemUI/res/layout/qs_detail.xml
index c6a7368..5869bf3 100644
--- a/packages/SystemUI/res/layout/qs_detail.xml
+++ b/packages/SystemUI/res/layout/qs_detail.xml
@@ -14,39 +14,43 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:background="@drawable/qs_detail_background"
-    android:padding="16dp" >
-
-    <TextView
-        android:id="@android:id/button1"
-        style="@style/QSBorderlessButton"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:minWidth="88dp"
-        android:layout_alignParentBottom="true"
-        android:layout_alignParentEnd="true"
-        android:text="@string/quick_settings_done"
-        android:textAppearance="@style/TextAppearance.QS.DetailButton" />
-
-    <TextView
-        android:id="@android:id/button2"
-        style="@style/QSBorderlessButton"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignParentBottom="true"
-        android:layout_marginEnd="8dp"
-        android:minWidth="132dp"
-        android:layout_toStartOf="@android:id/button1"
-        android:text="@string/quick_settings_more_settings"
-        android:textAppearance="@style/TextAppearance.QS.DetailButton" />
-
-    <FrameLayout
-        android:id="@android:id/content"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:layout_above="@android:id/button1" />
+        android:background="@drawable/qs_detail_background"
+        android:paddingBottom="16dp"
+        android:orientation="vertical">
 
-</RelativeLayout>
\ No newline at end of file
+    <FrameLayout
+            android:id="@android:id/content"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1" />
+
+    <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingEnd="16dp"
+            android:gravity="end">
+
+        <TextView
+                android:id="@android:id/button2"
+                style="@style/QSBorderlessButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginEnd="8dp"
+                android:minWidth="132dp"
+                android:text="@string/quick_settings_more_settings"
+                android:textAppearance="@style/TextAppearance.QS.DetailButton" />
+
+        <TextView
+                android:id="@android:id/button1"
+                style="@style/QSBorderlessButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:minWidth="88dp"
+                android:text="@string/quick_settings_done"
+                android:textAppearance="@style/TextAppearance.QS.DetailButton" />
+
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_detail_items.xml b/packages/SystemUI/res/layout/qs_detail_items.xml
index b64005f..f61a43c 100644
--- a/packages/SystemUI/res/layout/qs_detail_items.xml
+++ b/packages/SystemUI/res/layout/qs_detail_items.xml
@@ -17,7 +17,10 @@
 <!-- extends FrameLayout -->
 <com.android.systemui.qs.QSDetailItems xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="match_parent" >
+    android:layout_height="match_parent"
+    android:paddingTop="16dp"
+    android:paddingStart="16dp"
+    android:paddingEnd="16dp">
 
     <LinearLayout
         android:id="@android:id/list"
diff --git a/packages/SystemUI/res/layout/qs_user_detail.xml b/packages/SystemUI/res/layout/qs_user_detail.xml
index 1d6df61..91d3a53 100644
--- a/packages/SystemUI/res/layout/qs_user_detail.xml
+++ b/packages/SystemUI/res/layout/qs_user_detail.xml
@@ -16,14 +16,12 @@
   ~ limitations under the License
   -->
 
-<!-- GridView -->
+<!-- PseudoGridView -->
 <com.android.systemui.qs.tiles.UserDetailView
         xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:sysui="http://schemas.android.com/apk/res-auto"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:verticalSpacing="4dp"
-        android:horizontalSpacing="4dp"
-        android:numColumns="3"
-        android:listSelector="@drawable/ripple_drawable">
-
-</com.android.systemui.qs.tiles.UserDetailView>
\ No newline at end of file
+        sysui:verticalSpacing="4dp"
+        sysui:horizontalSpacing="4dp"
+        style="@style/UserDetailView" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_user_detail_item.xml b/packages/SystemUI/res/layout/qs_user_detail_item.xml
index 5ceed84..2322f16 100644
--- a/packages/SystemUI/res/layout/qs_user_detail_item.xml
+++ b/packages/SystemUI/res/layout/qs_user_detail_item.xml
@@ -25,16 +25,17 @@
         android:orientation="vertical"
         android:gravity="top|center_horizontal"
         android:paddingTop="16dp"
-        android:paddingBottom="20dp"
+        android:minHeight="112dp"
         android:clipChildren="false"
         android:clipToPadding="false"
+        android:background="@drawable/ripple_drawable"
         systemui:activatedFontFamily="sans-serif-medium">
 
     <com.android.systemui.statusbar.phone.UserAvatarView
             android:id="@+id/user_picture"
             android:layout_width="@dimen/max_avatar_size"
             android:layout_height="@dimen/max_avatar_size"
-            android:layout_marginBottom="12dp"
+            android:layout_marginBottom="10dp"
             systemui:frameWidth="2dp"
             systemui:framePadding="6dp"
             systemui:activeFrameColor="@color/current_user_border_color"/>
diff --git a/packages/SystemUI/res/values-sw600dp/styles.xml b/packages/SystemUI/res/values-sw600dp/styles.xml
index 5daf08e..9e5b1d6 100644
--- a/packages/SystemUI/res/values-sw600dp/styles.xml
+++ b/packages/SystemUI/res/values-sw600dp/styles.xml
@@ -34,4 +34,8 @@
         <item name="android:layout_height">@dimen/search_panel_scrim_height</item>
         <item name="android:layout_gravity">bottom</item>
     </style>
+
+    <style name="UserDetailView">
+        <item name="numColumns">4</item>
+    </style>
 </resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 8bd3c39..6ecdca3 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -67,5 +67,10 @@
     <declare-styleable name="DateView">
         <attr name="datePattern" format="string" />
     </declare-styleable>
+    <declare-styleable name="PseudoGridView">
+        <attr name="numColumns" format="integer" />
+        <attr name="verticalSpacing" format="dimension" />
+        <attr name="horizontalSpacing" format="dimension" />
+    </declare-styleable>
 </resources>
 
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 5db6912..baaa379 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -289,4 +289,8 @@
         <item name="android:layout_height">@dimen/search_panel_scrim_height</item>
         <item name="android:layout_gravity">bottom</item>
     </style>
+
+    <style name="UserDetailView">
+        <item name="numColumns">3</item>
+    </style>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java b/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java
new file mode 100644
index 0000000..d58663d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2014 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.qs;
+
+import com.android.systemui.R;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * A view that arranges it's children in a grid with a fixed number of evenly spaced columns.
+ *
+ * {@see android.widget.GridView}
+ */
+public class PseudoGridView extends ViewGroup {
+
+    private int mNumColumns = 3;
+    private int mVerticalSpacing;
+    private int mHorizontalSpacing;
+
+    public PseudoGridView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PseudoGridView);
+
+        final int N = a.getIndexCount();
+        for (int i = 0; i < N; i++) {
+            int attr = a.getIndex(i);
+            switch (attr) {
+                case R.styleable.PseudoGridView_numColumns:
+                    mNumColumns = a.getInt(attr, 3);
+                    break;
+                case R.styleable.PseudoGridView_verticalSpacing:
+                    mVerticalSpacing = a.getDimensionPixelSize(attr, 0);
+                    break;
+                case R.styleable.PseudoGridView_horizontalSpacing:
+                    mHorizontalSpacing = a.getDimensionPixelSize(attr, 0);
+                    break;
+            }
+        }
+
+        a.recycle();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        if (widthMode == MeasureSpec.UNSPECIFIED) {
+            throw new UnsupportedOperationException("Needs a maximum width");
+        }
+        int width = MeasureSpec.getSize(widthMeasureSpec);
+
+        int childWidth = (width - (mNumColumns - 1) * mHorizontalSpacing) / mNumColumns;
+        int childWidthSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
+        int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+        int totalHeight = 0;
+        int children = getChildCount();
+        int rows = (children + mNumColumns - 1) / mNumColumns;
+        for (int row = 0; row < rows; row++) {
+            int startOfRow = row * mNumColumns;
+            int endOfRow = Math.min(startOfRow + mNumColumns, children);
+            int maxHeight = 0;
+            for (int i = startOfRow; i < endOfRow; i++) {
+                View child = getChildAt(i);
+                child.measure(childWidthSpec, childHeightSpec);
+                maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
+            }
+            int maxHeightSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY);
+            for (int i = startOfRow; i < endOfRow; i++) {
+                View child = getChildAt(i);
+                child.measure(childWidthSpec, maxHeightSpec);
+            }
+            totalHeight += maxHeight;
+            if (row > 0) {
+                totalHeight += mVerticalSpacing;
+            }
+        }
+
+        setMeasuredDimension(width, getDefaultSize(totalHeight, heightMeasureSpec));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        int children = getChildCount();
+        int rows = (children + mNumColumns - 1) / mNumColumns;
+        int y = 0;
+        for (int row = 0; row < rows; row++) {
+            int x = 0;
+            int maxHeight = 0;
+            int startOfRow = row * mNumColumns;
+            int endOfRow = Math.min(startOfRow + mNumColumns, children);
+            for (int i = startOfRow; i < endOfRow; i++) {
+                View child = getChildAt(i);
+                int width = child.getMeasuredWidth();
+                int height = child.getMeasuredHeight();
+                child.layout(x, y, x + width, y + height);
+                maxHeight = Math.max(maxHeight, height);
+                x += width + mHorizontalSpacing;
+            }
+            y += maxHeight;
+            if (row > 0) {
+                y += mVerticalSpacing;
+            }
+        }
+    }
+
+    /**
+     * Bridges between a ViewGroup and a BaseAdapter.
+     * <p>
+     * Usage: {@code ViewGroupAdapterBridge.link(viewGroup, adapter)}
+     * <br />
+     * After this call, the ViewGroup's children will be provided by the adapter.
+     */
+    public static class ViewGroupAdapterBridge extends DataSetObserver {
+
+        private final WeakReference<ViewGroup> mViewGroup;
+        private final BaseAdapter mAdapter;
+        private boolean mReleased;
+
+        public static void link(ViewGroup viewGroup, BaseAdapter adapter) {
+            new ViewGroupAdapterBridge(viewGroup, adapter);
+        }
+
+        private ViewGroupAdapterBridge(ViewGroup viewGroup, BaseAdapter adapter) {
+            mViewGroup = new WeakReference<>(viewGroup);
+            mAdapter = adapter;
+            mReleased = false;
+            mAdapter.registerDataSetObserver(this);
+            refresh();
+        }
+
+        private void refresh() {
+            if (mReleased) {
+                return;
+            }
+            ViewGroup viewGroup = mViewGroup.get();
+            if (viewGroup == null) {
+                release();
+                return;
+            }
+            final int childCount = viewGroup.getChildCount();
+            final int adapterCount = mAdapter.getCount();
+            final int N = Math.max(childCount, adapterCount);
+            for (int i = 0; i < N; i++) {
+                if (i < adapterCount) {
+                    View oldView = null;
+                    if (i < childCount) {
+                        oldView = viewGroup.getChildAt(i);
+                    }
+                    View newView = mAdapter.getView(i, oldView, viewGroup);
+                    if (oldView == null) {
+                        // We ran out of existing views. Add it at the end.
+                        viewGroup.addView(newView);
+                    } else if (oldView != newView) {
+                        // We couldn't rebind the view. Replace it.
+                        viewGroup.removeViewAt(i);
+                        viewGroup.addView(newView, i);
+                    }
+                } else {
+                    int lastIndex = viewGroup.getChildCount() - 1;
+                    viewGroup.removeViewAt(lastIndex);
+                }
+            }
+        }
+
+        @Override
+        public void onChanged() {
+            refresh();
+        }
+
+        @Override
+        public void onInvalidated() {
+            release();
+        }
+
+        private void release() {
+            if (!mReleased) {
+                mReleased = true;
+                mAdapter.unregisterDataSetObserver(this);
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 59f3b3d..3679b4c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -326,8 +326,11 @@
         if (mFooter.hasFooter()) {
             h += mFooter.getView().getHeight();
         }
-        mDetail.measure(exactly(width), exactly(h));
-        setMeasuredDimension(width, h);
+        mDetail.measure(exactly(width), MeasureSpec.UNSPECIFIED);
+        if (mDetail.getMeasuredHeight() < h) {
+            mDetail.measure(exactly(width), exactly(h));
+        }
+        setMeasuredDimension(width, Math.max(h, mDetail.getMeasuredHeight()));
     }
 
     private static int exactly(int size) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index 8cff81a..c524edc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -17,45 +17,24 @@
 package com.android.systemui.qs.tiles;
 
 import com.android.systemui.R;
+import com.android.systemui.qs.PseudoGridView;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 
 import android.content.Context;
-import android.content.Intent;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.GridView;
 
 /**
  * Quick settings detail view for user switching.
  */
-public class UserDetailView extends GridView {
+public class UserDetailView extends PseudoGridView {
 
-    public UserDetailView(Context context) {
-        this(context, null);
-    }
+    private Adapter mAdapter;
 
     public UserDetailView(Context context, AttributeSet attrs) {
-        this(context, attrs, android.R.attr.gridViewStyle);
-    }
-
-    public UserDetailView(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public UserDetailView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-
-        setOnItemClickListener(new OnItemClickListener() {
-            @Override
-            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-                UserSwitcherController.UserRecord tag =
-                        (UserSwitcherController.UserRecord) view.getTag();
-                ((Adapter)getAdapter()).switchTo(tag);
-            }
-        });
+        super(context, attrs);
     }
 
     public static UserDetailView inflate(Context context, ViewGroup parent, boolean attach) {
@@ -64,10 +43,12 @@
     }
 
     public void createAndSetAdapter(UserSwitcherController controller) {
-        setAdapter(new Adapter(mContext, controller));
+        mAdapter = new Adapter(mContext, controller);
+        ViewGroupAdapterBridge.link(this, mAdapter);
     }
 
-    public static class Adapter extends UserSwitcherController.BaseUserAdapter {
+    public static class Adapter extends UserSwitcherController.BaseUserAdapter
+            implements OnClickListener {
 
         private Context mContext;
 
@@ -81,6 +62,9 @@
             UserSwitcherController.UserRecord item = getItem(position);
             UserDetailItemView v = UserDetailItemView.convertOrInflate(
                     mContext, convertView, parent);
+            if (v != convertView) {
+                v.setOnClickListener(this);
+            }
             String name = getName(mContext, item);
             if (item.picture == null) {
                 v.bind(name, getDrawable(mContext, item));
@@ -91,5 +75,12 @@
             v.setTag(item);
             return v;
         }
+
+        @Override
+        public void onClick(View view) {
+            UserSwitcherController.UserRecord tag =
+                    (UserSwitcherController.UserRecord) view.getTag();
+            switchTo(tag);
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index c48f3f5..a1993f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -465,12 +465,12 @@
 
         @Override
         public View createDetailView(Context context, View convertView, ViewGroup parent) {
+            UserDetailView v;
             if (!(convertView instanceof UserDetailView)) {
-                convertView = UserDetailView.inflate(context, parent, false);
-            }
-            UserDetailView v = (UserDetailView) convertView;
-            if (v.getAdapter() == null) {
+                v = UserDetailView.inflate(context, parent, false);
                 v.createAndSetAdapter(UserSwitcherController.this);
+            } else {
+                v = (UserDetailView) convertView;
             }
             return v;
         }