OSDN Git Service

User management UI: take 2
authorAmith Yamasani <yamasani@google.com>
Fri, 7 Sep 2012 01:04:06 +0000 (18:04 -0700)
committerAmith Yamasani <yamasani@google.com>
Tue, 11 Sep 2012 01:15:19 +0000 (18:15 -0700)
Single screen user management, also visible on secondary users.
Work in progress, especially with respect to synchronizing with
the Me profile.

Change-Id: Id6e94a85d53356847e4e019c52e3388de9ecb354

15 files changed:
res/drawable/ic_default_user.png [new file with mode: 0644]
res/drawable/ic_user.png [new file with mode: 0644]
res/drawable/ic_user_cyan.png [new file with mode: 0644]
res/drawable/ic_user_green.png [new file with mode: 0644]
res/drawable/ic_user_purple.png [new file with mode: 0644]
res/drawable/ic_user_red.png [new file with mode: 0644]
res/drawable/ic_user_yellow.png [new file with mode: 0644]
res/layout/preference_user_delete_widget.xml [new file with mode: 0644]
res/values/strings.xml
res/xml/user_details.xml [deleted file]
res/xml/user_settings.xml
src/com/android/settings/Settings.java
src/com/android/settings/users/UserDetailsSettings.java [deleted file]
src/com/android/settings/users/UserPreference.java [new file with mode: 0644]
src/com/android/settings/users/UserSettings.java

diff --git a/res/drawable/ic_default_user.png b/res/drawable/ic_default_user.png
new file mode 100644 (file)
index 0000000..ddf797f
Binary files /dev/null and b/res/drawable/ic_default_user.png differ
diff --git a/res/drawable/ic_user.png b/res/drawable/ic_user.png
new file mode 100644 (file)
index 0000000..dc4c390
Binary files /dev/null and b/res/drawable/ic_user.png differ
diff --git a/res/drawable/ic_user_cyan.png b/res/drawable/ic_user_cyan.png
new file mode 100644 (file)
index 0000000..c212615
Binary files /dev/null and b/res/drawable/ic_user_cyan.png differ
diff --git a/res/drawable/ic_user_green.png b/res/drawable/ic_user_green.png
new file mode 100644 (file)
index 0000000..ca09f85
Binary files /dev/null and b/res/drawable/ic_user_green.png differ
diff --git a/res/drawable/ic_user_purple.png b/res/drawable/ic_user_purple.png
new file mode 100644 (file)
index 0000000..b7bdeb2
Binary files /dev/null and b/res/drawable/ic_user_purple.png differ
diff --git a/res/drawable/ic_user_red.png b/res/drawable/ic_user_red.png
new file mode 100644 (file)
index 0000000..c4b4e29
Binary files /dev/null and b/res/drawable/ic_user_red.png differ
diff --git a/res/drawable/ic_user_yellow.png b/res/drawable/ic_user_yellow.png
new file mode 100644 (file)
index 0000000..c46838c
Binary files /dev/null and b/res/drawable/ic_user_yellow.png differ
diff --git a/res/layout/preference_user_delete_widget.xml b/res/layout/preference_user_delete_widget.xml
new file mode 100644 (file)
index 0000000..994b77a
--- /dev/null
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<!-- Used by UserPreference to show the trash icon -->
+<LinearLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:orientation="horizontal">
+    <View
+        android:layout_width="2dip"
+        android:layout_height="match_parent"
+        android:layout_marginTop="5dip"
+        android:layout_marginBottom="5dip"
+        android:background="@android:drawable/divider_horizontal_dark" />
+    <ImageView
+        android:id="@+id/trash_user"
+        android:layout_width="wrap_content"
+        android:layout_height="fill_parent"
+        android:paddingStart="15dip"
+        android:paddingEnd="?android:attr/scrollbarSize"
+        android:src="@drawable/ic_menu_delete_holo_dark"
+        android:contentDescription="@string/user_delete_user_description"
+        android:layout_gravity="center"
+        android:clickable="true"
+        android:focusable="true"
+        android:background="?android:attr/selectableItemBackground" />
+</LinearLayout>
index ba3f386..36eb09d 100644 (file)
     <!-- User settings screen title [CHAR LIMIT=25] -->
     <string name="user_settings_title">Users</string>
     <!-- User settings user list section header [CHAR LIMIT=30] -->
-    <string name="user_list_title">Users</string>
+    <string name="user_list_title">Other users</string>
     <!-- User settings add user menu [CHAR LIMIT=20] -->
     <string name="user_add_user_menu">Add user</string>
+    <!-- User summary to indicate that user is currently active in the background [CHAR LIMIT=100] -->
+    <string name="user_summary_active">Active</string>
+    <!-- User summary to indicate that user is currently inactive in the background [CHAR LIMIT=100] -->
+    <string name="user_summary_inactive">Not active</string>
+    <!-- User information string to represent the owner of the device [CHAR LIMIT=25] -->
+    <string name="user_owner">Owner</string>
+    <!-- Title for the preference to enter the nickname of the userto display in the user switcher [CHAR LIMIT=25]-->
+    <string name="user_nickname">Nickname</string>
+    <!-- Title for add user confirmation dialog [CHAR LIMIT=30] -->
+    <string name="user_add_user_title">Add new user</string>
+    <!-- Message for add user confirmation dialog [CHAR LIMIT=none] -->
+    <string name="user_add_user_message">Adding a user to this device will take them through guided setup.\n\nEach user will have their own apps and personal space on this device.\nUsers can accept permissions for updates on behalf of other users of this device.\nYou can switch between users on the lock screen.</string>
 
-    <!-- User details -->
-    <skip/>
-
-    <!-- User details screen title [CHAR LIMIT=25] -->
-    <string name="user_details_title">Edit details</string>
-    <!-- User information section title [CHAR LIMIT=30] -->
-    <string name="user_information_heading">User information</string>
-    <!-- User name title [CHAR LIMIT=25] -->
-    <string name="user_name_title">Name</string>
-    <!-- User restrictions section title [CHAR LIMIT=30] -->
-    <string name="user_restrictions_heading">Content restrictions</string>
-    <!-- User restrictions, does market require PIN protection [CHAR LIMIT=30] -->
-    <string name="user_market_requires_pin">Require PIN</string>
-    <!-- User restrictions, maximum content rating for apps [CHAR LIMIT=25] -->
-    <string name="user_max_content_rating">Content rating</string>
-    <!-- Section title for list of system apps [CHAR LIMIT=30] -->
-    <string name="user_system_apps_heading">System apps</string>
-    <!-- Section title for list of downloaded apps [CHAR LIMIT=30] -->
-    <string name="user_market_apps_heading">Installed apps</string>
-    <!-- User details discard user menu [CHAR LIMIT=20] -->
-    <string name="user_discard_user_menu">Discard</string>
     <!-- User details remove user menu [CHAR LIMIT=20] -->
     <string name="user_remove_user_menu">Remove user</string>
     <!-- User details new user name [CHAR LIMIT=30] -->
-    <string name="user_new_user_name">Pesky kid</string>
+    <string name="user_new_user_name">New user</string>
     <!-- User removal confirmation title [CHAR LIMIT=25] -->
     <string name="user_confirm_remove_title">Remove user?</string>
     <!-- User removal confirmation message [CHAR LIMIT=none] -->
     <string name="user_confirm_remove_message">Are you sure you want to remove the user and all associated data from the device?</string>
+    <!-- Setting label to show that a new user is being added [CHAR LIMIT=30] -->
+    <string name="user_adding_new_user">Adding new user\u2026</string>
+    <!-- Spoken content description for delete icon beside a user [CHAR LIMIT=none] -->
+    <string name="user_delete_user_description">Delete user</string>
 
     <!-- Label for are-notifications-enabled checkbox in app details [CHAR LIMIT=20] -->
     <string name="app_notifications_switch_label">Show notifications</string>
diff --git a/res/xml/user_details.xml b/res/xml/user_details.xml
deleted file mode 100644 (file)
index f36fd45..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 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.
--->
-
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
-        android:title="@string/user_details_title">
-
-    <PreferenceCategory
-            android:key="information_category"
-            android:title="@string/user_information_heading">
-        <EditTextPreference
-                android:key="user_name"
-                android:title="@string/user_name_title"
-                android:persistent="false"
-                />
-        <Preference
-                android:key="user_picture"
-                android:title="@string/user_picture_title"
-                android:persistent="false"
-                />
-    </PreferenceCategory>
-
-</PreferenceScreen>
index 95bc703..acb7c0f 100644 (file)
         xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
         android:title="@string/user_settings_title">
 
+    <Preference
+            android:key="user_me"
+            android:summary="@string/user_owner" />
+    <EditTextPreference
+            android:key="user_nickname"
+            android:title="@string/user_nickname"
+            android:icon="@drawable/empty_icon" />
+    <Preference
+            android:key="user_owner_info"
+            android:title="@string/owner_info_settings_title"
+            android:icon="@drawable/empty_icon"
+            android:fragment="com.android.settings.OwnerInfoSettings" />
+
     <PreferenceCategory
             android:key="user_list"
             android:title="@string/user_list_title">
index d5a90f6..6f2002a 100644 (file)
@@ -107,14 +107,13 @@ public class Settings extends PreferenceActivity
             R.id.application_settings,
             R.id.personal_section,
             R.id.security_settings,
+            R.id.user_settings,
             R.id.account_settings,
             R.id.account_add,
             R.id.system_section,
             R.id.about_settings
     };
 
-    private boolean mEnableUserManagement = false;
-
     // TODO: Update Call Settings based on airplane mode state.
 
     protected HashMap<Integer, Integer> mHeaderIndexMap = new HashMap<Integer, Integer>();
@@ -129,11 +128,6 @@ public class Settings extends PreferenceActivity
             getWindow().setUiOptions(0);
         }
 
-        if (android.provider.Settings.Secure.getInt(getContentResolver(), "multiuser_enabled", -1)
-                > 0) {
-            mEnableUserManagement = true;
-        }
-
         mAuthenticatorHelper = new AuthenticatorHelper();
         mAuthenticatorHelper.updateAuthDescriptions(this);
         mAuthenticatorHelper.onAccountsUpdated(this, null);
@@ -418,8 +412,7 @@ public class Settings extends PreferenceActivity
                 int headerIndex = i + 1;
                 i = insertAccountsHeaders(target, headerIndex);
             } else if (id == R.id.user_settings) {
-                if (!mEnableUserManagement
-                        || !UserHandle.MU_ENABLED || UserHandle.myUserId() != 0
+                if (!UserHandle.MU_ENABLED
                         || !getResources().getBoolean(R.bool.enable_user_management)
                         || Utils.isMonkeyRunning()) {
                     target.remove(header);
diff --git a/src/com/android/settings/users/UserDetailsSettings.java b/src/com/android/settings/users/UserDetailsSettings.java
deleted file mode 100644 (file)
index 518c6b6..0000000
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * Copyright (C) 2012 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.settings.users;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.UserInfo;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.CompressFormat;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.UserManager;
-import android.preference.CheckBoxPreference;
-import android.preference.EditTextPreference;
-import android.preference.Preference;
-import android.preference.PreferenceGroup;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-
-import com.android.settings.DialogCreatable;
-import com.android.settings.R;
-import com.android.settings.SettingsPreferenceFragment;
-
-import java.util.HashMap;
-import java.util.List;
-
-public class UserDetailsSettings extends SettingsPreferenceFragment
-        implements Preference.OnPreferenceChangeListener, DialogCreatable,
-                   Preference.OnPreferenceClickListener {
-
-    private static final String TAG = "UserDetailsSettings";
-
-    private static final int MENU_REMOVE_USER = Menu.FIRST;
-    private static final int DIALOG_CONFIRM_REMOVE = 1;
-
-    private static final String KEY_USER_NAME = "user_name";
-    private static final String KEY_USER_PICTURE = "user_picture";
-
-    public static final String EXTRA_USER_ID = "user_id";
-
-    private static final int RESULT_PICK_IMAGE = 1;
-    private static final int RESULT_CROP_IMAGE = 2;
-
-    private EditTextPreference mNamePref;
-    private Preference mPicturePref;
-
-    private IPackageManager mIPm;
-    private PackageManager mPm;
-    private UserManager mUm;
-    private int mUserId;
-    private boolean mNewUser;
-
-    @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        addPreferencesFromResource(R.xml.user_details);
-        Bundle args = getArguments();
-        mNewUser = args == null || args.getInt(EXTRA_USER_ID, -1) == -1;
-        mUserId = mNewUser ? -1 : args.getInt(EXTRA_USER_ID, -1);
-        mIPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
-        mUm = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
-
-        if (icicle != null && icicle.containsKey(EXTRA_USER_ID)) {
-            mUserId = icicle.getInt(EXTRA_USER_ID);
-            mNewUser = false;
-        }
-
-        if (mUserId == -1) {
-            mUserId = mUm.createUser(getString(R.string.user_new_user_name), 0).id;
-        }
-        mNamePref = (EditTextPreference) findPreference(KEY_USER_NAME);
-        mNamePref.setOnPreferenceChangeListener(this);
-        mPicturePref = findPreference(KEY_USER_PICTURE);
-        mPicturePref.setOnPreferenceClickListener(this);
-        setHasOptionsMenu(true);
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        mPm = getActivity().getPackageManager();
-        if (mUserId >= 0) {
-            initExistingUser();
-        } else {
-            initNewUser();
-        }
-    }
-
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        outState.putInt(EXTRA_USER_ID, mUserId);
-    }
-
-    @Override
-    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
-        if (mUserId == 0) {
-            return;
-        }
-        MenuItem addAccountItem = menu.add(0, MENU_REMOVE_USER, 0,
-                mNewUser ? R.string.user_discard_user_menu : R.string.user_remove_user_menu);
-        addAccountItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM
-                | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        final int itemId = item.getItemId();
-        if (itemId == MENU_REMOVE_USER) {
-            onRemoveUserClicked();
-            return true;
-        } else {
-            return super.onOptionsItemSelected(item);
-        }
-    }
-
-    private void initExistingUser() {
-        List<UserInfo> users = mUm.getUsers();
-        UserInfo foundUser = null;
-        for (UserInfo user : users) {
-            if (user.id == mUserId) {
-                foundUser = user;
-                break;
-            }
-        }
-        if (foundUser != null) {
-            mNamePref.setSummary(foundUser.name);
-            mNamePref.setText(foundUser.name);
-            if (foundUser.iconPath != null) {
-                setPhotoId(foundUser.iconPath);
-            }
-        }
-    }
-
-    private void initNewUser() {
-        // TODO: Check if there's already a "New user" and localize
-        mNamePref.setText(getString(R.string.user_new_user_name));
-        mNamePref.setSummary(getString(R.string.user_new_user_name));
-    }
-
-    private void onRemoveUserClicked() {
-        if (mNewUser) {
-            removeUserNow();
-        } else {
-            showDialog(DIALOG_CONFIRM_REMOVE);
-        }
-    }
-
-    private void removeUserNow() {
-        mUm.removeUser(mUserId);
-        finish();
-    }
-
-    @Override
-    public boolean onPreferenceChange(Preference preference, Object newValue) {
-        if (preference instanceof CheckBoxPreference) {
-            String packageName = preference.getKey();
-            int newState = ((Boolean) newValue) ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
-                    : PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
-            try {
-                mIPm.setApplicationEnabledSetting(packageName, newState, 0, mUserId);
-            } catch (RemoteException re) {
-                Log.e(TAG, "Unable to change enabled state of package " + packageName
-                        + " for user " + mUserId);
-            }
-        } else if (preference == mNamePref) {
-            String name = (String) newValue;
-            if (TextUtils.isEmpty(name)) {
-                return false;
-            }
-            mUm.setUserName(mUserId, (String) newValue);
-            mNamePref.setSummary((String) newValue);
-        }
-        return true;
-    }
-
-    @Override
-    public Dialog onCreateDialog(int dialogId) {
-        switch (dialogId) {
-            case DIALOG_CONFIRM_REMOVE:
-                return new AlertDialog.Builder(getActivity())
-                    .setTitle(R.string.user_confirm_remove_title)
-                    .setMessage(R.string.user_confirm_remove_message)
-                    .setPositiveButton(android.R.string.ok,
-                        new DialogInterface.OnClickListener() {
-                            public void onClick(DialogInterface dialog, int which) {
-                                removeUserNow();
-                            }
-                    })
-                    .setNegativeButton(android.R.string.cancel, null)
-                    .create();
-            default:
-                return null;
-        }
-    }
-
-    @Override
-    public boolean onPreferenceClick(Preference preference) {
-        if (preference == mPicturePref) {
-            Intent intent = new Intent();
-            intent.setType("image/*");
-            intent.setAction(Intent.ACTION_GET_CONTENT);
-
-            startActivityForResult(intent, RESULT_PICK_IMAGE);
-        }
-        return false;
-    }
-
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        if (resultCode != Activity.RESULT_OK) {
-            return;
-        }
-        switch (requestCode) {
-        case RESULT_PICK_IMAGE:
-            if (data.getData() != null) {
-                Uri imageUri = data.getData();
-                System.err.println("imageUri = " + imageUri);
-                cropImage(imageUri);
-            }
-            break;
-        case RESULT_CROP_IMAGE:
-            saveCroppedImage(data);
-            break;
-        }
-    }
-
-    private void cropImage(Uri imageUri) {
-        final Uri inputPhotoUri = imageUri;
-        Intent intent = new Intent("com.android.camera.action.CROP");
-        intent.setDataAndType(inputPhotoUri, "image/*");
-        intent.putExtra("crop", "true");
-        intent.putExtra("aspectX", 1);
-        intent.putExtra("aspectY", 1);
-        intent.putExtra("outputX", 96);
-        intent.putExtra("outputY", 96);
-        intent.putExtra("return-data", true);
-        startActivityForResult(intent, RESULT_CROP_IMAGE);
-    }
-
-    private void saveCroppedImage(Intent data) {
-        if (data.hasExtra("data")) {
-            Bitmap bitmap = (Bitmap) data.getParcelableExtra("data");
-            ParcelFileDescriptor fd = mUm.setUserIcon(mUserId);
-            if (fd != null) {
-                bitmap.compress(CompressFormat.PNG, 100,
-                        new ParcelFileDescriptor.AutoCloseOutputStream(fd));
-                setPhotoId(mUm.getUserInfo(mUserId).iconPath);
-            }
-        }
-    }
-
-    private void setPhotoId(String realPath) {
-        Drawable d = Drawable.createFromPath(realPath);
-        if (d == null) return;
-        mPicturePref.setIcon(d);
-    }
-}
diff --git a/src/com/android/settings/users/UserPreference.java b/src/com/android/settings/users/UserPreference.java
new file mode 100644 (file)
index 0000000..2ced7ae
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2012 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.settings.users;
+
+import com.android.internal.util.CharSequences;
+import com.android.settings.R;
+
+import android.content.Context;
+import android.os.UserManager;
+import android.preference.Preference;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.View.OnClickListener;
+
+public class UserPreference extends Preference {
+
+    public static final int USERID_UNKNOWN = -10;
+
+    private OnClickListener mDeleteClickListener;
+    private int mSerialNumber = -1;
+    private int mUserId = USERID_UNKNOWN;
+
+    public UserPreference(Context context, AttributeSet attrs) {
+        this(context, attrs, USERID_UNKNOWN, false, null);
+    }
+
+    UserPreference(Context context, AttributeSet attrs, int userId, boolean showDelete,
+            OnClickListener deleteListener) {
+        super(context, attrs);
+        if (showDelete) {
+            setWidgetLayoutResource(R.layout.preference_user_delete_widget);
+            mDeleteClickListener = deleteListener;
+        }
+        mUserId = userId;
+    }
+
+    @Override
+    protected void onBindView(View view) {
+        view.setClickable(true);
+        view.setFocusable(true);
+        View deleteView = view.findViewById(R.id.trash_user);
+        if (deleteView != null) {
+            deleteView.setOnClickListener(mDeleteClickListener);
+            deleteView.setTag(this);
+        }
+        super.onBindView(view);
+    }
+
+    public int getSerialNumber() {
+        if (mSerialNumber < 0) {
+            // If the userId is unknown
+            if (mUserId == USERID_UNKNOWN) return Integer.MAX_VALUE;
+            mSerialNumber = ((UserManager) getContext().getSystemService(Context.USER_SERVICE))
+                    .getUserSerialNumber(mUserId);
+            if (mSerialNumber < 0) return mUserId;
+        }
+        return mSerialNumber;
+    }
+
+    public int getUserId() {
+        return mUserId;
+    }
+
+    public int compareTo(Preference another) {
+        if (another instanceof UserPreference) {
+            return getSerialNumber() > ((UserPreference) another).getSerialNumber() ? 1 : -1;
+        } else {
+            return 1;
+        }
+    }
+}
index e530493..7369a92 100644 (file)
 
 package com.android.settings.users;
 
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.UserInfo;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Bitmap.CompressFormat;
 import android.graphics.drawable.Drawable;
+import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
 import android.os.UserManager;
+import android.preference.EditTextPreference;
 import android.preference.Preference;
 import android.preference.Preference.OnPreferenceClickListener;
-import android.preference.PreferenceActivity;
 import android.preference.PreferenceGroup;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Profile;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.util.Log;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Toast;
 
 import com.android.settings.R;
 import com.android.settings.SettingsPreferenceFragment;
 
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.List;
 
 public class UserSettings extends SettingsPreferenceFragment
-        implements OnPreferenceClickListener {
+        implements OnPreferenceClickListener, OnClickListener, DialogInterface.OnDismissListener,
+        Preference.OnPreferenceChangeListener {
 
+    private static final String TAG = "UserSettings";
+
+    private static final String KEY_USER_NICKNAME = "user_nickname";
     private static final String KEY_USER_LIST = "user_list";
+    private static final String KEY_USER_ME = "user_me";
+
     private static final int MENU_ADD_USER = Menu.FIRST;
+    private static final int MENU_REMOVE_USER = Menu.FIRST+1;
+
+    private static final int DIALOG_CONFIRM_REMOVE = 1;
+    private static final int DIALOG_ADD_USER = 2;
+
+    private static final int MESSAGE_UPDATE_LIST = 1;
+
+    private static final int[] USER_DRAWABLES = {
+        R.drawable.ic_user,
+        R.drawable.ic_user_cyan,
+        R.drawable.ic_user_green,
+        R.drawable.ic_user_purple,
+        R.drawable.ic_user_red,
+        R.drawable.ic_user_yellow
+    };
+
+    private static final String[] CONTACT_PROJECTION = new String[] {
+        Phone._ID,                      // 0
+        Phone.DISPLAY_NAME,             // 1
+    };
 
     private PreferenceGroup mUserListCategory;
+    private Preference mMePreference;
+    private EditTextPreference mNicknamePreference;
+    private int mRemovingUserId = -1;
+    private boolean mAddingUser;
+
+    private final Object mUserLock = new Object();
+    private UserManager mUserManager;
+    private boolean mProfileChanged;
+
+    private Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+            case MESSAGE_UPDATE_LIST:
+                updateUserList();
+                break;
+            }
+        }
+    };
+
+    private ContentObserver mProfileObserver = new ContentObserver(mHandler) {
+        @Override
+        public void onChange(boolean selfChange) {
+            mProfileChanged = true;
+        }
+    };
+
+    private BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mHandler.sendEmptyMessage(MESSAGE_UPDATE_LIST);
+        }
+    };
 
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
+        mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
         addPreferencesFromResource(R.xml.user_settings);
         mUserListCategory = (PreferenceGroup) findPreference(KEY_USER_LIST);
-
+        mMePreference = (Preference) findPreference(KEY_USER_ME);
+        mMePreference.setOnPreferenceClickListener(this);
+        if (UserHandle.myUserId() != UserHandle.USER_OWNER) {
+            mMePreference.setSummary(null);
+        }
+        mNicknamePreference = (EditTextPreference) findPreference(KEY_USER_NICKNAME);
+        mNicknamePreference.setOnPreferenceChangeListener(this);
+        mNicknamePreference.setSummary(mUserManager.getUserInfo(UserHandle.myUserId()).name);
+        loadProfile(false);
         setHasOptionsMenu(true);
+        // Register to watch for profile changes
+        getActivity().getContentResolver().registerContentObserver(
+                ContactsContract.Profile.CONTENT_URI, false, mProfileObserver);
+        getActivity().registerReceiver(mUserChangeReceiver,
+                new IntentFilter(Intent.ACTION_USER_REMOVED));
     }
 
     @Override
     public void onResume() {
         super.onResume();
+        if (mProfileChanged) {
+            loadProfile(true);
+            mProfileChanged = false;
+        }
         updateUserList();
     }
 
     @Override
+    public void onDestroy() {
+        super.onDestroy();
+        getActivity().getContentResolver().unregisterContentObserver(mProfileObserver);
+        getActivity().unregisterReceiver(mUserChangeReceiver);
+    }
+
+    @Override
     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
-        MenuItem addAccountItem = menu.add(0, MENU_ADD_USER, 0, R.string.user_add_user_menu);
-        addAccountItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM
-                | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+        if (UserHandle.myUserId() == UserHandle.USER_OWNER) {
+            MenuItem addUserItem = menu.add(0, MENU_ADD_USER, 0, R.string.user_add_user_menu);
+            addUserItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM
+                    | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+        } else {
+            MenuItem removeThisUser = menu.add(0, MENU_REMOVE_USER, 0, R.string.user_remove_user_menu);
+            removeThisUser.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM
+                    | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+        }
     }
 
     @Override
@@ -70,52 +188,270 @@ public class UserSettings extends SettingsPreferenceFragment
         if (itemId == MENU_ADD_USER) {
             onAddUserClicked();
             return true;
+        } else if (itemId == MENU_REMOVE_USER) {
+            onRemoveUserClicked(UserHandle.myUserId());
+            return true;
         } else {
             return super.onOptionsItemSelected(item);
         }
     }
 
+    private void loadProfile(boolean force) {
+        UserInfo user = mUserManager.getUserInfo(UserHandle.myUserId());
+        if (force || user.iconPath == null || user.iconPath.equals("")) {
+            assignProfilePhoto(user);
+        }
+        String profileName = getProfileName();
+        mMePreference.setTitle(profileName);
+    }
+
     private void onAddUserClicked() {
-        ((PreferenceActivity) getActivity()).startPreferencePanel(
-                UserDetailsSettings.class.getName(), null, R.string.user_details_title,
-                null, this, 0);
+        synchronized (mUserLock) {
+            if (mRemovingUserId == -1 && !mAddingUser) {
+                showDialog(DIALOG_ADD_USER);
+                setOnDismissListener(this);
+            }
+        }
+    }
+
+    private void onRemoveUserClicked(int userId) {
+        synchronized (mUserLock) {
+            if (mRemovingUserId == -1 && !mAddingUser) {
+                mRemovingUserId = userId;
+                showDialog(DIALOG_CONFIRM_REMOVE);
+                setOnDismissListener(this);
+            }
+        }
+    }
+
+    @Override
+    public Dialog onCreateDialog(int dialogId) {
+        switch (dialogId) {
+            case DIALOG_CONFIRM_REMOVE:
+                return new AlertDialog.Builder(getActivity())
+                    .setTitle(R.string.user_confirm_remove_title)
+                    .setMessage(R.string.user_confirm_remove_message)
+                    .setPositiveButton(android.R.string.ok,
+                        new DialogInterface.OnClickListener() {
+                            public void onClick(DialogInterface dialog, int which) {
+                                removeUserNow();
+                            }
+                    })
+                    .setNegativeButton(android.R.string.cancel, null)
+                    .create();
+            case DIALOG_ADD_USER:
+                return new AlertDialog.Builder(getActivity())
+                .setTitle(R.string.user_add_user_title)
+                .setMessage(R.string.user_add_user_message)
+                .setPositiveButton(android.R.string.ok,
+                    new DialogInterface.OnClickListener() {
+                        public void onClick(DialogInterface dialog, int which) {
+                            addUserNow();
+                        }
+                })
+                .setNegativeButton(android.R.string.cancel, null)
+                .create();
+            default:
+                return null;
+        }
+    }
+
+    private void removeUserNow() {
+        if (mRemovingUserId == UserHandle.myUserId()) {
+            removeThisUser();
+        } else {
+            new Thread() {
+                public void run() {
+                    synchronized (mUserLock) {
+                        // TODO: Show some progress while removing the user
+                        mUserManager.removeUser(mRemovingUserId);
+                        mHandler.sendEmptyMessage(MESSAGE_UPDATE_LIST);
+                        mRemovingUserId = -1;
+                    }
+                }
+            }.start();
+        }
+    }
+
+    private void removeThisUser() {
+        // TODO:
+        Toast.makeText(getActivity(), "Not implemented yet!", Toast.LENGTH_SHORT).show();
+
+        synchronized (mUserLock) {
+            mRemovingUserId = -1;
+        }
+    }
+
+    private void addUserNow() {
+        synchronized (mUserLock) {
+            mAddingUser = true;
+            updateUserList();
+            new Thread() {
+                public void run() {
+                    // Could take a few seconds
+                    UserInfo user = mUserManager.createUser(
+                            getActivity().getResources().getString(R.string.user_new_user_name), 0);
+                    if (user != null) {
+                        assignDefaultPhoto(user);
+                    }
+                    synchronized (mUserLock) {
+                        mAddingUser = false;
+                        mHandler.sendEmptyMessage(MESSAGE_UPDATE_LIST);
+                    }
+                }
+            }.start();
+        }
     }
 
     private void updateUserList() {
-        List<UserInfo> users = ((UserManager) getActivity().getSystemService(Context.USER_SERVICE))
-                .getUsers();
+        List<UserInfo> users = mUserManager.getUsers();
 
         mUserListCategory.removeAll();
+        mUserListCategory.setOrderingAsAdded(false);
+
         for (UserInfo user : users) {
-            Preference pref = new Preference(getActivity());
-            pref.setTitle(user.name);
-            pref.setOnPreferenceClickListener(this);
-            pref.setKey("id=" + user.id);
+            Preference pref;
+            if (user.id == UserHandle.myUserId()) {
+                pref = mMePreference;
+            } else {
+                pref = new UserPreference(getActivity(), null, user.id,
+                        UserHandle.myUserId() == UserHandle.USER_OWNER, this);
+                pref.setOnPreferenceClickListener(this);
+                pref.setKey("id=" + user.id);
+                mUserListCategory.addPreference(pref);
+                if (user.id == UserHandle.USER_OWNER) {
+                    pref.setSummary(R.string.user_owner);
+                }
+                pref.setTitle(user.name);
+            }
             if (user.iconPath != null) {
-                setPhotoId(pref, user.iconPath);
+                setPhotoId(pref, user);
             }
+        }
+        // Add a temporary entry for the user being created
+        if (mAddingUser) {
+            Preference pref = new UserPreference(getActivity(), null, UserPreference.USERID_UNKNOWN,
+                    false, null);
+            pref.setEnabled(false);
+            pref.setTitle(R.string.user_new_user_name);
+            pref.setSummary(R.string.user_adding_new_user);
+            pref.setIcon(R.drawable.ic_user);
             mUserListCategory.addPreference(pref);
         }
     }
 
-    private void setPhotoId(Preference pref, String realPath) {
-        Drawable d = Drawable.createFromPath(realPath);
+    /* TODO: Put this in an AsyncTask */
+    private void assignProfilePhoto(final UserInfo user) {
+        // If the contact is "me", then use my local profile photo. Otherwise, build a
+        // uri to get the avatar of the contact.
+        Uri contactUri = Profile.CONTENT_URI;
+
+        InputStream avatarDataStream = Contacts.openContactPhotoInputStream(
+                    getActivity().getContentResolver(),
+                    contactUri, true);
+        // If there's no profile photo, assign a default avatar
+        if (avatarDataStream == null) {
+            assignDefaultPhoto(user);
+            setPhotoId(mMePreference, user);
+            return;
+        }
+
+        ParcelFileDescriptor fd = mUserManager.setUserIcon(user.id);
+        FileOutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+        byte[] buffer = new byte[4096];
+        int readSize;
+        try {
+            while ((readSize = avatarDataStream.read(buffer)) > 0) {
+                os.write(buffer, 0, readSize);
+            }
+            os.close();
+            avatarDataStream.close();
+        } catch (IOException ioe) {
+            Log.e(TAG, "Error copying profile photo " + ioe);
+        }
+
+        setPhotoId(mMePreference, user);
+    }
+
+    private String getProfileName() {
+        Cursor cursor = getActivity().getContentResolver().query(
+                    Profile.CONTENT_URI, CONTACT_PROJECTION, null, null, null);
+        if (cursor == null) {
+            Log.w(TAG, "getProfileName() returned NULL cursor!"
+                    + " contact uri used " + Profile.CONTENT_URI);
+            return null;
+        }
+
+        try {
+            if (cursor.moveToFirst()) {
+                return cursor.getString(cursor.getColumnIndex(Phone.DISPLAY_NAME));
+            }
+        } finally {
+            cursor.close();
+        }
+        return null;
+    }
+
+    private void assignDefaultPhoto(UserInfo user) {
+        Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
+                USER_DRAWABLES[user.id % USER_DRAWABLES.length]);
+        ParcelFileDescriptor fd = mUserManager.setUserIcon(user.id);
+        if (fd != null) {
+            bitmap.compress(CompressFormat.PNG, 100,
+                    new ParcelFileDescriptor.AutoCloseOutputStream(fd));
+        }
+    }
+
+    private void setPhotoId(Preference pref, UserInfo user) {
+        ParcelFileDescriptor fd = mUserManager.setUserIcon(user.id);
+        Drawable d = Drawable.createFromStream(new ParcelFileDescriptor.AutoCloseInputStream(fd),
+                user.iconPath);
         if (d == null) return;
         pref.setIcon(d);
     }
 
+    private void setUserName(String name) {
+        mUserManager.setUserName(UserHandle.myUserId(), name);
+        mNicknamePreference.setSummary(name);
+    }
+
     @Override
     public boolean onPreferenceClick(Preference pref) {
-        String sid = pref.getKey();
-        if (sid != null && sid.startsWith("id=")) {
-            int id = Integer.parseInt(sid.substring(3));
-            Bundle args = new Bundle();
-            args.putInt(UserDetailsSettings.EXTRA_USER_ID, id);
-            ((PreferenceActivity) getActivity()).startPreferencePanel(
-                    UserDetailsSettings.class.getName(),
-                    args, 0, pref.getTitle(), this, 0);
+        if (pref == mMePreference) {
+            Intent editProfile = new Intent(Intent.ACTION_EDIT);
+            editProfile.setData(ContactsContract.Profile.CONTENT_URI);
+            startActivity(editProfile);
+        }
+        return false;
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (v.getTag() instanceof UserPreference) {
+            int userId = ((UserPreference) v.getTag()).getUserId();
+            onRemoveUserClicked(userId);
+        }
+    }
+
+    @Override
+    public void onDismiss(DialogInterface dialog) {
+        synchronized (mUserLock) {
+            mAddingUser = false;
+            mRemovingUserId = -1;
+        }
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        if (preference == mNicknamePreference) {
+            String value = (String) newValue;
+            if (preference == mNicknamePreference && value != null
+                    && value.length() > 0) {
+                setUserName(value);
+            }
             return true;
         }
         return false;
     }
+
 }