diff options
Diffstat (limited to 'src/com/android/settings/users')
10 files changed, 1081 insertions, 788 deletions
diff --git a/src/com/android/settings/users/AppRestrictionsFragment.java b/src/com/android/settings/users/AppRestrictionsFragment.java index fcaf18f..d717489 100644 --- a/src/com/android/settings/users/AppRestrictionsFragment.java +++ b/src/com/android/settings/users/AppRestrictionsFragment.java @@ -17,7 +17,6 @@ package com.android.settings.users; import android.app.Activity; -import android.app.AppGlobals; import android.appwidget.AppWidgetManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -58,13 +57,13 @@ import android.view.View.OnClickListener; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; import android.view.ViewGroup; -import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.Switch; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.drawable.CircleFramedDrawable; import java.util.ArrayList; import java.util.Collections; @@ -161,34 +160,17 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen private boolean panelOpen; private boolean immutable; private List<Preference> mChildren = new ArrayList<Preference>(); - private final ColorFilter grayscaleFilter; AppRestrictionsPreference(Context context, OnClickListener listener) { super(context); setLayoutResource(R.layout.preference_app_restrictions); this.listener = listener; - - ColorMatrix colorMatrix = new ColorMatrix(); - colorMatrix.setSaturation(0f); - float[] matrix = colorMatrix.getArray(); - matrix[18] = 0.5f; - grayscaleFilter = new ColorMatrixColorFilter(colorMatrix); } private void setSettingsEnabled(boolean enable) { hasSettings = enable; } - @Override - public void setChecked(boolean checked) { - if (checked) { - getIcon().setColorFilter(null); - } else { - getIcon().setColorFilter(grayscaleFilter); - } - super.setChecked(checked); - } - void setRestrictions(ArrayList<RestrictionEntry> restrictions) { this.restrictions = restrictions; } @@ -248,6 +230,8 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen final Switch toggle = (Switch) widget.getChildAt(0); toggle.setEnabled(!isImmutable()); toggle.setTag(this); + toggle.setClickable(true); + toggle.setFocusable(true); toggle.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { @@ -346,7 +330,7 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen return getPreferenceScreen(); } - protected Drawable getCircularUserIcon() { + Drawable getCircularUserIcon() { Bitmap userIcon = mUserManager.getUserIcon(mUser.getIdentifier()); if (userIcon == null) { return null; @@ -387,12 +371,12 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen Log.d(TAG, "Installing " + packageName); } } - if (info != null && (info.flags&ApplicationInfo.FLAG_BLOCKED) != 0 + if (info != null && (info.flags&ApplicationInfo.FLAG_HIDDEN) != 0 && (info.flags&ApplicationInfo.FLAG_INSTALLED) != 0) { disableUiForPackage(packageName); - mIPm.setApplicationBlockedSettingAsUser(packageName, false, userId); + mIPm.setApplicationHiddenSettingAsUser(packageName, false, userId); if (DEBUG) { - Log.d(TAG, "Unblocking " + packageName); + Log.d(TAG, "Unhiding " + packageName); } } } catch (RemoteException re) { @@ -410,9 +394,9 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen } } else { disableUiForPackage(packageName); - mIPm.setApplicationBlockedSettingAsUser(packageName, true, userId); + mIPm.setApplicationHiddenSettingAsUser(packageName, true, userId); if (DEBUG) { - Log.d(TAG, "Blocking " + packageName); + Log.d(TAG, "Hiding " + packageName); } } } @@ -654,9 +638,9 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen private boolean isAppEnabledForUser(PackageInfo pi) { if (pi == null) return false; final int flags = pi.applicationInfo.flags; - // Return true if it is installed and not blocked + // Return true if it is installed and not hidden return ((flags&ApplicationInfo.FLAG_INSTALLED) != 0 - && (flags&ApplicationInfo.FLAG_BLOCKED) == 0); + && (flags&ApplicationInfo.FLAG_HIDDEN) == 0); } private void populateApps() { @@ -684,7 +668,7 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen app.masterEntry.activityName)); } p.setKey(getKeyForPackage(packageName)); - p.setSettingsEnabled(hasSettings || isSettingsApp); + p.setSettingsEnabled((hasSettings || isSettingsApp) && app.masterEntry == null); p.setPersistent(false); p.setOnPreferenceChangeListener(this); p.setOnPreferenceClickListener(this); @@ -703,7 +687,8 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen // Get and populate the defaults, since the user is not going to be // able to toggle this app ON (it's ON by default and immutable). // Only do this for restricted profiles, not single-user restrictions - if (hasSettings) { + // Also don't do this for slave icons + if (hasSettings && app.masterEntry == null) { requestRestrictionsForApp(packageName, p, false); } } else if (!mNewUser && isAppEnabledForUser(pi)) { @@ -1008,6 +993,7 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen + entry.getKey()); mAppList.addPreference(p); p.setOnPreferenceChangeListener(AppRestrictionsFragment.this); + p.setIcon(R.drawable.empty_icon); preference.mChildren.add(p); count++; } diff --git a/src/com/android/settings/users/CircleFramedDrawable.java b/src/com/android/settings/users/CircleFramedDrawable.java deleted file mode 100644 index 671cfbe..0000000 --- a/src/com/android/settings/users/CircleFramedDrawable.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (C) 2013 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.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.ColorFilter; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.PixelFormat; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.drawable.Drawable; - -import com.android.settings.R; - -/** - * Converts the user avatar icon to a circularly clipped one. - * TODO: Move this to an internal framework class and share with the one in Keyguard. - */ -class CircleFramedDrawable extends Drawable { - - private final Bitmap mBitmap; - private final int mSize; - private final Paint mPaint; - private final float mShadowRadius; - private final float mStrokeWidth; - private final int mFrameColor; - private final int mHighlightColor; - private final int mFrameShadowColor; - - private float mScale; - private Path mFramePath; - private Rect mSrcRect; - private RectF mDstRect; - private RectF mFrameRect; - private boolean mPressed; - - public static CircleFramedDrawable getInstance(Context context, Bitmap icon) { - Resources res = context.getResources(); - float iconSize = res.getDimension(R.dimen.circle_avatar_size); - float strokeWidth = res.getDimension(R.dimen.circle_avatar_frame_stroke_width); - float shadowRadius = res.getDimension(R.dimen.circle_avatar_frame_shadow_radius); - int frameColor = res.getColor(R.color.circle_avatar_frame_color); - int frameShadowColor = res.getColor(R.color.circle_avatar_frame_shadow_color); - int highlightColor = res.getColor(R.color.circle_avatar_frame_pressed_color); - - CircleFramedDrawable instance = new CircleFramedDrawable(icon, - (int) iconSize, frameColor, strokeWidth, frameShadowColor, shadowRadius, - highlightColor); - return instance; - } - - public CircleFramedDrawable(Bitmap icon, int size, - int frameColor, float strokeWidth, - int frameShadowColor, float shadowRadius, - int highlightColor) { - super(); - mSize = size; - mShadowRadius = shadowRadius; - mFrameColor = frameColor; - mFrameShadowColor = frameShadowColor; - mStrokeWidth = strokeWidth; - mHighlightColor = highlightColor; - - mBitmap = Bitmap.createBitmap(mSize, mSize, Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(mBitmap); - - final int width = icon.getWidth(); - final int height = icon.getHeight(); - final int square = Math.min(width, height); - - final Rect cropRect = new Rect((width - square) / 2, (height - square) / 2, square, square); - final RectF circleRect = new RectF(0f, 0f, mSize, mSize); - circleRect.inset(mStrokeWidth / 2f, mStrokeWidth / 2f); - circleRect.inset(mShadowRadius, mShadowRadius); - - final Path fillPath = new Path(); - fillPath.addArc(circleRect, 0f, 360f); - - canvas.drawColor(0, PorterDuff.Mode.CLEAR); - - // opaque circle matte - mPaint = new Paint(); - mPaint.setAntiAlias(true); - mPaint.setColor(Color.BLACK); - mPaint.setStyle(Paint.Style.FILL); - canvas.drawPath(fillPath, mPaint); - - // mask in the icon where the bitmap is opaque - mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP)); - canvas.drawBitmap(icon, cropRect, circleRect, mPaint); - - // prepare paint for frame drawing - mPaint.setXfermode(null); - - mScale = 1f; - - mSrcRect = new Rect(0, 0, mSize, mSize); - mDstRect = new RectF(0, 0, mSize, mSize); - mFrameRect = new RectF(mDstRect); - mFramePath = new Path(); - } - - @Override - public void draw(Canvas canvas) { - final float inside = mScale * mSize; - final float pad = (mSize - inside) / 2f; - - mDstRect.set(pad, pad, mSize - pad, mSize - pad); - canvas.drawBitmap(mBitmap, mSrcRect, mDstRect, null); - - mFrameRect.set(mDstRect); - mFrameRect.inset(mStrokeWidth / 2f, mStrokeWidth / 2f); - mFrameRect.inset(mShadowRadius, mShadowRadius); - - mFramePath.reset(); - mFramePath.addArc(mFrameRect, 0f, 360f); - - // white frame - if (mPressed) { - mPaint.setStyle(Paint.Style.FILL); - mPaint.setColor(Color.argb((int) (0.33f * 255), - Color.red(mHighlightColor), - Color.green(mHighlightColor), - Color.blue(mHighlightColor))); - canvas.drawPath(mFramePath, mPaint); - } - mPaint.setStrokeWidth(mStrokeWidth); - mPaint.setStyle(Paint.Style.STROKE); - mPaint.setColor(mPressed ? mHighlightColor : mFrameColor); - mPaint.setShadowLayer(mShadowRadius, 0f, 0f, mFrameShadowColor); - canvas.drawPath(mFramePath, mPaint); - } - - public void setScale(float scale) { - mScale = scale; - } - - public float getScale() { - return mScale; - } - - public void setPressed(boolean pressed) { - mPressed = pressed; - } - - @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; - } - - @Override - public void setAlpha(int alpha) { - } - - @Override - public void setColorFilter(ColorFilter cf) { - } - - @Override - public int getIntrinsicWidth() { - return mSize; - } - - @Override - public int getIntrinsicHeight() { - return mSize; - } -} diff --git a/src/com/android/settings/users/EditUserInfoController.java b/src/com/android/settings/users/EditUserInfoController.java new file mode 100644 index 0000000..0f844a7 --- /dev/null +++ b/src/com/android/settings/users/EditUserInfoController.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2013 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.app.Fragment; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.UserInfo; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.UserHandle; +import android.os.UserManager; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; +import android.widget.EditText; +import android.widget.ImageView; + +import com.android.settings.R; +import com.android.settings.drawable.CircleFramedDrawable; + +/** + * This class encapsulates a Dialog for editing the user nickname and photo. + */ +public class EditUserInfoController { + + private static final String KEY_AWAITING_RESULT = "awaiting_result"; + private static final String KEY_SAVED_PHOTO = "pending_photo"; + + private Dialog mEditUserInfoDialog; + private Bitmap mSavedPhoto; + private EditUserPhotoController mEditUserPhotoController; + private UserHandle mUser; + private UserManager mUserManager; + private boolean mWaitingForActivityResult = false; + + public interface OnContentChangedCallback { + public void onPhotoChanged(Drawable photo); + public void onLabelChanged(CharSequence label); + } + + public void clear() { + mEditUserInfoDialog = null; + mSavedPhoto = null; + } + + public Dialog getDialog() { + return mEditUserInfoDialog; + } + + public void onRestoreInstanceState(Bundle icicle) { + mSavedPhoto = (Bitmap) icicle.getParcelable(KEY_SAVED_PHOTO); + mWaitingForActivityResult = icicle.getBoolean(KEY_AWAITING_RESULT, false); + } + + public void onSaveInstanceState(Bundle outState) { + if (mEditUserInfoDialog != null && mEditUserInfoDialog.isShowing() + && mEditUserPhotoController != null) { + outState.putParcelable(KEY_SAVED_PHOTO, + mEditUserPhotoController.getNewUserPhotoBitmap()); + } + if (mWaitingForActivityResult) { + outState.putBoolean(KEY_AWAITING_RESULT, + mWaitingForActivityResult); + } + } + + public void startingActivityForResult() { + mWaitingForActivityResult = true; + } + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + mWaitingForActivityResult = false; + + if (mEditUserInfoDialog != null && mEditUserInfoDialog.isShowing() + && mEditUserPhotoController.onActivityResult(requestCode, resultCode, data)) { + return; + } + } + + Drawable getCircularUserIcon(Activity activity) { + Bitmap userIcon = mUserManager.getUserIcon(mUser.getIdentifier()); + if (userIcon == null) { + return null; + } + CircleFramedDrawable circularIcon = + CircleFramedDrawable.getInstance(activity, userIcon); + return circularIcon; + } + + public Dialog createDialog(final Fragment fragment, final Drawable currentUserIcon, + final CharSequence currentUserName, + int titleResId, final OnContentChangedCallback callback, UserHandle user) { + Activity activity = fragment.getActivity(); + mUser = user; + if (mUserManager == null) { + mUserManager = UserManager.get(activity); + } + LayoutInflater inflater = activity.getLayoutInflater(); + View content = inflater.inflate(R.layout.edit_user_info_dialog_content, null); + + UserInfo info = mUserManager.getUserInfo(mUser.getIdentifier()); + + final EditText userNameView = (EditText) content.findViewById(R.id.user_name); + userNameView.setText(info.name); + + final ImageView userPhotoView = (ImageView) content.findViewById(R.id.user_photo); + Drawable drawable = null; + if (mSavedPhoto != null) { + drawable = CircleFramedDrawable.getInstance(activity, mSavedPhoto); + } else { + drawable = currentUserIcon; + if (drawable == null) { + drawable = getCircularUserIcon(activity); + } + } + userPhotoView.setImageDrawable(drawable); + mEditUserPhotoController = new EditUserPhotoController(fragment, userPhotoView, + mSavedPhoto, drawable, mWaitingForActivityResult); + mEditUserInfoDialog = new AlertDialog.Builder(activity) + .setTitle(R.string.profile_info_settings_title) + .setView(content) + .setCancelable(true) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (which == DialogInterface.BUTTON_POSITIVE) { + // Update the name if changed. + CharSequence userName = userNameView.getText(); + if (!TextUtils.isEmpty(userName)) { + if (currentUserName == null + || !userName.toString().equals(currentUserName.toString())) { + if (callback != null) { + callback.onLabelChanged(userName.toString()); + } + mUserManager.setUserName(mUser.getIdentifier(), + userName.toString()); + } + } + // Update the photo if changed. + Drawable drawable = mEditUserPhotoController.getNewUserPhotoDrawable(); + Bitmap bitmap = mEditUserPhotoController.getNewUserPhotoBitmap(); + if (drawable != null && bitmap != null + && !drawable.equals(currentUserIcon)) { + if (callback != null) { + callback.onPhotoChanged(drawable); + } + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + mUserManager.setUserIcon(mUser.getIdentifier(), + mEditUserPhotoController.getNewUserPhotoBitmap()); + return null; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); + } + fragment.getActivity().removeDialog( + RestrictedProfileSettings.DIALOG_ID_EDIT_USER_INFO); + } + clear(); + } + }) + .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + clear(); + } + }) + .create(); + + // Make sure the IME is up. + mEditUserInfoDialog.getWindow().setSoftInputMode( + WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + + return mEditUserInfoDialog; + } +} diff --git a/src/com/android/settings/users/EditUserPhotoController.java b/src/com/android/settings/users/EditUserPhotoController.java new file mode 100644 index 0000000..82e550e --- /dev/null +++ b/src/com/android/settings/users/EditUserPhotoController.java @@ -0,0 +1,346 @@ +/* + * Copyright (C) 2013 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.Fragment; +import android.content.ClipData; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Bitmap.Config; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.AsyncTask; +import android.provider.MediaStore; +import android.provider.ContactsContract.DisplayPhoto; +import android.support.v4.content.FileProvider; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.ListAdapter; +import android.widget.ListPopupWindow; + +import com.android.settings.R; +import com.android.settings.drawable.CircleFramedDrawable; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +public class EditUserPhotoController { + private static final String TAG = "EditUserPhotoController"; + + private static final int POPUP_LIST_ITEM_ID_CHOOSE_PHOTO = 1; + private static final int POPUP_LIST_ITEM_ID_TAKE_PHOTO = 2; + + // It seems that this class generates custom request codes and they may + // collide with ours, these values are very unlikely to have a conflict. + private static final int REQUEST_CODE_CHOOSE_PHOTO = 1001; + private static final int REQUEST_CODE_TAKE_PHOTO = 1002; + private static final int REQUEST_CODE_CROP_PHOTO = 1003; + + private static final String CROP_PICTURE_FILE_NAME = "CropEditUserPhoto.jpg"; + private static final String TAKE_PICTURE_FILE_NAME = "TakeEditUserPhoto2.jpg"; + + private final int mPhotoSize; + + private final Context mContext; + private final Fragment mFragment; + private final ImageView mImageView; + + private final Uri mCropPictureUri; + private final Uri mTakePictureUri; + + private Bitmap mNewUserPhotoBitmap; + private Drawable mNewUserPhotoDrawable; + + public EditUserPhotoController(Fragment fragment, ImageView view, + Bitmap bitmap, Drawable drawable, boolean waiting) { + mContext = view.getContext(); + mFragment = fragment; + mImageView = view; + mCropPictureUri = createTempImageUri(mContext, CROP_PICTURE_FILE_NAME, !waiting); + mTakePictureUri = createTempImageUri(mContext, TAKE_PICTURE_FILE_NAME, !waiting); + mPhotoSize = getPhotoSize(mContext); + mImageView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + showUpdatePhotoPopup(); + } + }); + mNewUserPhotoBitmap = bitmap; + mNewUserPhotoDrawable = drawable; + } + + public boolean onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode != Activity.RESULT_OK) { + return false; + } + final Uri pictureUri = data != null && data.getData() != null + ? data.getData() : mTakePictureUri; + switch (requestCode) { + case REQUEST_CODE_CROP_PHOTO: + onPhotoCropped(pictureUri, true); + return true; + case REQUEST_CODE_TAKE_PHOTO: + case REQUEST_CODE_CHOOSE_PHOTO: + cropPhoto(pictureUri); + return true; + } + return false; + } + + public Bitmap getNewUserPhotoBitmap() { + return mNewUserPhotoBitmap; + } + + public Drawable getNewUserPhotoDrawable() { + return mNewUserPhotoDrawable; + } + + private void showUpdatePhotoPopup() { + final boolean canTakePhoto = canTakePhoto(); + final boolean canChoosePhoto = canChoosePhoto(); + + if (!canTakePhoto && !canChoosePhoto) { + return; + } + + Context context = mImageView.getContext(); + final List<EditUserPhotoController.AdapterItem> items = new ArrayList<EditUserPhotoController.AdapterItem>(); + + if (canTakePhoto()) { + String title = mImageView.getContext().getString( R.string.user_image_take_photo); + EditUserPhotoController.AdapterItem item = new AdapterItem(title, POPUP_LIST_ITEM_ID_TAKE_PHOTO); + items.add(item); + } + + if (canChoosePhoto) { + String title = context.getString(R.string.user_image_choose_photo); + EditUserPhotoController.AdapterItem item = new AdapterItem(title, POPUP_LIST_ITEM_ID_CHOOSE_PHOTO); + items.add(item); + } + + final ListPopupWindow listPopupWindow = new ListPopupWindow(context); + + listPopupWindow.setAnchorView(mImageView); + listPopupWindow.setModal(true); + listPopupWindow.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED); + + ListAdapter adapter = new ArrayAdapter<EditUserPhotoController.AdapterItem>(context, + R.layout.edit_user_photo_popup_item, items); + listPopupWindow.setAdapter(adapter); + + final int width = Math.max(mImageView.getWidth(), context.getResources() + .getDimensionPixelSize(R.dimen.update_user_photo_popup_min_width)); + listPopupWindow.setWidth(width); + + listPopupWindow.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + EditUserPhotoController.AdapterItem item = items.get(position); + switch (item.id) { + case POPUP_LIST_ITEM_ID_CHOOSE_PHOTO: { + choosePhoto(); + listPopupWindow.dismiss(); + } break; + case POPUP_LIST_ITEM_ID_TAKE_PHOTO: { + takePhoto(); + listPopupWindow.dismiss(); + } break; + } + } + }); + + listPopupWindow.show(); + } + + private boolean canTakePhoto() { + return mImageView.getContext().getPackageManager().queryIntentActivities( + new Intent(MediaStore.ACTION_IMAGE_CAPTURE), + PackageManager.MATCH_DEFAULT_ONLY).size() > 0; + } + + private boolean canChoosePhoto() { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("image/*"); + return mImageView.getContext().getPackageManager().queryIntentActivities( + intent, 0).size() > 0; + } + + private void takePhoto() { + Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + appendOutputExtra(intent, mTakePictureUri); + mFragment.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO); + } + + private void choosePhoto() { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null); + intent.setType("image/*"); + appendOutputExtra(intent, mTakePictureUri); + mFragment.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO); + } + + private void cropPhoto(Uri pictureUri) { + // TODO: Use a public intent, when there is one. + Intent intent = new Intent("com.android.camera.action.CROP"); + intent.setDataAndType(pictureUri, "image/*"); + appendOutputExtra(intent, mCropPictureUri); + appendCropExtras(intent); + if (intent.resolveActivity(mContext.getPackageManager()) != null) { + mFragment.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO); + } else { + onPhotoCropped(pictureUri, false); + } + } + + private void appendOutputExtra(Intent intent, Uri pictureUri) { + intent.putExtra(MediaStore.EXTRA_OUTPUT, pictureUri); + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION + | Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, pictureUri)); + } + + private void appendCropExtras(Intent intent) { + intent.putExtra("crop", "true"); + intent.putExtra("scale", true); + intent.putExtra("scaleUpIfNeeded", true); + intent.putExtra("aspectX", 1); + intent.putExtra("aspectY", 1); + intent.putExtra("outputX", mPhotoSize); + intent.putExtra("outputY", mPhotoSize); + } + + private void onPhotoCropped(final Uri data, final boolean cropped) { + new AsyncTask<Void, Void, Bitmap>() { + @Override + protected Bitmap doInBackground(Void... params) { + if (cropped) { + InputStream imageStream = null; + try { + imageStream = mContext.getContentResolver() + .openInputStream(data); + return BitmapFactory.decodeStream(imageStream); + } catch (FileNotFoundException fe) { + Log.w(TAG, "Cannot find image file", fe); + return null; + } finally { + if (imageStream != null) { + try { + imageStream.close(); + } catch (IOException ioe) { + Log.w(TAG, "Cannot close image stream", ioe); + } + } + } + } else { + // Scale and crop to a square aspect ratio + Bitmap croppedImage = Bitmap.createBitmap(mPhotoSize, mPhotoSize, + Config.ARGB_8888); + Canvas canvas = new Canvas(croppedImage); + Bitmap fullImage = null; + try { + InputStream imageStream = mContext.getContentResolver() + .openInputStream(data); + fullImage = BitmapFactory.decodeStream(imageStream); + } catch (FileNotFoundException fe) { + return null; + } + if (fullImage != null) { + final int squareSize = Math.min(fullImage.getWidth(), + fullImage.getHeight()); + final int left = (fullImage.getWidth() - squareSize) / 2; + final int top = (fullImage.getHeight() - squareSize) / 2; + Rect rectSource = new Rect(left, top, + left + squareSize, top + squareSize); + Rect rectDest = new Rect(0, 0, mPhotoSize, mPhotoSize); + Paint paint = new Paint(); + canvas.drawBitmap(fullImage, rectSource, rectDest, paint); + return croppedImage; + } else { + // Bah! Got nothin. + return null; + } + } + } + + @Override + protected void onPostExecute(Bitmap bitmap) { + if (bitmap != null) { + mNewUserPhotoBitmap = bitmap; + mNewUserPhotoDrawable = CircleFramedDrawable + .getInstance(mImageView.getContext(), mNewUserPhotoBitmap); + mImageView.setImageDrawable(mNewUserPhotoDrawable); + } + new File(mContext.getCacheDir(), TAKE_PICTURE_FILE_NAME).delete(); + new File(mContext.getCacheDir(), CROP_PICTURE_FILE_NAME).delete(); + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); + } + + private static int getPhotoSize(Context context) { + Cursor cursor = context.getContentResolver().query( + DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI, + new String[]{DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null); + try { + cursor.moveToFirst(); + return cursor.getInt(0); + } finally { + cursor.close(); + } + } + + private Uri createTempImageUri(Context context, String fileName, boolean purge) { + final File folder = context.getCacheDir(); + folder.mkdirs(); + final File fullPath = new File(folder, fileName); + if (purge) { + fullPath.delete(); + } + final Uri fileUri = + FileProvider.getUriForFile(context, RestrictedProfileSettings.FILE_PROVIDER_AUTHORITY, fullPath); + return fileUri; + } + + private static final class AdapterItem { + final String title; + final int id; + + public AdapterItem(String title, int id) { + this.title = title; + this.id = id; + } + + @Override + public String toString() { + return title; + } + } +}
\ No newline at end of file diff --git a/src/com/android/settings/users/RestrictedProfileSettings.java b/src/com/android/settings/users/RestrictedProfileSettings.java index c293536..8db911c 100644 --- a/src/com/android/settings/users/RestrictedProfileSettings.java +++ b/src/com/android/settings/users/RestrictedProfileSettings.java @@ -16,77 +16,44 @@ package com.android.settings.users; -import android.app.Activity; -import android.app.AlertDialog; import android.app.Dialog; -import android.app.Fragment; -import android.content.ClipData; -import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.content.pm.PackageManager; import android.content.pm.UserInfo; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.AsyncTask; import android.os.Bundle; import android.os.UserHandle; -import android.provider.MediaStore; -import android.provider.ContactsContract.DisplayPhoto; -import android.support.v4.content.FileProvider; -import android.text.TextUtils; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.view.View.OnClickListener; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.EditText; import android.widget.ImageView; -import android.widget.ListAdapter; -import android.widget.ListPopupWindow; import android.widget.TextView; import com.android.settings.R; +import com.android.settings.Utils; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.util.ArrayList; import java.util.List; -public class RestrictedProfileSettings extends AppRestrictionsFragment { +public class RestrictedProfileSettings extends AppRestrictionsFragment + implements EditUserInfoController.OnContentChangedCallback { - private static final String KEY_SAVED_PHOTO = "pending_photo"; - private static final String KEY_AWAITING_RESULT = "awaiting_result"; - private static final int DIALOG_ID_EDIT_USER_INFO = 1; public static final String FILE_PROVIDER_AUTHORITY = "com.android.settings.files"; + static final int DIALOG_ID_EDIT_USER_INFO = 1; + private static final int DIALOG_CONFIRM_REMOVE = 2; private View mHeaderView; private ImageView mUserIconView; private TextView mUserNameView; + private ImageView mDeleteButton; - private Dialog mEditUserInfoDialog; - private EditUserPhotoController mEditUserPhotoController; - private Bitmap mSavedPhoto; - private boolean mWaitingForActivityResult; + private EditUserInfoController mEditUserInfoController = + new EditUserInfoController(); @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); if (icicle != null) { - mSavedPhoto = (Bitmap) icicle.getParcelable(KEY_SAVED_PHOTO); - mWaitingForActivityResult = icicle.getBoolean(KEY_AWAITING_RESULT, false); + mEditUserInfoController.onRestoreInstanceState(icicle); } init(icicle); @@ -97,10 +64,12 @@ public class RestrictedProfileSettings extends AppRestrictionsFragment { if (mHeaderView == null) { mHeaderView = LayoutInflater.from(getActivity()).inflate( R.layout.user_info_header, null); - ((ViewGroup) getListView().getParent()).addView(mHeaderView, 0); + setPinnedHeaderView(mHeaderView); mHeaderView.setOnClickListener(this); mUserIconView = (ImageView) mHeaderView.findViewById(android.R.id.icon); mUserNameView = (TextView) mHeaderView.findViewById(android.R.id.title); + mDeleteButton = (ImageView) mHeaderView.findViewById(R.id.delete); + mDeleteButton.setOnClickListener(this); getListView().setFastScrollEnabled(true); } // This is going to bind the preferences. @@ -110,14 +79,7 @@ public class RestrictedProfileSettings extends AppRestrictionsFragment { @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - if (mEditUserInfoDialog != null && mEditUserInfoDialog.isShowing() - && mEditUserPhotoController != null) { - outState.putParcelable(KEY_SAVED_PHOTO, - mEditUserPhotoController.getNewUserPhotoBitmap()); - } - if (mWaitingForActivityResult) { - outState.putBoolean(KEY_AWAITING_RESULT, mWaitingForActivityResult); - } + mEditUserInfoController.onSaveInstanceState(outState); } @Override @@ -147,25 +109,23 @@ public class RestrictedProfileSettings extends AppRestrictionsFragment { @Override public void startActivityForResult(Intent intent, int requestCode) { - mWaitingForActivityResult = true; + mEditUserInfoController.startingActivityForResult(); super.startActivityForResult(intent, requestCode); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - mWaitingForActivityResult = false; - if (mEditUserInfoDialog != null && mEditUserInfoDialog.isShowing() - && mEditUserPhotoController.onActivityResult(requestCode, resultCode, data)) { - return; - } + mEditUserInfoController.onActivityResult(requestCode, resultCode, data); } @Override public void onClick(View view) { if (view == mHeaderView) { showDialog(DIALOG_ID_EDIT_USER_INFO); + } else if (view == mDeleteButton) { + showDialog(DIALOG_CONFIRM_REMOVE); } else { super.onClick(view); // in AppRestrictionsFragment } @@ -174,373 +134,40 @@ public class RestrictedProfileSettings extends AppRestrictionsFragment { @Override public Dialog onCreateDialog(int dialogId) { if (dialogId == DIALOG_ID_EDIT_USER_INFO) { - if (mEditUserInfoDialog != null) { - return mEditUserInfoDialog; - } - - LayoutInflater inflater = getActivity().getLayoutInflater(); - View content = inflater.inflate(R.layout.edit_user_info_dialog_content, null); - - UserInfo info = mUserManager.getUserInfo(mUser.getIdentifier()); - - final EditText userNameView = (EditText) content.findViewById(R.id.user_name); - userNameView.setText(info.name); - - final ImageView userPhotoView = (ImageView) content.findViewById(R.id.user_photo); - Drawable drawable = null; - if (mSavedPhoto != null) { - drawable = CircleFramedDrawable.getInstance(getActivity(), mSavedPhoto); - } else { - drawable = mUserIconView.getDrawable(); - if (drawable == null) { - drawable = getCircularUserIcon(); - } - } - userPhotoView.setImageDrawable(drawable); - mEditUserPhotoController = new EditUserPhotoController(this, userPhotoView, - mSavedPhoto, drawable, mWaitingForActivityResult); - - mEditUserInfoDialog = new AlertDialog.Builder(getActivity()) - .setTitle(R.string.profile_info_settings_title) - .setIconAttribute(R.drawable.ic_settings_multiuser) - .setView(content) - .setCancelable(true) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (which == DialogInterface.BUTTON_POSITIVE) { - // Update the name if changed. - CharSequence userName = userNameView.getText(); - if (!TextUtils.isEmpty(userName)) { - CharSequence oldUserName = mUserNameView.getText(); - if (oldUserName == null - || !userName.toString().equals(oldUserName.toString())) { - ((TextView) mHeaderView.findViewById(android.R.id.title)) - .setText(userName.toString()); - mUserManager.setUserName(mUser.getIdentifier(), - userName.toString()); + return mEditUserInfoController.createDialog(this, mUserIconView.getDrawable(), + mUserNameView.getText(), R.string.profile_info_settings_title, + this, mUser); + } else if (dialogId == DIALOG_CONFIRM_REMOVE) { + Dialog dlg = + Utils.createRemoveConfirmationDialog(getActivity(), mUser.getIdentifier(), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + removeUser(); } } - // Update the photo if changed. - Drawable drawable = mEditUserPhotoController.getNewUserPhotoDrawable(); - Bitmap bitmap = mEditUserPhotoController.getNewUserPhotoBitmap(); - if (drawable != null && bitmap != null - && !drawable.equals(mUserIconView.getDrawable())) { - mUserIconView.setImageDrawable(drawable); - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... params) { - mUserManager.setUserIcon(mUser.getIdentifier(), - mEditUserPhotoController.getNewUserPhotoBitmap()); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); - } - removeDialog(DIALOG_ID_EDIT_USER_INFO); - } - clearEditUserInfoDialog(); - } - }) - .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - clearEditUserInfoDialog(); - } - }) - .create(); - - // Make sure the IME is up. - mEditUserInfoDialog.getWindow().setSoftInputMode( - WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); - - return mEditUserInfoDialog; + ); + return dlg; } return null; } - private void clearEditUserInfoDialog() { - mEditUserInfoDialog = null; - mSavedPhoto = null; - } - - private static class EditUserPhotoController { - private static final int POPUP_LIST_ITEM_ID_CHOOSE_PHOTO = 1; - private static final int POPUP_LIST_ITEM_ID_TAKE_PHOTO = 2; - - // It seems that this class generates custom request codes and they may - // collide with ours, these values are very unlikely to have a conflict. - private static final int REQUEST_CODE_CHOOSE_PHOTO = 1; - private static final int REQUEST_CODE_TAKE_PHOTO = 2; - private static final int REQUEST_CODE_CROP_PHOTO = 3; - - private static final String CROP_PICTURE_FILE_NAME = "CropEditUserPhoto.jpg"; - private static final String TAKE_PICTURE_FILE_NAME = "TakeEditUserPhoto2.jpg"; - - private final int mPhotoSize; - - private final Context mContext; - private final Fragment mFragment; - private final ImageView mImageView; - - private final Uri mCropPictureUri; - private final Uri mTakePictureUri; - - private Bitmap mNewUserPhotoBitmap; - private Drawable mNewUserPhotoDrawable; - - public EditUserPhotoController(Fragment fragment, ImageView view, - Bitmap bitmap, Drawable drawable, boolean waiting) { - mContext = view.getContext(); - mFragment = fragment; - mImageView = view; - mCropPictureUri = createTempImageUri(mContext, CROP_PICTURE_FILE_NAME, !waiting); - mTakePictureUri = createTempImageUri(mContext, TAKE_PICTURE_FILE_NAME, !waiting); - mPhotoSize = getPhotoSize(mContext); - mImageView.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - showUpdatePhotoPopup(); - } - }); - mNewUserPhotoBitmap = bitmap; - mNewUserPhotoDrawable = drawable; - } - - public boolean onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode != Activity.RESULT_OK) { - return false; - } - final Uri pictureUri = data != null && data.getData() != null - ? data.getData() : mTakePictureUri; - switch (requestCode) { - case REQUEST_CODE_CROP_PHOTO: - onPhotoCropped(pictureUri, true); - return true; - case REQUEST_CODE_TAKE_PHOTO: - case REQUEST_CODE_CHOOSE_PHOTO: - cropPhoto(pictureUri); - return true; - } - return false; - } - - public Bitmap getNewUserPhotoBitmap() { - return mNewUserPhotoBitmap; - } - - public Drawable getNewUserPhotoDrawable() { - return mNewUserPhotoDrawable; - } - - private void showUpdatePhotoPopup() { - final boolean canTakePhoto = canTakePhoto(); - final boolean canChoosePhoto = canChoosePhoto(); - - if (!canTakePhoto && !canChoosePhoto) { - return; - } - - Context context = mImageView.getContext(); - final List<AdapterItem> items = new ArrayList<AdapterItem>(); - - if (canTakePhoto()) { - String title = mImageView.getContext().getString( R.string.user_image_take_photo); - AdapterItem item = new AdapterItem(title, POPUP_LIST_ITEM_ID_TAKE_PHOTO); - items.add(item); - } - - if (canChoosePhoto) { - String title = context.getString(R.string.user_image_choose_photo); - AdapterItem item = new AdapterItem(title, POPUP_LIST_ITEM_ID_CHOOSE_PHOTO); - items.add(item); - } - - final ListPopupWindow listPopupWindow = new ListPopupWindow(context); - - listPopupWindow.setAnchorView(mImageView); - listPopupWindow.setModal(true); - listPopupWindow.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED); - - ListAdapter adapter = new ArrayAdapter<AdapterItem>(context, - R.layout.edit_user_photo_popup_item, items); - listPopupWindow.setAdapter(adapter); - - final int width = Math.max(mImageView.getWidth(), context.getResources() - .getDimensionPixelSize(R.dimen.update_user_photo_popup_min_width)); - listPopupWindow.setWidth(width); - - listPopupWindow.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - AdapterItem item = items.get(position); - switch (item.id) { - case POPUP_LIST_ITEM_ID_CHOOSE_PHOTO: { - choosePhoto(); - listPopupWindow.dismiss(); - } break; - case POPUP_LIST_ITEM_ID_TAKE_PHOTO: { - takePhoto(); - listPopupWindow.dismiss(); - } break; - } - } - }); - - listPopupWindow.show(); - } - - private boolean canTakePhoto() { - return mImageView.getContext().getPackageManager().queryIntentActivities( - new Intent(MediaStore.ACTION_IMAGE_CAPTURE), - PackageManager.MATCH_DEFAULT_ONLY).size() > 0; - } - - private boolean canChoosePhoto() { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType("image/*"); - return mImageView.getContext().getPackageManager().queryIntentActivities( - intent, 0).size() > 0; - } - - private void takePhoto() { - Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - appendOutputExtra(intent, mTakePictureUri); - mFragment.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO); - } - - private void choosePhoto() { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null); - intent.setType("image/*"); - appendOutputExtra(intent, mTakePictureUri); - mFragment.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO); - } - - private void cropPhoto(Uri pictureUri) { - // TODO: Use a public intent, when there is one. - Intent intent = new Intent("com.android.camera.action.CROP"); - intent.setDataAndType(pictureUri, "image/*"); - appendOutputExtra(intent, mCropPictureUri); - appendCropExtras(intent); - if (intent.resolveActivity(mContext.getPackageManager()) != null) { - mFragment.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO); - } else { - onPhotoCropped(pictureUri, false); - } - } - - private void appendOutputExtra(Intent intent, Uri pictureUri) { - intent.putExtra(MediaStore.EXTRA_OUTPUT, pictureUri); - intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION - | Intent.FLAG_GRANT_READ_URI_PERMISSION); - intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, pictureUri)); - } - - private void appendCropExtras(Intent intent) { - intent.putExtra("crop", "true"); - intent.putExtra("scale", true); - intent.putExtra("scaleUpIfNeeded", true); - intent.putExtra("aspectX", 1); - intent.putExtra("aspectY", 1); - intent.putExtra("outputX", mPhotoSize); - intent.putExtra("outputY", mPhotoSize); - } - - private void onPhotoCropped(final Uri data, final boolean cropped) { - new AsyncTask<Void, Void, Bitmap>() { - @Override - protected Bitmap doInBackground(Void... params) { - if (cropped) { - try { - InputStream imageStream = mContext.getContentResolver() - .openInputStream(data); - return BitmapFactory.decodeStream(imageStream); - } catch (FileNotFoundException fe) { - return null; - } - } else { - // Scale and crop to a square aspect ratio - Bitmap croppedImage = Bitmap.createBitmap(mPhotoSize, mPhotoSize, - Config.ARGB_8888); - Canvas canvas = new Canvas(croppedImage); - Bitmap fullImage = null; - try { - InputStream imageStream = mContext.getContentResolver() - .openInputStream(data); - fullImage = BitmapFactory.decodeStream(imageStream); - } catch (FileNotFoundException fe) { - return null; - } - if (fullImage != null) { - final int squareSize = Math.min(fullImage.getWidth(), - fullImage.getHeight()); - final int left = (fullImage.getWidth() - squareSize) / 2; - final int top = (fullImage.getHeight() - squareSize) / 2; - Rect rectSource = new Rect(left, top, - left + squareSize, top + squareSize); - Rect rectDest = new Rect(0, 0, mPhotoSize, mPhotoSize); - Paint paint = new Paint(); - canvas.drawBitmap(fullImage, rectSource, rectDest, paint); - return croppedImage; - } else { - // Bah! Got nothin. - return null; - } - } - } - - @Override - protected void onPostExecute(Bitmap bitmap) { - if (bitmap != null) { - mNewUserPhotoBitmap = bitmap; - mNewUserPhotoDrawable = CircleFramedDrawable - .getInstance(mImageView.getContext(), mNewUserPhotoBitmap); - mImageView.setImageDrawable(mNewUserPhotoDrawable); - } - new File(mContext.getCacheDir(), TAKE_PICTURE_FILE_NAME).delete(); - new File(mContext.getCacheDir(), CROP_PICTURE_FILE_NAME).delete(); - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); - } - - private static int getPhotoSize(Context context) { - Cursor cursor = context.getContentResolver().query( - DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI, - new String[]{DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null); - try { - cursor.moveToFirst(); - return cursor.getInt(0); - } finally { - cursor.close(); - } - } - - private Uri createTempImageUri(Context context, String fileName, boolean purge) { - final File folder = context.getCacheDir(); - folder.mkdirs(); - final File fullPath = new File(folder, fileName); - if (purge) { - fullPath.delete(); - } - final Uri fileUri = - FileProvider.getUriForFile(context, FILE_PROVIDER_AUTHORITY, fullPath); - return fileUri; - } - - private static final class AdapterItem { - final String title; - final int id; - - public AdapterItem(String title, int id) { - this.title = title; - this.id = id; + private void removeUser() { + getView().post(new Runnable() { + public void run() { + mUserManager.removeUser(mUser.getIdentifier()); + finishFragment(); } + }); + } - @Override - public String toString() { - return title; - } - } + @Override + public void onPhotoChanged(Drawable photo) { + mUserIconView.setImageDrawable(photo); } + @Override + public void onLabelChanged(CharSequence label) { + mUserNameView.setText(label); + } } diff --git a/src/com/android/settings/users/RestrictionUtils.java b/src/com/android/settings/users/RestrictionUtils.java index 3ee6d51..e8d46e9 100644 --- a/src/com/android/settings/users/RestrictionUtils.java +++ b/src/com/android/settings/users/RestrictionUtils.java @@ -84,8 +84,8 @@ public class RestrictionUtils { userRestrictions.putBoolean(entry.getKey(), !entry.getSelectedState()); if (entry.getKey().equals(UserManager.DISALLOW_SHARE_LOCATION) && !entry.getSelectedState()) { - Secure.putStringForUser(context.getContentResolver(), - Secure.LOCATION_PROVIDERS_ALLOWED, "", user.getIdentifier()); + Secure.putIntForUser(context.getContentResolver(), + Secure.LOCATION_MODE, Secure.LOCATION_MODE_OFF, user.getIdentifier()); } } um.setUserRestrictions(userRestrictions, user); diff --git a/src/com/android/settings/users/UserDetailsSettings.java b/src/com/android/settings/users/UserDetailsSettings.java new file mode 100644 index 0000000..366b628 --- /dev/null +++ b/src/com/android/settings/users/UserDetailsSettings.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2014 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.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.pm.UserInfo; +import android.os.Bundle; +import android.os.UserHandle; +import android.os.UserManager; +import android.preference.Preference; +import android.preference.SwitchPreference; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.Utils; + +import java.util.List; + +/** + * Settings screen for configuring a specific user. It can contain user restrictions + * and deletion controls. It is shown when you tap on the settings icon in the + * user management (UserSettings) screen. + * + * Arguments to this fragment must include the userId of the user (in EXTRA_USER_ID) for whom + * to display controls, or should contain the EXTRA_USER_GUEST = true. + */ +public class UserDetailsSettings extends SettingsPreferenceFragment + implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener { + + private static final String TAG = UserDetailsSettings.class.getSimpleName(); + + private static final String KEY_ENABLE_TELEPHONY = "enable_calling"; + private static final String KEY_REMOVE_USER = "remove_user"; + + /** Integer extra containing the userId to manage */ + static final String EXTRA_USER_ID = "user_id"; + /** Boolean extra to indicate guest preferences */ + static final String EXTRA_USER_GUEST = "guest_user"; + + private static final int DIALOG_CONFIRM_REMOVE = 1; + private static final int DIALOG_CONFIRM_ENABLE_CALLING = 2; + private static final int DIALOG_CONFIRM_ENABLE_CALLING_SMS = 3; + + private UserManager mUserManager; + private SwitchPreference mPhonePref; + private Preference mRemoveUserPref; + + private UserInfo mUserInfo; + private boolean mGuestUser; + private Bundle mDefaultGuestRestrictions; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + final Context context = getActivity(); + mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); + + addPreferencesFromResource(R.xml.user_details_settings); + mPhonePref = (SwitchPreference) findPreference(KEY_ENABLE_TELEPHONY); + mRemoveUserPref = findPreference(KEY_REMOVE_USER); + + mGuestUser = getArguments().getBoolean(EXTRA_USER_GUEST, false); + + if (!mGuestUser) { + // Regular user. Get the user id from the caller. + final int userId = getArguments().getInt(EXTRA_USER_ID, -1); + if (userId == -1) { + throw new RuntimeException("Arguments to this fragment must contain the user id"); + } + mUserInfo = mUserManager.getUserInfo(userId); + mPhonePref.setChecked(!mUserManager.hasUserRestriction( + UserManager.DISALLOW_OUTGOING_CALLS, new UserHandle(userId))); + mRemoveUserPref.setOnPreferenceClickListener(this); + } else { + // These are not for an existing user, just general Guest settings. + removePreference(KEY_REMOVE_USER); + // Default title is for calling and SMS. Change to calling-only here + mPhonePref.setTitle(R.string.user_enable_calling); + mDefaultGuestRestrictions = mUserManager.getDefaultGuestRestrictions(); + mPhonePref.setChecked( + !mDefaultGuestRestrictions.getBoolean(UserManager.DISALLOW_OUTGOING_CALLS)); + } + if (mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_USER)) { + removePreference(KEY_REMOVE_USER); + } + mPhonePref.setOnPreferenceChangeListener(this); + } + + @Override + public boolean onPreferenceClick(Preference preference) { + if (preference == mRemoveUserPref) { + if (UserHandle.myUserId() != UserHandle.USER_OWNER) { + throw new RuntimeException("Only the owner can remove a user"); + } + showDialog(DIALOG_CONFIRM_REMOVE); + return true; + } + return false; + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (mGuestUser) { + // TODO: Show confirmation dialog: b/15761405 + mDefaultGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, + !((Boolean) newValue)); + // SMS is always disabled for guest + mDefaultGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true); + mUserManager.setDefaultGuestRestrictions(mDefaultGuestRestrictions); + // Update the guest's restrictions, if there is a guest + List<UserInfo> users = mUserManager.getUsers(true); + for (UserInfo user: users) { + if (user.isGuest()) { + UserHandle userHandle = new UserHandle(user.id); + Bundle userRestrictions = mUserManager.getUserRestrictions(userHandle); + userRestrictions.putAll(mDefaultGuestRestrictions); + mUserManager.setUserRestrictions(userRestrictions, userHandle); + } + } + } else { + // TODO: Show confirmation dialog: b/15761405 + UserHandle userHandle = new UserHandle(mUserInfo.id); + mUserManager.setUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, + !((Boolean) newValue), userHandle); + mUserManager.setUserRestriction(UserManager.DISALLOW_SMS, + !((Boolean) newValue), userHandle); + } + return true; + } + + @Override + public Dialog onCreateDialog(int dialogId) { + Context context = getActivity(); + if (context == null) return null; + switch (dialogId) { + case DIALOG_CONFIRM_REMOVE: { + Dialog dlg = Utils.createRemoveConfirmationDialog(getActivity(), mUserInfo.id, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + removeUser(); + } + }); + return dlg; + } + case DIALOG_CONFIRM_ENABLE_CALLING: + case DIALOG_CONFIRM_ENABLE_CALLING_SMS: + // TODO: b/15761405 + } + return null; + } + + void removeUser() { + mUserManager.removeUser(mUserInfo.id); + finishFragment(); + } +} diff --git a/src/com/android/settings/users/UserPreference.java b/src/com/android/settings/users/UserPreference.java index 9f53aa5..23359ec 100644 --- a/src/com/android/settings/users/UserPreference.java +++ b/src/com/android/settings/users/UserPreference.java @@ -16,7 +16,6 @@ package com.android.settings.users; -import com.android.internal.util.CharSequences; import com.android.settings.R; import android.content.Context; @@ -30,13 +29,12 @@ import android.view.View.OnClickListener; public class UserPreference extends Preference { public static final int USERID_UNKNOWN = -10; + public static final int USERID_GUEST_DEFAULTS = -11; private OnClickListener mDeleteClickListener; private OnClickListener mSettingsClickListener; private int mSerialNumber = -1; private int mUserId = USERID_UNKNOWN; - private boolean mRestricted; - private boolean mSelf; static final int SETTINGS_ID = R.id.manage_user; static final int DELETE_ID = R.id.trash_user; @@ -58,11 +56,13 @@ public class UserPreference extends Preference { @Override protected void onBindView(View view) { + UserManager um = (UserManager) getContext().getSystemService(Context.USER_SERVICE); View deleteDividerView = view.findViewById(R.id.divider_delete); View manageDividerView = view.findViewById(R.id.divider_manage); View deleteView = view.findViewById(R.id.trash_user); if (deleteView != null) { - if (mDeleteClickListener != null) { + if (mDeleteClickListener != null + && !um.hasUserRestriction(UserManager.DISALLOW_REMOVE_USER)) { deleteView.setOnClickListener(mDeleteClickListener); deleteView.setTag(this); } else { @@ -90,7 +90,11 @@ public class UserPreference extends Preference { if (mUserId == UserHandle.myUserId()) return Integer.MIN_VALUE; if (mSerialNumber < 0) { // If the userId is unknown - if (mUserId == USERID_UNKNOWN) return Integer.MAX_VALUE; + if (mUserId == USERID_UNKNOWN) { + return Integer.MAX_VALUE; + } else if (mUserId == USERID_GUEST_DEFAULTS) { + return Integer.MAX_VALUE - 1; + } mSerialNumber = ((UserManager) getContext().getSystemService(Context.USER_SERVICE)) .getUserSerialNumber(mUserId); if (mSerialNumber < 0) return mUserId; diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java index bbae37d..b95c397 100644 --- a/src/com/android/settings/users/UserSettings.java +++ b/src/com/android/settings/users/UserSettings.java @@ -16,16 +16,13 @@ package com.android.settings.users; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - import android.accounts.Account; import android.accounts.AccountManager; import android.app.Activity; import android.app.ActivityManagerNative; import android.app.AlertDialog; import android.app.Dialog; +import android.app.Fragment; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -36,7 +33,6 @@ 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.Drawable; import android.os.AsyncTask; import android.os.Bundle; @@ -47,10 +43,8 @@ import android.os.UserHandle; import android.os.UserManager; 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.Settings; import android.provider.Settings.Secure; import android.util.Log; import android.util.SparseArray; @@ -61,17 +55,33 @@ import android.view.View; import android.view.View.OnClickListener; import android.widget.SimpleAdapter; +import com.android.internal.util.UserIcons; import com.android.internal.widget.LockPatternUtils; import com.android.settings.ChooseLockGeneric; import com.android.settings.OwnerInfoSettings; import com.android.settings.R; -import com.android.settings.RestrictedSettingsFragment; import com.android.settings.SelectableEditTextPreference; +import com.android.settings.SettingsActivity; +import com.android.settings.SettingsPreferenceFragment; import com.android.settings.Utils; +import com.android.settings.drawable.CircleFramedDrawable; -public class UserSettings extends RestrictedSettingsFragment +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * Screen that manages the list of users on the device. + * Guest user is an always visible entry, even if the guest is not currently + * active/created. It is meant for controlling properties of a guest user. + * + * The first one is always the current user. + * Owner is the primary user. + */ +public class UserSettings extends SettingsPreferenceFragment implements OnPreferenceClickListener, OnClickListener, DialogInterface.OnDismissListener, - Preference.OnPreferenceChangeListener { + Preference.OnPreferenceChangeListener, + EditUserInfoController.OnContentChangedCallback { private static final String TAG = "UserSettings"; @@ -85,6 +95,7 @@ public class UserSettings extends RestrictedSettingsFragment private static final String KEY_ADD_USER = "user_add"; private static final int MENU_REMOVE_USER = Menu.FIRST; + private static final int MENU_ADD_ON_LOCKSCREEN = Menu.FIRST + 1; private static final int DIALOG_CONFIRM_REMOVE = 1; private static final int DIALOG_ADD_USER = 2; @@ -93,6 +104,8 @@ public class UserSettings extends RestrictedSettingsFragment private static final int DIALOG_USER_CANNOT_MANAGE = 5; private static final int DIALOG_CHOOSE_USER_TYPE = 6; private static final int DIALOG_NEED_LOCKSCREEN = 7; + private static final int DIALOG_CONFIRM_EXIT_GUEST = 8; + private static final int DIALOG_USER_PROFILE_EDITOR = 9; private static final int MESSAGE_UPDATE_LIST = 1; private static final int MESSAGE_SETUP_USER = 2; @@ -106,17 +119,6 @@ public class UserSettings extends RestrictedSettingsFragment private static final String KEY_ADD_USER_LONG_MESSAGE_DISPLAYED = "key_add_user_long_message_displayed"; - 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 static final String KEY_TITLE = "title"; private static final String KEY_SUMMARY = "summary"; @@ -127,16 +129,20 @@ public class UserSettings extends RestrictedSettingsFragment private int mRemovingUserId = -1; private int mAddedUserId = 0; private boolean mAddingUser; - private boolean mProfileExists; + private boolean mEnabled = true; + private boolean mCanAddRestrictedProfile = true; private final Object mUserLock = new Object(); private UserManager mUserManager; private SparseArray<Bitmap> mUserIcons = new SparseArray<Bitmap>(); private boolean mIsOwner = UserHandle.myUserId() == UserHandle.USER_OWNER; + private boolean mIsGuest; - public UserSettings() { - super(RestrictedSettingsFragment.RESTRICTIONS_PIN_SET); - } + private EditUserInfoController mEditUserInfoController = + new EditUserInfoController(); + + // A place to cache the generated default avatar + private Drawable mDefaultIconDrawable; private Handler mHandler = new Handler() { @Override @@ -181,34 +187,59 @@ public class UserSettings extends RestrictedSettingsFragment if (icicle.containsKey(SAVE_REMOVING_USER)) { mRemovingUserId = icicle.getInt(SAVE_REMOVING_USER); } + mEditUserInfoController.onRestoreInstanceState(icicle); + } + final Context context = getActivity(); + mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); + boolean hasMultipleUsers = mUserManager.getUserCount() > 1; + if ((!UserManager.supportsMultipleUsers() && !hasMultipleUsers) + || Utils.isMonkeyRunning()) { + mEnabled = false; + return; } - mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE); + final int myUserId = UserHandle.myUserId(); + mIsGuest = mUserManager.getUserInfo(myUserId).isGuest(); + addPreferencesFromResource(R.xml.user_settings); mUserListCategory = (PreferenceGroup) findPreference(KEY_USER_LIST); - mMePreference = new UserPreference(getActivity(), null, UserHandle.myUserId(), - mUserManager.isLinkedUser() ? null : this, null); + mMePreference = new UserPreference(context, null /* attrs */, myUserId, + null /* settings icon handler */, + null /* delete icon handler */); mMePreference.setKey(KEY_USER_ME); mMePreference.setOnPreferenceClickListener(this); if (mIsOwner) { mMePreference.setSummary(R.string.user_owner); } mAddUser = findPreference(KEY_ADD_USER); - mAddUser.setOnPreferenceClickListener(this); - if (!mIsOwner || UserManager.getMaxSupportedUsers() < 2) { + if (!mIsOwner || UserManager.getMaxSupportedUsers() < 2 + || !UserManager.supportsMultipleUsers() + || mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER)) { removePreference(KEY_ADD_USER); + } else { + mAddUser.setOnPreferenceClickListener(this); + DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( + Context.DEVICE_POLICY_SERVICE); + // No restricted profiles for tablets with a device owner, or phones. + if (dpm.getDeviceOwner() != null || Utils.isVoiceCapable(context)) { + mCanAddRestrictedProfile = false; + mAddUser.setTitle(R.string.user_add_user_menu); + } } 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, + context.registerReceiverAsUser(mUserChangeReceiver, UserHandle.ALL, filter, null, mHandler); } @Override public void onResume() { super.onResume(); + + if (!mEnabled) return; + loadProfile(); updateUserList(); } @@ -216,26 +247,43 @@ public class UserSettings extends RestrictedSettingsFragment @Override public void onDestroy() { super.onDestroy(); + + if (!mEnabled) return; + getActivity().unregisterReceiver(mUserChangeReceiver); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - + mEditUserInfoController.onSaveInstanceState(outState); outState.putInt(SAVE_ADDING_USER, mAddedUserId); outState.putInt(SAVE_REMOVING_USER, mRemovingUserId); } @Override + public void startActivityForResult(Intent intent, int requestCode) { + mEditUserInfoController.startingActivityForResult(); + super.startActivityForResult(intent, requestCode); + } + + @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + int pos = 0; UserManager um = (UserManager) getActivity().getSystemService(Context.USER_SERVICE); if (!mIsOwner && !um.hasUserRestriction(UserManager.DISALLOW_REMOVE_USER)) { String nickname = mUserManager.getUserName(); - MenuItem removeThisUser = menu.add(0, MENU_REMOVE_USER, 0, + MenuItem removeThisUser = menu.add(0, MENU_REMOVE_USER, pos++, getResources().getString(R.string.user_remove_user_menu, nickname)); removeThisUser.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); } + if (mIsOwner && !um.hasUserRestriction(UserManager.DISALLOW_ADD_USER)) { + MenuItem allowAddOnLockscreen = menu.add(0, MENU_ADD_ON_LOCKSCREEN, pos++, + R.string.user_add_on_lockscreen_menu); + allowAddOnLockscreen.setCheckable(true); + allowAddOnLockscreen.setChecked(Settings.Global.getInt(getContentResolver(), + Settings.Global.ADD_USERS_WHEN_LOCKED, 0) == 1); + } super.onCreateOptionsMenu(menu, inflater); } @@ -245,13 +293,28 @@ public class UserSettings extends RestrictedSettingsFragment if (itemId == MENU_REMOVE_USER) { onRemoveUserClicked(UserHandle.myUserId()); return true; + } else if (itemId == MENU_ADD_ON_LOCKSCREEN) { + final boolean isChecked = item.isChecked(); + Settings.Global.putInt(getContentResolver(), Settings.Global.ADD_USERS_WHEN_LOCKED, + isChecked ? 0 : 1); + item.setChecked(!isChecked); + return true; } else { return super.onOptionsItemSelected(item); } } + /** + * Loads profile information for the current user. + */ private void loadProfile() { - mProfileExists = false; + if (mIsGuest) { + // No need to load profile information + mMePreference.setIcon(getEncircledDefaultIcon()); + mMePreference.setTitle(R.string.user_exit_guest_title); + return; + } + new AsyncTask<Void, Void, String>() { @Override protected void onPostExecute(String result) { @@ -264,11 +327,7 @@ public class UserSettings extends RestrictedSettingsFragment if (user.iconPath == null || user.iconPath.equals("")) { assignProfilePhoto(user); } - String profileName = getProfileName(); - if (profileName == null) { - profileName = user.name; - } - return profileName; + return user.name; } }.execute(); } @@ -303,9 +362,9 @@ public class UserSettings extends RestrictedSettingsFragment if (requestCode == REQUEST_CHOOSE_LOCK) { if (resultCode != Activity.RESULT_CANCELED && hasLockscreenSecurity()) { addUserNow(USER_TYPE_RESTRICTED_PROFILE); - } else { - showDialog(DIALOG_NEED_LOCKSCREEN); } + } else { + mEditUserInfoController.onActivityResult(requestCode, resultCode, data); } } @@ -338,19 +397,18 @@ public class UserSettings extends RestrictedSettingsFragment } private UserInfo createLimitedUser() { - UserInfo newUserInfo = mUserManager.createUser( + UserInfo newUserInfo = mUserManager.createSecondaryUser( getResources().getString(R.string.user_new_profile_name), UserInfo.FLAG_RESTRICTED); int userId = newUserInfo.id; UserHandle user = new UserHandle(userId); mUserManager.setUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, true, user); + // Change the setting before applying the DISALLOW_SHARE_LOCATION restriction, otherwise + // the putIntForUser() will fail. + Secure.putIntForUser(getContentResolver(), + Secure.LOCATION_MODE, Secure.LOCATION_MODE_OFF, userId); mUserManager.setUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, true, user); - Secure.putStringForUser(getContentResolver(), - Secure.LOCATION_PROVIDERS_ALLOWED, "", userId); - Bitmap bitmap = BitmapFactory.decodeResource(getResources(), - UserSettings.USER_DRAWABLES[ - userId % UserSettings.USER_DRAWABLES.length]); - mUserManager.setUserIcon(userId, bitmap); + assignDefaultPhoto(newUserInfo); // Add shared accounts AccountManager am = AccountManager.get(getActivity()); Account [] accounts = am.getAccounts(); @@ -363,7 +421,7 @@ public class UserSettings extends RestrictedSettingsFragment } private UserInfo createTrustedUser() { - UserInfo newUserInfo = mUserManager.createUser( + UserInfo newUserInfo = mUserManager.createSecondaryUser( getResources().getString(R.string.user_new_user_name), 0); if (newUserInfo != null) { assignDefaultPhoto(newUserInfo); @@ -372,12 +430,20 @@ public class UserSettings extends RestrictedSettingsFragment } private void onManageUserClicked(int userId, boolean newUser) { + if (userId == UserPreference.USERID_GUEST_DEFAULTS) { + Bundle extras = new Bundle(); + extras.putBoolean(UserDetailsSettings.EXTRA_USER_GUEST, true); + ((SettingsActivity) getActivity()).startPreferencePanel( + UserDetailsSettings.class.getName(), + extras, R.string.user_guest, null, null, 0); + return; + } UserInfo info = mUserManager.getUserInfo(userId); if (info.isRestricted() && mIsOwner) { Bundle extras = new Bundle(); extras.putInt(RestrictedProfileSettings.EXTRA_USER_ID, userId); extras.putBoolean(RestrictedProfileSettings.EXTRA_NEW_USER, newUser); - ((PreferenceActivity) getActivity()).startPreferencePanel( + ((SettingsActivity) getActivity()).startPreferencePanel( RestrictedProfileSettings.class.getName(), extras, R.string.user_restrictions_title, null, null, 0); @@ -390,9 +456,19 @@ public class UserSettings extends RestrictedSettingsFragment int titleResId = info.id == UserHandle.USER_OWNER ? R.string.owner_info_settings_title : (info.isRestricted() ? R.string.profile_info_settings_title : R.string.user_info_settings_title); - ((PreferenceActivity) getActivity()).startPreferencePanel( + ((SettingsActivity) getActivity()).startPreferencePanel( OwnerInfoSettings.class.getName(), extras, titleResId, null, null, 0); + } else if (mIsOwner) { + Bundle extras = new Bundle(); + extras.putInt(UserDetailsSettings.EXTRA_USER_ID, userId); + ((SettingsActivity) getActivity()).startPreferencePanel( + UserDetailsSettings.class.getName(), + extras, + -1, /* No title res id */ + info.name, /* title */ + null, /* resultTo */ + 0 /* resultRequestCode */); } } @@ -418,25 +494,14 @@ public class UserSettings extends RestrictedSettingsFragment if (context == null) return null; switch (dialogId) { case DIALOG_CONFIRM_REMOVE: { - Dialog dlg = new AlertDialog.Builder(getActivity()) - .setTitle(UserHandle.myUserId() == mRemovingUserId - ? R.string.user_confirm_remove_self_title - : (mUserManager.getUserInfo(mRemovingUserId).isRestricted() - ? R.string.user_profile_confirm_remove_title - : R.string.user_confirm_remove_title)) - .setMessage(UserHandle.myUserId() == mRemovingUserId - ? R.string.user_confirm_remove_self_message - : (mUserManager.getUserInfo(mRemovingUserId).isRestricted() - ? R.string.user_profile_confirm_remove_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(); + Dialog dlg = + Utils.createRemoveConfirmationDialog(getActivity(), mRemovingUserId, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + removeUserNow(); + } + } + ); return dlg; } case DIALOG_USER_CANNOT_MANAGE: @@ -537,6 +602,31 @@ public class UserSettings extends RestrictedSettingsFragment .create(); return dlg; } + case DIALOG_CONFIRM_EXIT_GUEST: { + Dialog dlg = new AlertDialog.Builder(context) + .setTitle(R.string.user_exit_guest_confirm_title) + .setMessage(R.string.user_exit_guest_confirm_message) + .setPositiveButton(R.string.user_exit_guest_dialog_remove, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + exitGuest(); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .create(); + return dlg; + } + case DIALOG_USER_PROFILE_EDITOR: { + Dialog dlg = mEditUserInfoController.createDialog( + (Fragment) this, + mMePreference.getIcon(), + mMePreference.getTitle(), + R.string.profile_info_settings_title, + this /* callback */, + android.os.Process.myUserHandle()); + return dlg; + } default: return null; } @@ -604,23 +694,54 @@ public class UserSettings extends RestrictedSettingsFragment } } + /** + * Erase the current user (guest) and switch to another user. + */ + private void exitGuest() { + // Just to be safe + if (!mIsGuest) { + return; + } + removeThisUser(); + } + private void updateUserList() { if (getActivity() == null) return; List<UserInfo> users = mUserManager.getUsers(true); + final Context context = getActivity(); mUserListCategory.removeAll(); mUserListCategory.setOrderingAsAdded(false); mUserListCategory.addPreference(mMePreference); + final boolean voiceCapable = Utils.isVoiceCapable(context); final ArrayList<Integer> missingIcons = new ArrayList<Integer>(); for (UserInfo user : users) { + if (user.isManagedProfile()) { + // Managed profiles appear under Accounts Settings instead + continue; + } Preference pref; if (user.id == UserHandle.myUserId()) { pref = mMePreference; + } else if (user.isGuest()) { + // Skip over Guest. We add generic Guest settings after this loop + continue; } else { - pref = new UserPreference(getActivity(), null, user.id, - mIsOwner && user.isRestricted() ? this : null, - mIsOwner ? this : null); + // With Telephony: + // Secondary user: Settings + // Guest: Settings + // Restricted Profile: There is no Restricted Profile + // Without Telephony: + // Secondary user: Delete + // Guest: Nothing + // Restricted Profile: Settings + final boolean showSettings = mIsOwner && (voiceCapable || user.isRestricted()); + final boolean showDelete = mIsOwner + && (!voiceCapable && !user.isRestricted() && !user.isGuest()); + pref = new UserPreference(context, null, user.id, + showSettings ? this : null, + showDelete ? this : null); pref.setOnPreferenceClickListener(this); pref.setKey("id=" + user.id); mUserListCategory.addPreference(pref); @@ -630,37 +751,69 @@ public class UserSettings extends RestrictedSettingsFragment pref.setTitle(user.name); } if (!isInitialized(user)) { - pref.setSummary(user.isRestricted() - ? R.string.user_summary_restricted_not_set_up - : R.string.user_summary_not_set_up); + if (user.isRestricted()) { + pref.setSummary(R.string.user_summary_restricted_not_set_up); + } else { + pref.setSummary(R.string.user_summary_not_set_up); + } } else if (user.isRestricted()) { pref.setSummary(R.string.user_summary_restricted_profile); } if (user.iconPath != null) { if (mUserIcons.get(user.id) == null) { + // Icon not loaded yet, print a placeholder missingIcons.add(user.id); - pref.setIcon(encircle(R.drawable.avatar_default_1)); + pref.setIcon(getEncircledDefaultIcon()); } else { setPhotoId(pref, user); } + } else { + // Icon not available yet, print a placeholder + pref.setIcon(getEncircledDefaultIcon()); } } + // Add a temporary entry for the user being created if (mAddingUser) { Preference pref = new UserPreference(getActivity(), null, UserPreference.USERID_UNKNOWN, null, null); pref.setEnabled(false); pref.setTitle(R.string.user_new_user_name); - pref.setIcon(encircle(R.drawable.avatar_default_1)); + pref.setIcon(getEncircledDefaultIcon()); + mUserListCategory.addPreference(pref); + } + + boolean showGuestPreference = !mIsGuest; + // If user has DISALLOW_ADD_USER don't allow creating a guest either. + if (showGuestPreference && mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER)) { + showGuestPreference = false; + // If guest already exists, no user creation needed. + for (UserInfo user : users) { + if (user.isGuest()) { + showGuestPreference = true; + break; + } + } + } + if (showGuestPreference) { + // Add a virtual Guest user for guest defaults + Preference pref = new UserPreference(getActivity(), null, + UserPreference.USERID_GUEST_DEFAULTS, + mIsOwner && voiceCapable? this : null /* settings icon handler */, + null /* delete icon handler */); + pref.setTitle(R.string.user_guest); + pref.setIcon(getEncircledDefaultIcon()); + pref.setOnPreferenceClickListener(this); mUserListCategory.addPreference(pref); } + getActivity().invalidateOptionsMenu(); // Load the icons if (missingIcons.size() > 0) { loadIconsAsync(missingIcons); } - boolean moreUsers = mUserManager.getMaxSupportedUsers() > users.size(); + boolean moreUsers = mUserManager.canAddMoreUsers(); mAddUser.setEnabled(moreUsers); } @@ -676,6 +829,10 @@ public class UserSettings extends RestrictedSettingsFragment protected Void doInBackground(List<Integer>... values) { for (int userId : values[0]) { Bitmap bitmap = mUserManager.getUserIcon(userId); + if (bitmap == null) { + bitmap = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon(userId, + /* light= */ false)); + } mUserIcons.append(userId, bitmap); } return null; @@ -689,20 +846,20 @@ public class UserSettings extends RestrictedSettingsFragment } } - 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]); + Bitmap bitmap = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon(user.id, + /* light= */ false)); mUserManager.setUserIcon(user.id, bitmap); } + private Drawable getEncircledDefaultIcon() { + if (mDefaultIconDrawable == null) { + mDefaultIconDrawable = encircle(UserIcons.convertToBitmap( + UserIcons.getDefaultUserIcon(UserHandle.USER_NULL, /* light= */ false))); + } + return mDefaultIconDrawable; + } + private void setPhotoId(Preference pref, UserInfo user) { Bitmap bitmap = mUserIcons.get(user.id); if (bitmap != null) { @@ -719,51 +876,67 @@ public class UserSettings extends RestrictedSettingsFragment @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); + if (mIsGuest) { + showDialog(DIALOG_CONFIRM_EXIT_GUEST); + return true; } - // To make sure that it returns back here when done - // TODO: Make this a proper API - editProfile.putExtra("finishActivityOnSaveCompleted", true); - // If this is a limited user, launch the user info settings instead of profile editor if (mUserManager.isLinkedUser()) { onManageUserClicked(UserHandle.myUserId(), false); } else { - startActivity(editProfile); + showDialog(DIALOG_USER_PROFILE_EDITOR); } } 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); + if (userId == UserPreference.USERID_GUEST_DEFAULTS) { + createAndSwitchToGuestUser(); } else { + // Get the latest status of the user + UserInfo user = mUserManager.getUserInfo(userId); if (!isInitialized(user)) { mHandler.sendMessage(mHandler.obtainMessage( MESSAGE_SETUP_USER, user.id, user.serialNumber)); - } else if (user.isRestricted()) { - onManageUserClicked(user.id, false); + } else { + switchUserNow(userId); } } } else if (pref == mAddUser) { - showDialog(DIALOG_CHOOSE_USER_TYPE); + // If we allow both types, show a picker, otherwise directly go to + // flow for full user. + if (mCanAddRestrictedProfile) { + showDialog(DIALOG_CHOOSE_USER_TYPE); + } else { + onAddUserClicked(USER_TYPE_USER); + } } return false; } - private boolean isInitialized(UserInfo user) { - return (user.flags & UserInfo.FLAG_INITIALIZED) != 0; + private void createAndSwitchToGuestUser() { + List<UserInfo> users = mUserManager.getUsers(); + for (UserInfo user : users) { + if (user.isGuest()) { + switchUserNow(user.id); + return; + } + } + // No guest user. Create one, if there's no restriction. + // If it is not the primary user, then adding users from lockscreen must be enabled + if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER) + || (!mIsOwner && Settings.Global.getInt(getContentResolver(), + Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 1)) { + Log.i(TAG, "Blocking guest creation because it is restricted"); + return; + } + UserInfo guestUser = mUserManager.createGuest(getActivity(), + getResources().getString(R.string.user_guest)); + if (guestUser != null) { + switchUserNow(guestUser.id); + } } - private Drawable encircle(int iconResId) { - Bitmap icon = BitmapFactory.decodeResource(getResources(), iconResId); - return encircle(icon); + private boolean isInitialized(UserInfo user) { + return (user.flags & UserInfo.FLAG_INITIALIZED) != 0; } private Drawable encircle(Bitmap icon) { @@ -812,4 +985,14 @@ public class UserSettings extends RestrictedSettingsFragment public int getHelpResource() { return R.string.help_url_users; } + + @Override + public void onPhotoChanged(Drawable photo) { + mMePreference.setIcon(photo); + } + + @Override + public void onLabelChanged(CharSequence label) { + mMePreference.setTitle(label); + } } diff --git a/src/com/android/settings/users/UserUtils.java b/src/com/android/settings/users/UserUtils.java deleted file mode 100644 index 946d871..0000000 --- a/src/com/android/settings/users/UserUtils.java +++ /dev/null @@ -1,34 +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.content.Context; -import android.content.pm.UserInfo; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.os.UserManager; - -public class UserUtils { - public static Drawable getUserIcon(Context context, UserManager um, UserInfo user, Resources res) { - if (user.iconPath == null) return null; - Bitmap icon = um.getUserIcon(user.id); - if (icon == null) return null; - return CircleFramedDrawable.getInstance(context, icon); - } -} |