User management UI: take 2

Single screen user management, also visible on secondary users.
Work in progress, especially with respect to synchronizing with
the Me profile.

Change-Id: Id6e94a85d53356847e4e019c52e3388de9ecb354
This commit is contained in:
Amith Yamasani
2012-09-06 18:04:06 -07:00
parent 1352c142a3
commit dc6bfa6ef5
15 changed files with 522 additions and 383 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
res/drawable/ic_user.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -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>

View File

@@ -4165,39 +4165,34 @@
<!-- User settings screen title [CHAR LIMIT=25] --> <!-- User settings screen title [CHAR LIMIT=25] -->
<string name="user_settings_title">Users</string> <string name="user_settings_title">Users</string>
<!-- User settings user list section header [CHAR LIMIT=30] --> <!-- 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] --> <!-- User settings add user menu [CHAR LIMIT=20] -->
<string name="user_add_user_menu">Add user</string> <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] --> <!-- User details remove user menu [CHAR LIMIT=20] -->
<string name="user_remove_user_menu">Remove user</string> <string name="user_remove_user_menu">Remove user</string>
<!-- User details new user name [CHAR LIMIT=30] --> <!-- 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] --> <!-- User removal confirmation title [CHAR LIMIT=25] -->
<string name="user_confirm_remove_title">Remove user?</string> <string name="user_confirm_remove_title">Remove user?</string>
<!-- User removal confirmation message [CHAR LIMIT=none] --> <!-- 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> <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] --> <!-- Label for are-notifications-enabled checkbox in app details [CHAR LIMIT=20] -->
<string name="app_notifications_switch_label">Show notifications</string> <string name="app_notifications_switch_label">Show notifications</string>

View File

@@ -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>

View File

@@ -18,6 +18,19 @@
xmlns:settings="http://schemas.android.com/apk/res/com.android.settings" xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
android:title="@string/user_settings_title"> 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 <PreferenceCategory
android:key="user_list" android:key="user_list"
android:title="@string/user_list_title"> android:title="@string/user_list_title">

View File

@@ -107,14 +107,13 @@ public class Settings extends PreferenceActivity
R.id.application_settings, R.id.application_settings,
R.id.personal_section, R.id.personal_section,
R.id.security_settings, R.id.security_settings,
R.id.user_settings,
R.id.account_settings, R.id.account_settings,
R.id.account_add, R.id.account_add,
R.id.system_section, R.id.system_section,
R.id.about_settings R.id.about_settings
}; };
private boolean mEnableUserManagement = false;
// TODO: Update Call Settings based on airplane mode state. // TODO: Update Call Settings based on airplane mode state.
protected HashMap<Integer, Integer> mHeaderIndexMap = new HashMap<Integer, Integer>(); protected HashMap<Integer, Integer> mHeaderIndexMap = new HashMap<Integer, Integer>();
@@ -129,11 +128,6 @@ public class Settings extends PreferenceActivity
getWindow().setUiOptions(0); getWindow().setUiOptions(0);
} }
if (android.provider.Settings.Secure.getInt(getContentResolver(), "multiuser_enabled", -1)
> 0) {
mEnableUserManagement = true;
}
mAuthenticatorHelper = new AuthenticatorHelper(); mAuthenticatorHelper = new AuthenticatorHelper();
mAuthenticatorHelper.updateAuthDescriptions(this); mAuthenticatorHelper.updateAuthDescriptions(this);
mAuthenticatorHelper.onAccountsUpdated(this, null); mAuthenticatorHelper.onAccountsUpdated(this, null);
@@ -418,8 +412,7 @@ public class Settings extends PreferenceActivity
int headerIndex = i + 1; int headerIndex = i + 1;
i = insertAccountsHeaders(target, headerIndex); i = insertAccountsHeaders(target, headerIndex);
} else if (id == R.id.user_settings) { } else if (id == R.id.user_settings) {
if (!mEnableUserManagement if (!UserHandle.MU_ENABLED
|| !UserHandle.MU_ENABLED || UserHandle.myUserId() != 0
|| !getResources().getBoolean(R.bool.enable_user_management) || !getResources().getBoolean(R.bool.enable_user_management)
|| Utils.isMonkeyRunning()) { || Utils.isMonkeyRunning()) {
target.remove(header); target.remove(header);

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -16,52 +16,170 @@
package com.android.settings.users; package com.android.settings.users;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.UserInfo; 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.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle; 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.os.UserManager;
import android.preference.EditTextPreference;
import android.preference.Preference; import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener; import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
import android.preference.PreferenceGroup; 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.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; 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.R;
import com.android.settings.SettingsPreferenceFragment; import com.android.settings.SettingsPreferenceFragment;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List; import java.util.List;
public class UserSettings extends SettingsPreferenceFragment 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_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_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 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 @Override
public void onCreate(Bundle icicle) { public void onCreate(Bundle icicle) {
super.onCreate(icicle); super.onCreate(icicle);
mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
addPreferencesFromResource(R.xml.user_settings); addPreferencesFromResource(R.xml.user_settings);
mUserListCategory = (PreferenceGroup) findPreference(KEY_USER_LIST); 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); 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 @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
if (mProfileChanged) {
loadProfile(true);
mProfileChanged = false;
}
updateUserList(); updateUserList();
} }
@Override
public void onDestroy() {
super.onDestroy();
getActivity().getContentResolver().unregisterContentObserver(mProfileObserver);
getActivity().unregisterReceiver(mUserChangeReceiver);
}
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
MenuItem addAccountItem = menu.add(0, MENU_ADD_USER, 0, R.string.user_add_user_menu); if (UserHandle.myUserId() == UserHandle.USER_OWNER) {
addAccountItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM MenuItem addUserItem = menu.add(0, MENU_ADD_USER, 0, R.string.user_add_user_menu);
| MenuItem.SHOW_AS_ACTION_WITH_TEXT); 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 @Override
@@ -70,52 +188,270 @@ public class UserSettings extends SettingsPreferenceFragment
if (itemId == MENU_ADD_USER) { if (itemId == MENU_ADD_USER) {
onAddUserClicked(); onAddUserClicked();
return true; return true;
} else if (itemId == MENU_REMOVE_USER) {
onRemoveUserClicked(UserHandle.myUserId());
return true;
} else { } else {
return super.onOptionsItemSelected(item); 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() { private void onAddUserClicked() {
((PreferenceActivity) getActivity()).startPreferencePanel( synchronized (mUserLock) {
UserDetailsSettings.class.getName(), null, R.string.user_details_title, if (mRemovingUserId == -1 && !mAddingUser) {
null, this, 0); 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() { private void updateUserList() {
List<UserInfo> users = ((UserManager) getActivity().getSystemService(Context.USER_SERVICE)) List<UserInfo> users = mUserManager.getUsers();
.getUsers();
mUserListCategory.removeAll(); mUserListCategory.removeAll();
mUserListCategory.setOrderingAsAdded(false);
for (UserInfo user : users) { for (UserInfo user : users) {
Preference pref = new Preference(getActivity()); Preference pref;
pref.setTitle(user.name); if (user.id == UserHandle.myUserId()) {
pref.setOnPreferenceClickListener(this); pref = mMePreference;
pref.setKey("id=" + user.id); } else {
if (user.iconPath != null) { pref = new UserPreference(getActivity(), null, user.id,
setPhotoId(pref, user.iconPath); 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);
}
}
// 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); mUserListCategory.addPreference(pref);
} }
} }
private void setPhotoId(Preference pref, String realPath) { /* TODO: Put this in an AsyncTask */
Drawable d = Drawable.createFromPath(realPath); 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; if (d == null) return;
pref.setIcon(d); pref.setIcon(d);
} }
private void setUserName(String name) {
mUserManager.setUserName(UserHandle.myUserId(), name);
mNicknamePreference.setSummary(name);
}
@Override @Override
public boolean onPreferenceClick(Preference pref) { public boolean onPreferenceClick(Preference pref) {
String sid = pref.getKey(); if (pref == mMePreference) {
if (sid != null && sid.startsWith("id=")) { Intent editProfile = new Intent(Intent.ACTION_EDIT);
int id = Integer.parseInt(sid.substring(3)); editProfile.setData(ContactsContract.Profile.CONTENT_URI);
Bundle args = new Bundle(); startActivity(editProfile);
args.putInt(UserDetailsSettings.EXTRA_USER_ID, id); }
((PreferenceActivity) getActivity()).startPreferencePanel( return false;
UserDetailsSettings.class.getName(), }
args, 0, pref.getTitle(), this, 0);
@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 true;
} }
return false; return false;
} }
} }