QS Guest fixes

- Fixes NPE when guest has not been created yet
- Reloads pictures only when they changed
- Adds "Exit guest" affordance

Bug: 16363920
Bug: 15759638
Change-Id: I99ff1c4be06fee96c5169fd7c2d31b1b13f7a389
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index b7210e1..5752646 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -70,6 +70,7 @@
     <uses-permission android:name="android.permission.STOP_APP_SWITCHES" />
     <uses-permission android:name="android.permission.SET_SCREEN_COMPATIBILITY" />
     <uses-permission android:name="android.permission.START_ANY_ACTIVITY" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <uses-permission android:name="android.permission.GET_TOP_ACTIVITY_INFO" />
     <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c8c8e9a..f8c9d4c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -703,4 +703,5 @@
 
     <!-- Text shown in place of notification contents when the notification is hidden on a secure lockscreen -->
     <string name="notification_hidden_text">Contents hidden</string>
+    <string name="guest_exit_guest">Exit guest</string>
 </resources>
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 3c647ed..67eef56 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -81,13 +81,7 @@
             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;
-            }
+            String name = getName(mContext, item);
             if (item.picture == null) {
                 v.bind(name, mContext.getDrawable(R.drawable.ic_account_circle_qs));
             } else {
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 4640067..7cc8ed5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -30,8 +30,10 @@
 import android.graphics.Bitmap;
 import android.os.AsyncTask;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManagerGlobal;
@@ -64,15 +66,37 @@
         filter.addAction(Intent.ACTION_USER_REMOVED);
         filter.addAction(Intent.ACTION_USER_INFO_CHANGED);
         filter.addAction(Intent.ACTION_USER_SWITCHED);
-        mContext.registerReceiver(mReceiver, filter);
-        refreshUsers();
+        filter.addAction(Intent.ACTION_USER_STOPPING);
+        mContext.registerReceiverAsUser(mReceiver, UserHandle.OWNER, filter,
+                null /* permission */, null /* scheduler */);
+        refreshUsers(UserHandle.USER_NULL);
     }
 
-    private void refreshUsers() {
-        new AsyncTask<Void, Void, ArrayList<UserRecord>>() {
+    /**
+     * Refreshes users from UserManager.
+     *
+     * The pictures are only loaded if they have not been loaded yet.
+     *
+     * @param forcePictureLoadForId forces the picture of the given user to be reloaded.
+     */
+    private void refreshUsers(int forcePictureLoadForId) {
 
+        SparseArray<Bitmap> bitmaps = new SparseArray<>(mUsers.size());
+        final int N = mUsers.size();
+        for (int i = 0; i < N; i++) {
+            UserRecord r = mUsers.get(i);
+            if (r == null || r.info == null
+                    || r.info.id == forcePictureLoadForId || r.picture == null) {
+                continue;
+            }
+            bitmaps.put(r.info.id, r.picture);
+        }
+
+        new AsyncTask<SparseArray<Bitmap>, Void, ArrayList<UserRecord>>() {
+            @SuppressWarnings("unchecked")
             @Override
-            protected ArrayList<UserRecord> doInBackground(Void... params) {
+            protected ArrayList<UserRecord> doInBackground(SparseArray<Bitmap>... params) {
+                final SparseArray<Bitmap> bitmaps = params[0];
                 List<UserInfo> infos = mUserManager.getUsers(true);
                 if (infos == null) {
                     return null;
@@ -87,8 +111,11 @@
                         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));
+                        Bitmap picture = bitmaps.get(info.id);
+                        if (picture == null) {
+                            picture = mUserManager.getUserIcon(info.id);
+                        }
+                        records.add(new UserRecord(info, picture, false /* isGuest */, isCurrent));
                     }
                 }
 
@@ -109,7 +136,7 @@
                     notifyAdapters();
                 }
             }
-        }.execute((Void[])null);
+        }.execute((SparseArray)bitmaps);
     }
 
     private void notifyAdapters() {
@@ -134,9 +161,16 @@
         }
 
         if (ActivityManager.getCurrentUser() == id) {
+            if (record.isGuest) {
+                exitGuest(id);
+            }
             return;
         }
 
+        switchToUserId(id);
+    }
+
+    private void switchToUserId(int id) {
         try {
             WindowManagerGlobal.getWindowManagerService().lockNow(null);
             ActivityManagerNative.getDefault().switchUser(id);
@@ -145,6 +179,12 @@
         }
     }
 
+    private void exitGuest(int id) {
+        // TODO: show confirmation dialog
+        switchToUserId(UserHandle.USER_OWNER);
+        mUserManager.removeUser(id);
+    }
+
     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -153,15 +193,20 @@
                 final int N = mUsers.size();
                 for (int i = 0; i < N; i++) {
                     UserRecord record = mUsers.get(i);
+                    if (record.info == null) continue;
                     boolean shouldBeCurrent = record.info.id == currentId;
                     if (record.isCurrent != shouldBeCurrent) {
                         mUsers.set(i, record.copyWithIsCurrent(shouldBeCurrent));
                     }
                 }
                 notifyAdapters();
-            } else {
-                refreshUsers();
             }
+            int forcePictureLoadForId = UserHandle.USER_NULL;
+            if (Intent.ACTION_USER_INFO_CHANGED.equals(intent.getAction())) {
+                forcePictureLoadForId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
+                        UserHandle.USER_NULL);
+            }
+            refreshUsers(forcePictureLoadForId);
         }
     };
 
@@ -195,12 +240,25 @@
 
         @Override
         public long getItemId(int position) {
-            return mController.mUsers.get(position).info.id;
+            return position;
         }
 
         public void switchTo(UserRecord record) {
             mController.switchTo(record);
         }
+
+        public String getName(Context context, UserRecord item) {
+            if (item.isGuest) {
+                if (item.isCurrent) {
+                    return context.getString(R.string.guest_exit_guest);
+                } else {
+                    return context.getString(
+                            item.info == null ? R.string.guest_new_guest : R.string.guest_nickname);
+                }
+            } else {
+                return item.info.name;
+            }
+        }
     }
 
     public static final class UserRecord {