Files
packages_apps_Settings/src/com/android/settings/users/UserSettings.java
Amith Yamasani 5cdc937251 Fix a race condition when switching between settings panels
The fragment was removed while the images were being loaded, so the
activity was null. This fix holds on to the Resources object.

Bug: 7490425
Change-Id: Ic2d8dd3c620fc34ce8255104001e17b39f828601
2012-11-14 16:23:40 -08:00

603 lines
22 KiB
Java

/*
* 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.ActivityManagerNative;
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.SharedPreferences;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.preference.Preference;
import android.preference.PreferenceScreen;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceGroup;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
import android.text.InputType;
import android.util.Log;
import android.util.SparseArray;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import com.android.settings.R;
import com.android.settings.SelectableEditTextPreference;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.Utils;
import com.android.settings.SettingsPreferenceFragment.SettingsDialogFragment;
import java.util.ArrayList;
import java.util.List;
public class UserSettings extends SettingsPreferenceFragment
implements OnPreferenceClickListener, OnClickListener, DialogInterface.OnDismissListener,
Preference.OnPreferenceChangeListener {
private static final String TAG = "UserSettings";
/** UserId of the user being removed */
private static final String SAVE_REMOVING_USER = "removing_user";
/** UserId of the user that was just added */
private static final String SAVE_ADDING_USER = "adding_user";
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 DIALOG_SETUP_USER = 3;
private static final int DIALOG_USER_CANNOT_MANAGE = 4;
private static final int MESSAGE_UPDATE_LIST = 1;
private static final int MESSAGE_SETUP_USER = 2;
private static final String KEY_ADD_USER_LONG_MESSAGE_DISPLAYED =
"key_add_user_long_message_displayed";
private static final int[] USER_DRAWABLES = {
R.drawable.avatar_default_1,
R.drawable.avatar_default_2,
R.drawable.avatar_default_3,
R.drawable.avatar_default_4,
R.drawable.avatar_default_5,
R.drawable.avatar_default_6,
R.drawable.avatar_default_7,
R.drawable.avatar_default_8
};
private PreferenceGroup mUserListCategory;
private Preference mMePreference;
private SelectableEditTextPreference mNicknamePreference;
private int mRemovingUserId = -1;
private int mAddedUserId = 0;
private boolean mAddingUser;
private boolean mProfileExists;
private final Object mUserLock = new Object();
private UserManager mUserManager;
private SparseArray<Drawable> mUserIcons = new SparseArray<Drawable>();
private boolean mIsOwner = UserHandle.myUserId() == UserHandle.USER_OWNER;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_UPDATE_LIST:
updateUserList();
break;
case MESSAGE_SETUP_USER:
onUserCreated(msg.arg1);
break;
}
}
};
private BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_USER_REMOVED)) {
mRemovingUserId = -1;
} else if (intent.getAction().equals(Intent.ACTION_USER_INFO_CHANGED)) {
int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
if (userHandle != -1) {
mUserIcons.remove(userHandle);
}
}
mHandler.sendEmptyMessage(MESSAGE_UPDATE_LIST);
}
};
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
if (icicle != null) {
if (icicle.containsKey(SAVE_ADDING_USER)) {
mAddedUserId = icicle.getInt(SAVE_ADDING_USER);
}
if (icicle.containsKey(SAVE_REMOVING_USER)) {
mRemovingUserId = icicle.getInt(SAVE_REMOVING_USER);
}
}
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 (!mIsOwner) {
mMePreference.setSummary(null);
}
Preference ownerInfo = findPreference("user_owner_info");
if (ownerInfo != null && !mIsOwner) {
ownerInfo.setTitle(R.string.user_info_settings_title);
}
mNicknamePreference = (SelectableEditTextPreference) findPreference(KEY_USER_NICKNAME);
mNicknamePreference.setOnPreferenceChangeListener(this);
mNicknamePreference.getEditText().setInputType(
InputType.TYPE_TEXT_VARIATION_NORMAL | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
mNicknamePreference.setInitialSelectionMode(
SelectableEditTextPreference.SELECTION_SELECT_ALL);
loadProfile();
setHasOptionsMenu(true);
IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_INFO_CHANGED);
getActivity().registerReceiverAsUser(mUserChangeReceiver, UserHandle.ALL, filter, null,
mHandler);
}
@Override
public void onResume() {
super.onResume();
loadProfile();
updateUserList();
}
@Override
public void onDestroy() {
super.onDestroy();
getActivity().unregisterReceiver(mUserChangeReceiver);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(SAVE_ADDING_USER, mAddedUserId);
outState.putInt(SAVE_REMOVING_USER, mRemovingUserId);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if (mIsOwner) {
if (UserManager.getMaxSupportedUsers() > mUserManager.getUsers(false).size()) {
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 {
String nickname = mUserManager.getUserName();
MenuItem removeThisUser = menu.add(0, MENU_REMOVE_USER, 0,
getResources().getString(R.string.user_remove_user_menu, nickname));
removeThisUser.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
}
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
final int itemId = item.getItemId();
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() {
mProfileExists = false;
new AsyncTask<Void, Void, String>() {
@Override
protected void onPostExecute(String result) {
finishLoadProfile(result);
}
@Override
protected String doInBackground(Void... values) {
UserInfo user = mUserManager.getUserInfo(UserHandle.myUserId());
if (user.iconPath == null || user.iconPath.equals("")) {
assignProfilePhoto(user);
}
String profileName = getProfileName();
return profileName;
}
}.execute();
}
private void finishLoadProfile(String profileName) {
mMePreference.setTitle(profileName);
int myUserId = UserHandle.myUserId();
Bitmap b = mUserManager.getUserIcon(myUserId);
if (b != null) {
Drawable d = new BitmapDrawable(b);
mMePreference.setIcon(d);
mUserIcons.put(myUserId, d);
}
}
private void onAddUserClicked() {
synchronized (mUserLock) {
if (mRemovingUserId == -1 && !mAddingUser) {
showDialog(DIALOG_ADD_USER);
}
}
}
private void onRemoveUserClicked(int userId) {
synchronized (mUserLock) {
if (mRemovingUserId == -1 && !mAddingUser) {
mRemovingUserId = userId;
showDialog(DIALOG_CONFIRM_REMOVE);
}
}
}
private void onUserCreated(int userId) {
mAddedUserId = userId;
showDialog(DIALOG_SETUP_USER);
}
@Override
public void onDialogShowing() {
super.onDialogShowing();
setOnDismissListener(this);
}
@Override
public Dialog onCreateDialog(int dialogId) {
switch (dialogId) {
case DIALOG_CONFIRM_REMOVE: {
Dialog dlg = new AlertDialog.Builder(getActivity())
.setTitle(UserHandle.myUserId() == mRemovingUserId
? R.string.user_confirm_remove_self_title
: R.string.user_confirm_remove_title)
.setMessage(UserHandle.myUserId() == mRemovingUserId
? R.string.user_confirm_remove_self_message
: R.string.user_confirm_remove_message)
.setPositiveButton(R.string.user_delete_button,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
removeUserNow();
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
return dlg;
}
case DIALOG_USER_CANNOT_MANAGE:
return new AlertDialog.Builder(getActivity())
.setMessage(R.string.user_cannot_manage_message)
.setPositiveButton(android.R.string.ok, null)
.create();
case DIALOG_ADD_USER: {
final SharedPreferences preferences = getActivity().getPreferences(
Context.MODE_PRIVATE);
final boolean longMessageDisplayed = preferences.getBoolean(
KEY_ADD_USER_LONG_MESSAGE_DISPLAYED, false);
final int messageResId = longMessageDisplayed
? R.string.user_add_user_message_short
: R.string.user_add_user_message_long;
Dialog dlg = new AlertDialog.Builder(getActivity())
.setTitle(R.string.user_add_user_title)
.setMessage(messageResId)
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
addUserNow();
if (!longMessageDisplayed) {
preferences.edit().putBoolean(KEY_ADD_USER_LONG_MESSAGE_DISPLAYED,
true).commit();
}
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
return dlg;
}
case DIALOG_SETUP_USER: {
Dialog dlg = new AlertDialog.Builder(getActivity())
.setTitle(R.string.user_setup_dialog_title)
.setMessage(R.string.user_setup_dialog_message)
.setPositiveButton(R.string.user_setup_button_setup_now,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
switchUserNow(mAddedUserId);
}
})
.setNegativeButton(R.string.user_setup_button_setup_later, null)
.create();
return dlg;
}
default:
return null;
}
}
private void removeUserNow() {
if (mRemovingUserId == UserHandle.myUserId()) {
removeThisUser();
} else {
new Thread() {
public void run() {
synchronized (mUserLock) {
mUserManager.removeUser(mRemovingUserId);
mHandler.sendEmptyMessage(MESSAGE_UPDATE_LIST);
}
}
}.start();
}
}
private void removeThisUser() {
try {
ActivityManagerNative.getDefault().switchUser(UserHandle.USER_OWNER);
((UserManager) getActivity().getSystemService(Context.USER_SERVICE))
.removeUser(UserHandle.myUserId());
} catch (RemoteException re) {
Log.e(TAG, "Unable to remove self user");
}
}
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);
mHandler.sendMessage(mHandler.obtainMessage(
MESSAGE_SETUP_USER, user.id, user.serialNumber));
}
}
}.start();
}
}
private void switchUserNow(int userId) {
try {
ActivityManagerNative.getDefault().switchUser(userId);
} catch (RemoteException re) {
// Nothing to do
}
}
private void updateUserList() {
if (getActivity() == null) return;
List<UserInfo> users = mUserManager.getUsers(true);
mUserListCategory.removeAll();
mUserListCategory.setOrderingAsAdded(false);
final ArrayList<Integer> missingIcons = new ArrayList<Integer>();
for (UserInfo user : users) {
Preference pref;
if (user.id == UserHandle.myUserId()) {
pref = mMePreference;
mNicknamePreference.setText(user.name);
mNicknamePreference.setSummary(user.name);
} 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 (!isInitialized(user)) {
pref.setSummary(R.string.user_summary_not_set_up);
}
}
if (user.iconPath != null) {
if (mUserIcons.get(user.id) == null) {
missingIcons.add(user.id);
pref.setIcon(R.drawable.avatar_default_1);
} else {
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.avatar_default_1);
mUserListCategory.addPreference(pref);
}
getActivity().invalidateOptionsMenu();
// Load the icons
if (missingIcons.size() > 0) {
loadIconsAsync(missingIcons);
}
}
private void loadIconsAsync(List<Integer> missingIcons) {
final Resources resources = getResources();
new AsyncTask<List<Integer>, Void, Void>() {
@Override
protected void onPostExecute(Void result) {
updateUserList();
}
@Override
protected Void doInBackground(List<Integer>... values) {
for (int userId : values[0]) {
Bitmap bitmap = mUserManager.getUserIcon(userId);
Drawable d = new BitmapDrawable(resources, bitmap);
mUserIcons.append(userId, d);
}
return null;
}
}.execute(missingIcons);
}
private void assignProfilePhoto(final UserInfo user) {
if (!Utils.copyMeProfilePhoto(getActivity(), user)) {
assignDefaultPhoto(user);
}
}
private String getProfileName() {
String name = Utils.getMeProfileName(getActivity(), true);
if (name != null) {
mProfileExists = true;
}
return name;
}
private void assignDefaultPhoto(UserInfo user) {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
USER_DRAWABLES[user.id % USER_DRAWABLES.length]);
mUserManager.setUserIcon(user.id, bitmap);
}
private void setPhotoId(Preference pref, UserInfo user) {
Drawable d = mUserIcons.get(user.id); // UserUtils.getUserIcon(mUserManager, user);
if (d != null) {
pref.setIcon(d);
}
}
private void setUserName(String name) {
mUserManager.setUserName(UserHandle.myUserId(), name);
mNicknamePreference.setSummary(name);
getActivity().invalidateOptionsMenu();
}
@Override
public boolean onPreferenceClick(Preference pref) {
if (pref == mMePreference) {
Intent editProfile;
if (!mProfileExists) {
editProfile = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
// TODO: Make this a proper API
editProfile.putExtra("newLocalProfile", true);
} else {
editProfile = new Intent(Intent.ACTION_EDIT, ContactsContract.Profile.CONTENT_URI);
}
// To make sure that it returns back here when done
// TODO: Make this a proper API
editProfile.putExtra("finishActivityOnSaveCompleted", true);
startActivity(editProfile);
} else if (pref instanceof UserPreference) {
int userId = ((UserPreference) pref).getUserId();
// Get the latest status of the user
UserInfo user = mUserManager.getUserInfo(userId);
if (UserHandle.myUserId() != UserHandle.USER_OWNER) {
showDialog(DIALOG_USER_CANNOT_MANAGE);
} else {
if (!isInitialized(user)) {
mHandler.sendMessage(mHandler.obtainMessage(
MESSAGE_SETUP_USER, user.id, user.serialNumber));
}
}
}
return false;
}
private boolean isInitialized(UserInfo user) {
return (user.flags & UserInfo.FLAG_INITIALIZED) != 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;
updateUserList();
}
}
@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;
}
@Override
public int getHelpResource() {
return R.string.help_url_users;
}
}