Implement real QS user switcher

Replaces the stop-gap user switcher with the real deal.
Dimensions may need some further adjustments.

Bug: 15545213
Change-Id: I4399635c03553dac935049d5b8297fe5f5c1dc9a
diff --git a/packages/SystemUI/res/layout/qs_user_detail.xml b/packages/SystemUI/res/layout/qs_user_detail.xml
index eedae9f..1d6df61 100644
--- a/packages/SystemUI/res/layout/qs_user_detail.xml
+++ b/packages/SystemUI/res/layout/qs_user_detail.xml
@@ -16,9 +16,14 @@
   ~ limitations under the License
   -->
 
-<com.android.systemui.qs.tiles.UserDetail
+<!-- GridView -->
+<com.android.systemui.qs.tiles.UserDetailView
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
-        android:layout_height="match_parent">
-    <include layout="@layout/user_switcher_host" />
-</com.android.systemui.qs.tiles.UserDetail>
\ No newline at end of file
+        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
diff --git a/packages/SystemUI/res/layout/qs_user_detail_item.xml b/packages/SystemUI/res/layout/qs_user_detail_item.xml
new file mode 100644
index 0000000..00b3645
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_user_detail_item.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ 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
+  -->
+
+<com.android.systemui.qs.tiles.UserDetailItemView
+        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="wrap_content"
+        android:orientation="vertical"
+        android:gravity="top|center_horizontal"
+        android:paddingTop="16dp"
+        android:paddingBottom="20dp">
+
+    <com.android.systemui.statusbar.phone.UserAvatarView
+            android:id="@+id/user_picture"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:layout_marginBottom="12dp"
+            systemui:frameWidth="2dp"
+            systemui:activeFrameColor="@color/current_user_border_color"/>
+
+    <TextView
+            android:id="@+id/user_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="14sp"
+            android:text="@string/guest_nickname"/>
+
+</com.android.systemui.qs.tiles.UserDetailItemView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/user_switcher_host.xml b/packages/SystemUI/res/layout/user_switcher_host.xml
deleted file mode 100644
index c1626c6..0000000
--- a/packages/SystemUI/res/layout/user_switcher_host.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  ~ 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
-  -->
-
-<!-- FrameLayout -->
-<com.android.systemui.settings.UserSwitcherHostView
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:tools="http://schemas.android.com/tools"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-
-        <ListView android:id="@android:id/list"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                tools:listitem="@layout/user_switcher_item"/>
-
-</com.android.systemui.settings.UserSwitcherHostView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/user_switcher_item.xml b/packages/SystemUI/res/layout/user_switcher_item.xml
deleted file mode 100644
index 8df2f5a..0000000
--- a/packages/SystemUI/res/layout/user_switcher_item.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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
-  -->
-
-<LinearLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:tools="http://schemas.android.com/tools"
-        android:layout_width="match_parent"
-        android:layout_height="64dp"
-        android:orientation="horizontal"
-        android:gravity="center_vertical"
-        tools:context=".settings.UserSwitcherDialog">
-    <ImageView
-            android:layout_width="48dp"
-            android:layout_height="48dp"
-            android:layout_marginStart="4dp"
-            android:id="@+id/user_picture"
-            tools:src="@drawable/dessert_zombiegingerbread"/>
-    <TextView
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-            android:id="@+id/user_name"
-            android:textAppearance="?android:attr/textAppearanceLarge"
-            android:padding="8dp"
-            android:gravity="center_vertical"
-            tools:text="Hiroshi Lockheimer"
-            />
-    <ImageView
-            android:layout_width="48dp"
-            android:layout_height="48dp"
-            android:layout_marginEnd="4dp"
-            android:src="@*android:drawable/ic_menu_delete"
-            android:id="@+id/user_delete"
-            android:background="?android:attr/selectableItemBackground"/>
-</LinearLayout>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 5f09cbd..4901f40 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -108,6 +108,9 @@
         mHost = host;
     }
 
+    public QSTileHost getHost() {
+        return mHost;
+    }
 
     public void updateResources() {
         final Resources res = mContext.getResources();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetail.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetail.java
deleted file mode 100644
index a9a2724..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetail.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * 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.tiles;
-
-import com.android.systemui.R;
-import com.android.systemui.qs.QSTile;
-
-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.FrameLayout;
-
-/**
- * Quick settings detail view for user switching.
- */
-public class UserDetail extends FrameLayout {
-
-    static final Intent USER_SETTINGS_INTENT = new Intent("android.settings.USER_SETTINGS");
-
-    public UserDetail(Context context) {
-        this(context, null);
-    }
-
-    public UserDetail(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public UserDetail(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public UserDetail(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
-    public static QSTile.DetailAdapter USER_DETAIL_ADAPTER = new QSTile.DetailAdapter() {
-        @Override
-        public int getTitle() {
-            return R.string.quick_settings_user_title;
-        }
-
-        @Override
-        public Boolean getToggleState() {
-            return null;
-        }
-
-        @Override
-        public View createDetailView(Context context, View convertView, ViewGroup parent) {
-            return LayoutInflater.from(context).inflate(R.layout.qs_user_detail, parent, false);
-        }
-
-        @Override
-        public Intent getSettingsIntent() {
-            return USER_SETTINGS_INTENT;
-        }
-
-        @Override
-        public void setToggleState(boolean state) {
-        }
-    };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
new file mode 100644
index 0000000..d765aab
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
@@ -0,0 +1,82 @@
+/*
+ * 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.tiles;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.UserAvatarView;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * Displays one user in the {@link UserDetailView} view.
+ */
+public class UserDetailItemView extends LinearLayout {
+
+    private UserAvatarView mAvatar;
+    private TextView mName;
+
+    public UserDetailItemView(Context context) {
+        this(context, null);
+    }
+
+    public UserDetailItemView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public UserDetailItemView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public UserDetailItemView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    public static UserDetailItemView convertOrInflate(Context context, View convertView,
+            ViewGroup root) {
+        if (!(convertView instanceof UserDetailItemView)) {
+            convertView = LayoutInflater.from(context).inflate(
+                    R.layout.qs_user_detail_item, root, false);
+        }
+        return (UserDetailItemView) convertView;
+    }
+
+    public void bind(String name, Bitmap picture) {
+        mName.setText(name);
+        mAvatar.setBitmap(picture);
+    }
+
+    public void bind(String name, Drawable picture) {
+        mName.setText(name);
+        mAvatar.setDrawable(picture);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        mAvatar = (UserAvatarView) findViewById(R.id.user_picture);
+        mName = (TextView) findViewById(R.id.user_name);
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
new file mode 100644
index 0000000..ec5f28c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -0,0 +1,101 @@
+/*
+ * 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.tiles;
+
+import com.android.systemui.R;
+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 UserDetailView(Context context) {
+        this(context, null);
+    }
+
+    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);
+            }
+        });
+    }
+
+    public static UserDetailView inflate(Context context, ViewGroup parent, boolean attach) {
+        return (UserDetailView) LayoutInflater.from(context).inflate(
+                R.layout.qs_user_detail, parent, attach);
+    }
+
+    public void createAndSetAdapter(UserSwitcherController controller) {
+        setAdapter(new Adapter(mContext, controller));
+    }
+
+    public static class Adapter extends UserSwitcherController.BaseUserAdapter {
+
+        private Context mContext;
+
+        public Adapter(Context context, UserSwitcherController controller) {
+            super(controller);
+            mContext = context;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            UserSwitcherController.UserRecord item = getItem(position);
+            UserDetailItemView v = UserDetailItemView.convertOrInflate(
+                    mContext, convertView, parent);
+            String name;
+            if (item.isGuest) {
+                name = mContext.getString(
+                        item.info == null ? R.string.guest_new_guest : R.string.guest_nickname);
+            } else {
+                name = item.info.name;
+            }
+            if (item.picture == null) {
+                v.bind(name, mContext.getDrawable(R.drawable.ic_account_circle));
+            } else {
+                v.bind(name, item.picture);
+            }
+            v.setActivated(item.isCurrent);
+            v.setTag(item);
+            return v;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java b/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java
deleted file mode 100644
index a5c5862..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * 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.settings;
-
-import com.android.systemui.R;
-
-import android.app.ActivityManagerNative;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.graphics.Bitmap;
-import android.graphics.BitmapShader;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Shader;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.provider.Settings.SettingNotFoundException;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManagerGlobal;
-import android.widget.AdapterView;
-import android.widget.BaseAdapter;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A quick and dirty view to show a user switcher.
- */
-public class UserSwitcherHostView extends FrameLayout
-        implements ListView.OnItemClickListener, View.OnClickListener {
-
-    private static final String TAG = "UserSwitcherDialog";
-
-    private ArrayList<UserInfo> mUserInfo = new ArrayList<UserInfo>();
-    private UserInfo mGuestUser;
-    private Adapter mAdapter = new Adapter();
-    private UserManager mUserManager;
-    private Runnable mFinishRunnable;
-    private ListView mListView;
-    private boolean mGuestUserEnabled;
-
-    public UserSwitcherHostView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-
-        if (isInEditMode()) {
-            return;
-        }
-        mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-
-        mGuestUserEnabled = Settings.Global.getInt(context.getContentResolver(),
-                Settings.Global.GUEST_USER_ENABLED, 0) == 1;
-    }
-
-    public UserSwitcherHostView(Context context, AttributeSet attrs) {
-        this(context, attrs, com.android.internal.R.attr.listViewStyle);
-    }
-
-    public UserSwitcherHostView(Context context) {
-        this(context, null);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mListView = (ListView) findViewById(android.R.id.list);
-        mListView.setAdapter(mAdapter);
-        mListView.setOnItemClickListener(this);
-        refreshUsers();
-    }
-
-    @Override
-    public void onItemClick(AdapterView<?> l, View v, int position, long id) {
-        // Last item is the guest
-        if (position == mUserInfo.size()) {
-            postDelayed(new Runnable() {
-                public void run() {
-                    switchToGuestUser();
-                }
-            }, 100);
-        } else {
-            final int userId = mAdapter.getItem(position).id;
-            postDelayed(new Runnable() {
-                public void run() {
-                    switchUser(userId);
-                }
-            }, 100);
-        }
-    }
-
-    @Override
-    public void onClick(View v) {
-        // Delete was clicked
-        postDelayed(new Runnable() {
-            public void run() {
-                if (mGuestUser != null) {
-                    switchUser(0);
-                    mUserManager.removeUser(mGuestUser.id);
-                    mGuestUser = null;
-                    refreshUsers();
-                }
-            }
-        }, 100);
-    }
-
-    private void switchUser(int userId) {
-        try {
-            WindowManagerGlobal.getWindowManagerService().lockNow(null);
-            ActivityManagerNative.getDefault().switchUser(userId);
-            finish();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Couldn't switch user.", e);
-        }
-    }
-
-    private void switchToGuestUser() {
-        if (mGuestUser == null) {
-            // No guest user. Create one.
-            mGuestUser = mUserManager.createGuest(mContext, 
-                    mContext.getResources().getString(R.string.guest_nickname));
-        }
-        switchUser(mGuestUser.id);
-    }
-
-    private void finish() {
-        if (mFinishRunnable != null) {
-            mFinishRunnable.run();
-        }
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        if (event.getAction() == MotionEvent.ACTION_UP) {
-            finish();
-        }
-        return true;
-    }
-
-    @Override
-    protected void onVisibilityChanged(View changedView, int visibility) {
-        super.onVisibilityChanged(changedView, visibility);
-        // A gross hack to get rid of the switcher when the shade is collapsed.
-        if (visibility != VISIBLE) {
-            finish();
-        }
-    }
-
-    public void setFinishRunnable(Runnable finishRunnable) {
-        mFinishRunnable = finishRunnable;
-    }
-
-    public void refreshUsers() {
-        mUserInfo.clear();
-        mGuestUser = null;
-        List<UserInfo> users = mUserManager.getUsers(true);
-        for (UserInfo user : users) {
-            if (user.isGuest()) {
-                mGuestUser = user;
-            } else if (!user.isManagedProfile()) {
-                mUserInfo.add(user);
-            }
-        }
-        mAdapter.notifyDataSetChanged();
-    }
-
-    private class Adapter extends BaseAdapter {
-
-        @Override
-        public int getCount() {
-            return mUserInfo.size() + (mGuestUserEnabled ? 1 : 0);
-        }
-
-        @Override
-        public UserInfo getItem(int position) {
-            if (position < mUserInfo.size()) {
-                return mUserInfo.get(position);
-            } else {
-                return mGuestUser;
-            }
-        }
-
-        @Override
-        public long getItemId(int position) {
-            if (position < mUserInfo.size()) {
-                return getItem(position).serialNumber;
-            } else {
-                return mGuestUser != null ? mGuestUser.serialNumber : -1;
-            }
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            if (convertView == null || (!(convertView.getTag() instanceof ViewHolder))) {
-                convertView = createView(parent);
-            }
-            ViewHolder h = (ViewHolder) convertView.getTag();
-            bindView(h, getItem(position));
-            return convertView;
-        }
-
-        private View createView(ViewGroup parent) {
-            View v = LayoutInflater.from(getContext()).inflate(
-                    R.layout.user_switcher_item, parent, false);
-            ViewHolder h = new ViewHolder();
-            h.name = (TextView) v.findViewById(R.id.user_name);
-            h.picture = (ImageView) v.findViewById(R.id.user_picture);
-            h.delete = (ImageView) v.findViewById(R.id.user_delete);
-            v.setTag(h);
-            return v;
-        }
-
-        private void bindView(ViewHolder h, UserInfo item) {
-            if (item != null) {
-                h.name.setText(item.name);
-                h.picture.setImageBitmap(circularClip(mUserManager.getUserIcon(item.id)));
-                h.delete.setVisibility(item.isGuest() ? View.VISIBLE : View.GONE);
-                h.delete.setOnClickListener(UserSwitcherHostView.this);
-                if (item.isGuest()) {
-                    h.picture.setImageResource(R.drawable.ic_account_circle);
-                }
-            } else {
-                h.name.setText(R.string.guest_new_guest);
-                h.picture.setImageResource(R.drawable.ic_account_circle);
-                h.delete.setVisibility(View.GONE);
-            }
-        }
-
-        private Bitmap circularClip(Bitmap input) {
-            if (input == null) {
-                return null;
-            }
-            Bitmap output = Bitmap.createBitmap(input.getWidth(),
-                    input.getHeight(), Bitmap.Config.ARGB_8888);
-            Canvas canvas = new Canvas(output);
-            final Paint paint = new Paint();
-            paint.setShader(new BitmapShader(input, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
-            paint.setAntiAlias(true);
-            canvas.drawCircle(input.getWidth() / 2, input.getHeight() / 2, input.getWidth() / 2,
-                    paint);
-            return output;
-        }
-
-        class ViewHolder {
-            TextView name;
-            ImageView picture;
-            ImageView delete;
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
index d32ad50..688c0d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -26,7 +26,7 @@
 import android.widget.FrameLayout;
 
 import com.android.systemui.qs.QSPanel;
-import com.android.systemui.qs.tiles.UserDetail;
+import com.android.systemui.qs.tiles.UserDetailView;
 
 /**
  * Container for image of the multi user switcher (tappable).
@@ -53,7 +53,8 @@
     public void onClick(View v) {
         final UserManager um = UserManager.get(getContext());
         if (um.isUserSwitcherEnabled()) {
-            mQsPanel.showDetailAdapter(true, UserDetail.USER_DETAIL_ADAPTER);
+            mQsPanel.showDetailAdapter(true,
+                    mQsPanel.getHost().getUserSwitcherController().userDetailAdapter);
         } else {
             Intent intent = ContactsContract.QuickContact.composeQuickContactsIntent(
                     getContext(), v, ContactsContract.Profile.CONTENT_URI,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 2c43161..a15a758 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -127,6 +127,7 @@
 import com.android.systemui.statusbar.policy.LocationControllerImpl;
 import com.android.systemui.statusbar.policy.NetworkControllerImpl;
 import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
+import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout.OnChildLocationsChangedListener;
@@ -194,6 +195,7 @@
     VolumeComponent mVolumeComponent;
     KeyguardUserSwitcher mKeyguardUserSwitcher;
     FlashlightController mFlashlightController;
+    UserSwitcherController mUserSwitcherController;
 
     int mNaturalBarHeight = -1;
     int mIconSize = -1;
@@ -691,6 +693,7 @@
         }
 
         mFlashlightController = new FlashlightController(mContext);
+        mUserSwitcherController = new UserSwitcherController(mContext);
 
         // Set up the quick settings tile panel
         mQSPanel = (QSPanel) mStatusBarWindow.findViewById(R.id.quick_settings_panel);
@@ -698,7 +701,8 @@
             final QSTileHost qsh = new QSTileHost(mContext, this,
                     mBluetoothController, mLocationController, mRotationLockController,
                     mNetworkController, mZenModeController, null /*tethering*/,
-                    mCastController, mVolumeComponent, mFlashlightController);
+                    mCastController, mVolumeComponent, mFlashlightController,
+                    mUserSwitcherController);
             mQSPanel.setHost(qsh);
             for (QSTile<?> tile : qsh.getTiles()) {
                 mQSPanel.addTile(tile);
@@ -2326,6 +2330,9 @@
         if (mCastController != null) {
             mCastController.dump(fd, pw, args);
         }
+        if (mUserSwitcherController != null) {
+            mUserSwitcherController.dump(fd, pw, args);
+        }
     }
 
     private String hunStateToString(Entry entry) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index 5fbade1..a599070 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -40,6 +40,7 @@
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.RotationLockController;
 import com.android.systemui.statusbar.policy.TetheringController;
+import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.volume.VolumeComponent;
 
@@ -63,12 +64,14 @@
     private final VolumeComponent mVolume;
     private final ArrayList<QSTile<?>> mTiles = new ArrayList<QSTile<?>>();
     private final FlashlightController mFlashlight;
+    private final UserSwitcherController mUserSwitcherController;
 
     public QSTileHost(Context context, PhoneStatusBar statusBar,
             BluetoothController bluetooth, LocationController location,
             RotationLockController rotation, NetworkController network,
             ZenModeController zen, TetheringController tethering,
-            CastController cast, VolumeComponent volume, FlashlightController flashlight) {
+            CastController cast, VolumeComponent volume, FlashlightController flashlight,
+            UserSwitcherController userSwitcher) {
         mContext = context;
         mStatusBar = statusBar;
         mBluetooth = bluetooth;
@@ -80,6 +83,7 @@
         mCast = cast;
         mVolume = volume;
         mFlashlight = flashlight;
+        mUserSwitcherController = userSwitcher;
 
         final HandlerThread ht = new HandlerThread(QSTileHost.class.getSimpleName());
         ht.start();
@@ -181,4 +185,8 @@
     public FlashlightController getFlashlightController() {
         return mFlashlight;
     }
+
+    public UserSwitcherController getUserSwitcherController() {
+        return mUserSwitcherController;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
new file mode 100644
index 0000000..4640067
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -0,0 +1,279 @@
+/*
+ * 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.statusbar.policy;
+
+import com.android.systemui.R;
+import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.tiles.UserDetailView;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.UserInfo;
+import android.graphics.Bitmap;
+import android.os.AsyncTask;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManagerGlobal;
+import android.widget.BaseAdapter;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Keeps a list of all users on the device for user switching.
+ */
+public class UserSwitcherController {
+
+    private static final String TAG = "UserSwitcherController";
+
+    private final Context mContext;
+    private final UserManager mUserManager;
+    private final ArrayList<WeakReference<BaseUserAdapter>> mAdapters = new ArrayList<>();
+
+    private ArrayList<UserRecord> mUsers = new ArrayList<>();
+
+    public UserSwitcherController(Context context) {
+        mContext = context;
+        mUserManager = UserManager.get(context);
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_USER_ADDED);
+        filter.addAction(Intent.ACTION_USER_REMOVED);
+        filter.addAction(Intent.ACTION_USER_INFO_CHANGED);
+        filter.addAction(Intent.ACTION_USER_SWITCHED);
+        mContext.registerReceiver(mReceiver, filter);
+        refreshUsers();
+    }
+
+    private void refreshUsers() {
+        new AsyncTask<Void, Void, ArrayList<UserRecord>>() {
+
+            @Override
+            protected ArrayList<UserRecord> doInBackground(Void... params) {
+                List<UserInfo> infos = mUserManager.getUsers(true);
+                if (infos == null) {
+                    return null;
+                }
+                ArrayList<UserRecord> records = new ArrayList<>(infos.size());
+                int currentId = ActivityManager.getCurrentUser();
+                UserRecord guestRecord = null;
+
+                for (UserInfo info : infos) {
+                    boolean isCurrent = currentId == info.id;
+                    if (info.isGuest()) {
+                        guestRecord = new UserRecord(info, null /* picture */,
+                                true /* isGuest */, isCurrent);
+                    } else if (!info.isManagedProfile()) {
+                        records.add(new UserRecord(info, mUserManager.getUserIcon(info.id),
+                                false /* isGuest */, isCurrent));
+                    }
+                }
+
+                if (guestRecord == null) {
+                    records.add(new UserRecord(null /* info */, null /* picture */,
+                            true /* isGuest */, false /* isCurrent */));
+                } else {
+                    records.add(guestRecord);
+                }
+
+                return records;
+            }
+
+            @Override
+            protected void onPostExecute(ArrayList<UserRecord> userRecords) {
+                if (userRecords != null) {
+                    mUsers = userRecords;
+                    notifyAdapters();
+                }
+            }
+        }.execute((Void[])null);
+    }
+
+    private void notifyAdapters() {
+        for (int i = mAdapters.size() - 1; i >= 0; i--) {
+            BaseUserAdapter adapter = mAdapters.get(i).get();
+            if (adapter != null) {
+                adapter.notifyDataSetChanged();
+            } else {
+                mAdapters.remove(i);
+            }
+        }
+    }
+
+    public void switchTo(UserRecord record) {
+        int id;
+        if (record.isGuest && record.info == null) {
+            // No guest user. Create one.
+            id = mUserManager.createGuest(mContext,
+                    mContext.getResources().getString(R.string.guest_nickname)).id;
+        } else {
+            id = record.info.id;
+        }
+
+        if (ActivityManager.getCurrentUser() == id) {
+            return;
+        }
+
+        try {
+            WindowManagerGlobal.getWindowManagerService().lockNow(null);
+            ActivityManagerNative.getDefault().switchUser(id);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Couldn't switch user.", e);
+        }
+    }
+
+    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
+                final int currentId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                final int N = mUsers.size();
+                for (int i = 0; i < N; i++) {
+                    UserRecord record = mUsers.get(i);
+                    boolean shouldBeCurrent = record.info.id == currentId;
+                    if (record.isCurrent != shouldBeCurrent) {
+                        mUsers.set(i, record.copyWithIsCurrent(shouldBeCurrent));
+                    }
+                }
+                notifyAdapters();
+            } else {
+                refreshUsers();
+            }
+        }
+    };
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("UserSwitcherController state:");
+        pw.print("  mUsers.size="); pw.println(mUsers.size());
+        for (int i = 0; i < mUsers.size(); i++) {
+            final UserRecord u = mUsers.get(i);
+            pw.print("    "); pw.println(u.toString());
+        }
+    }
+
+    public static abstract class BaseUserAdapter extends BaseAdapter {
+
+        final UserSwitcherController mController;
+
+        protected BaseUserAdapter(UserSwitcherController controller) {
+            mController = controller;
+            controller.mAdapters.add(new WeakReference<>(this));
+        }
+
+        @Override
+        public int getCount() {
+            return mController.mUsers.size();
+        }
+
+        @Override
+        public UserRecord getItem(int position) {
+            return mController.mUsers.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return mController.mUsers.get(position).info.id;
+        }
+
+        public void switchTo(UserRecord record) {
+            mController.switchTo(record);
+        }
+    }
+
+    public static final class UserRecord {
+        public final UserInfo info;
+        public final Bitmap picture;
+        public final boolean isGuest;
+        public final boolean isCurrent;
+
+        public UserRecord(UserInfo info, Bitmap picture, boolean isGuest, boolean isCurrent) {
+            this.info = info;
+            this.picture = picture;
+            this.isGuest = isGuest;
+            this.isCurrent = isCurrent;
+        }
+
+        public UserRecord copyWithIsCurrent(boolean _isCurrent) {
+            return new UserRecord(info, picture, isGuest, _isCurrent);
+        }
+
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("UserRecord(");
+            if (info != null) {
+                sb.append("name=\"" + info.name + "\" id=" + info.id);
+            } else {
+                sb.append("<add guest placeholder>");
+            }
+            if (isGuest) {
+                sb.append(" <isGuest>");
+            }
+            if (isCurrent) {
+                sb.append(" <isCurrent>");
+            }
+            if (picture != null) {
+                sb.append(" <hasPicture>");
+            }
+            sb.append(')');
+            return sb.toString();
+        }
+    }
+
+    public final QSTile.DetailAdapter userDetailAdapter = new QSTile.DetailAdapter() {
+        private final Intent USER_SETTINGS_INTENT = new Intent("android.settings.USER_SETTINGS");
+
+        @Override
+        public int getTitle() {
+            return R.string.quick_settings_user_title;
+        }
+
+        @Override
+        public View createDetailView(Context context, View convertView, ViewGroup parent) {
+            if (!(convertView instanceof UserDetailView)) {
+                convertView = UserDetailView.inflate(context, parent, false);
+            }
+            UserDetailView v = (UserDetailView) convertView;
+            if (v.getAdapter() == null) {
+                v.createAndSetAdapter(UserSwitcherController.this);
+            }
+            return v;
+        }
+
+        @Override
+        public Intent getSettingsIntent() {
+            return USER_SETTINGS_INTENT;
+        }
+
+        @Override
+        public Boolean getToggleState() {
+            return null;
+        }
+
+        @Override
+        public void setToggleState(boolean state) {
+        }
+    };
+}