Guest user first iteration

Setting for controlling if guest is enabled on the device.
Setting to hint to apps that they should skip showing first use clings.

User switcher handles creation and deletion of the guest user.
Some tweaks to the user switcher to show some feedback and make the icons
circular.

Change-Id: I187dc381d2ee7c372ec6d35e14aa9ea4dfbe5936
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index cd47099..e77ef95 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -39,9 +39,6 @@
     UserInfo getProfileParent(int userHandle);
     UserInfo getUserInfo(int userHandle);
     boolean isRestricted();
-    void setGuestEnabled(boolean enable);
-    boolean isGuestEnabled();
-    void wipeUser(int userHandle);
     int getUserSerialNumber(int userHandle);
     int getUserHandle(int userSerialNumber);
     Bundle getUserRestrictions(int userHandle);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 91fbb9d..eb3c748 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -25,6 +25,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.provider.Settings;
 import android.util.Log;
 
 import com.android.internal.R;
@@ -361,6 +362,16 @@
     }
 
     /**
+     * Checks if the calling app is running as a guest user.
+     * @return whether the caller is a guest user.
+     * @hide
+     */
+    public boolean isGuestUser() {
+        UserInfo user = getUserInfo(UserHandle.myUserId());
+        return user != null ? user.isGuest() : false;
+    }
+
+    /**
      * Return whether the given user is actively running.  This means that
      * the user is in the "started" state, not "stopped" -- it is currently
      * allowed to run code through scheduled alarms, receiving broadcasts,
@@ -550,6 +561,21 @@
     }
 
     /**
+     * Creates a guest user and configures it.
+     * @param context an application context
+     * @param name the name to set for the user
+     * @hide
+     */
+    public UserInfo createGuest(Context context, String name) {
+        UserInfo guest = createUser(name, UserInfo.FLAG_GUEST);
+        if (guest != null) {
+            Settings.Secure.putStringForUser(context.getContentResolver(),
+                    Settings.Secure.SKIP_FIRST_USE_HINTS, "1", guest.id);
+        }
+        return guest;
+    }
+
+    /**
      * Creates a user with the specified name and options as a profile of another user.
      * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
      *
@@ -827,50 +853,6 @@
     }
 
     /**
-     * Enable or disable the use of a guest account. If disabled, the existing guest account
-     * will be wiped.
-     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
-     * @param enable whether to enable a guest account.
-     * @hide
-     */
-    public void setGuestEnabled(boolean enable) {
-        try {
-            mService.setGuestEnabled(enable);
-        } catch (RemoteException re) {
-            Log.w(TAG, "Could not change guest account availability to " + enable);
-        }
-    }
-
-    /**
-     * Checks if a guest user is enabled for this device.
-     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
-     * @return whether a guest user is enabled
-     * @hide
-     */
-    public boolean isGuestEnabled() {
-        try {
-            return mService.isGuestEnabled();
-        } catch (RemoteException re) {
-            Log.w(TAG, "Could not retrieve guest enabled state");
-            return false;
-        }
-    }
-
-    /**
-     * Wipes all the data for a user, but doesn't remove the user.
-     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
-     * @param userHandle
-     * @hide
-     */
-    public void wipeUser(int userHandle) {
-        try {
-            mService.wipeUser(userHandle);
-        } catch (RemoteException re) {
-            Log.w(TAG, "Could not wipe user " + userHandle);
-        }
-    }
-
-    /**
      * Returns the maximum number of users that can be created on this device. A return value
      * of 1 means that it is a single user device.
      * @hide
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1001677..3fe0fb8 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4576,6 +4576,16 @@
         public static final String DISPLAY_INTERCEPTED_NOTIFICATIONS = "display_intercepted_notifications";
 
         /**
+         * If enabled, apps should try to skip any introductory hints on first launch. This might
+         * apply to users that are already familiar with the environment or temporary users, like
+         * guests.
+         * <p>
+         * Type : int (0 to show hints, 1 to skip showing hints)
+         * @hide
+         */
+        public static final String SKIP_FIRST_USE_HINTS = "skip_first_use_hints";
+
+        /**
          * This are the settings to be backed up.
          *
          * NOTE: Settings are backed up and restored in the order they appear
@@ -6216,6 +6226,14 @@
         public static final String DEVICE_NAME = "device_name";
 
         /**
+         * Whether it should be possible to create a guest user on the device.
+         * <p>
+         * Type: int (0 for disabled, 1 for enabled)
+         * @hide
+         */
+        public static final String GUEST_USER_ENABLED = "guest_user_enabled";
+
+        /**
          * Settings to backup. This is here so that it's in the same place as the settings
          * keys and easy to update.
          *
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 0e025a9..a92ab7e 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -195,4 +195,7 @@
     <!-- Default for Settings.Secure.WAKE_GESTURE_ENABLED -->
     <bool name="def_wake_gesture_enabled">true</bool>
 
+    <!-- Default for Settings.Global.GUEST_USER_ENABLED -->
+    <bool name="def_guest_user_enabled">true</bool>
+
 </resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 9b21ae4..09e6a94 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -70,7 +70,7 @@
     // database gets upgraded properly. At a minimum, please confirm that 'upgradeVersion'
     // is properly propagated through your change.  Not doing so will result in a loss of user
     // settings.
-    private static final int DATABASE_VERSION = 104;
+    private static final int DATABASE_VERSION = 105;
 
     private Context mContext;
     private int mUserHandle;
@@ -1677,6 +1677,24 @@
             upgradeVersion = 104;
         }
 
+        if (upgradeVersion < 105) {
+            if (mUserHandle == UserHandle.USER_OWNER) {
+                db.beginTransaction();
+                SQLiteStatement stmt = null;
+                try {
+                    stmt = db.compileStatement("INSERT OR IGNORE INTO global(name,value)"
+                            + " VALUES(?,?);");
+                    loadBooleanSetting(stmt, Settings.Global.GUEST_USER_ENABLED,
+                            R.bool.def_guest_user_enabled);
+                    db.setTransactionSuccessful();
+                } finally {
+                    db.endTransaction();
+                    if (stmt != null) stmt.close();
+                }
+            }
+            upgradeVersion = 105;
+        }
+
         // *** Remember to update DATABASE_VERSION above!
 
         if (upgradeVersion != currentVersion) {
@@ -2410,6 +2428,8 @@
 
             loadSetting(stmt, Settings.Global.DEVICE_NAME, getDefaultDeviceName());
 
+            loadBooleanSetting(stmt, Settings.Global.GUEST_USER_ENABLED,
+                    R.bool.def_guest_user_enabled);
             // --- New global settings start here
         } finally {
             if (stmt != null) stmt.close();
diff --git a/packages/SystemUI/res/layout/user_switcher_item.xml b/packages/SystemUI/res/layout/user_switcher_item.xml
index 43a85e7..8df2f5a 100644
--- a/packages/SystemUI/res/layout/user_switcher_item.xml
+++ b/packages/SystemUI/res/layout/user_switcher_item.xml
@@ -21,10 +21,12 @@
         android:layout_width="match_parent"
         android:layout_height="64dp"
         android:orientation="horizontal"
+        android:gravity="center_vertical"
         tools:context=".settings.UserSwitcherDialog">
     <ImageView
-            android:layout_width="64dp"
-            android:layout_height="match_parent"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:layout_marginStart="4dp"
             android:id="@+id/user_picture"
             tools:src="@drawable/dessert_zombiegingerbread"/>
     <TextView
@@ -37,4 +39,11 @@
             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/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ed32795..c5bb1ae 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -596,6 +596,13 @@
     <!-- Indication on the keyguard that is shown when the device is charging. [CHAR LIMIT=40]-->
     <string name="keyguard_indication_charging_time">Charging (<xliff:g id="charging_time_left" example="4 hours and 2 minutes">%s</xliff:g> until full)</string>
 
+    <!-- Related to user switcher --><skip/>
+    <!-- Name for the guest user -->
+    <string name="guest_nickname">Guest</string>
+
+    <!-- Label for adding a new guest -->
+    <string name="guest_new_guest">+ Guest</string>
+
     <!-- Zen mode condition: time duration in minutes. [CHAR LIMIT=NONE] -->
     <plurals name="zen_mode_duration_minutes">
         <item quantity="one">For one minute</item>
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java b/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java
index d67e7cb..a3b10f2 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java
@@ -21,8 +21,16 @@
 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;
@@ -43,15 +51,18 @@
 /**
  * A quick and dirty view to show a user switcher.
  */
-public class UserSwitcherHostView extends FrameLayout implements ListView.OnItemClickListener {
+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);
@@ -60,6 +71,9 @@
             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) {
@@ -80,7 +94,39 @@
 
     @Override
     public void onItemClick(AdapterView<?> l, View v, int position, long id) {
-        int userId = mAdapter.getItem(position).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);
@@ -90,6 +136,15 @@
         }
     }
 
+    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();
@@ -119,9 +174,12 @@
 
     public void refreshUsers() {
         mUserInfo.clear();
+        mGuestUser = null;
         List<UserInfo> users = mUserManager.getUsers(true);
         for (UserInfo user : users) {
-            if (!user.isManagedProfile()) {
+            if (user.isGuest()) {
+                mGuestUser = user;
+            } else if (!user.isManagedProfile()) {
                 mUserInfo.add(user);
             }
         }
@@ -132,17 +190,25 @@
 
         @Override
         public int getCount() {
-            return mUserInfo.size();
+            return mUserInfo.size() + (mGuestUserEnabled ? 1 : 0);
         }
 
         @Override
         public UserInfo getItem(int position) {
-            return mUserInfo.get(position);
+            if (position < mUserInfo.size()) {
+                return mUserInfo.get(position);
+            } else {
+                return mGuestUser;
+            }
         }
 
         @Override
         public long getItemId(int position) {
-            return getItem(position).serialNumber;
+            if (position < mUserInfo.size()) {
+                return getItem(position).serialNumber;
+            } else {
+                return mGuestUser != null ? mGuestUser.serialNumber : -1;
+            }
         }
 
         @Override
@@ -161,18 +227,46 @@
             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) {
-            h.name.setText(item.name);
-            h.picture.setImageBitmap(mUserManager.getUserIcon(item.id));
+            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/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b7bc2e1..0cb2ab9 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -161,7 +161,6 @@
     private final SparseBooleanArray mRemovingUserIds = new SparseBooleanArray();
 
     private int[] mUserIds;
-    private boolean mGuestEnabled;
     private int mNextSerialNumber;
     private int mUserVersion = 0;
 
@@ -427,43 +426,6 @@
         }
     }
 
-    @Override
-    public void setGuestEnabled(boolean enable) {
-        checkManageUsersPermission("enable guest users");
-        synchronized (mPackagesLock) {
-            if (mGuestEnabled != enable) {
-                mGuestEnabled = enable;
-                // Erase any guest user that currently exists
-                for (int i = 0; i < mUsers.size(); i++) {
-                    UserInfo user = mUsers.valueAt(i);
-                    if (!user.partial && user.isGuest()) {
-                        if (!enable) {
-                            removeUser(user.id);
-                        }
-                        return;
-                    }
-                }
-                // No guest was found
-                if (enable) {
-                    createUser("Guest", UserInfo.FLAG_GUEST);
-                }
-            }
-        }
-    }
-
-    @Override
-    public boolean isGuestEnabled() {
-        synchronized (mPackagesLock) {
-            return mGuestEnabled;
-        }
-    }
-
-    @Override
-    public void wipeUser(int userHandle) {
-        checkManageUsersPermission("wipe user");
-        // TODO:
-    }
-
     public void makeInitialized(int userId) {
         checkManageUsersPermission("makeInitialized");
         synchronized (mPackagesLock) {
@@ -583,7 +545,6 @@
     }
 
     private void readUserListLocked() {
-        mGuestEnabled = false;
         if (!mUserListFile.exists()) {
             fallbackToSingleUserLocked();
             return;
@@ -625,9 +586,6 @@
 
                     if (user != null) {
                         mUsers.put(user.id, user);
-                        if (user.isGuest()) {
-                            mGuestEnabled = true;
-                        }
                         if (mNextSerialNumber < 0 || mNextSerialNumber <= user.id) {
                             mNextSerialNumber = user.id + 1;
                         }