Improve multi user functionality for restricted users

- Restricted users can change their name
- Improved App&Content access screen
- Remove "Turn on phone calls" from restricted user detail page

Doc: http://shortn/_Prb3SJ3xJ3
Bug: 142798722
Test: Run robo tests with this command:
  make -j64 RunSettingsRoboTests
Change-Id: I2aadf32aef52ba5ad0db7aa0cd83bac9d9941589
(cherry picked from commit f4759e00d5)
This commit is contained in:
Andras Kloczl
2020-05-18 14:53:07 +01:00
committed by András Klöczl
parent ff35a3e641
commit 106431e525
14 changed files with 311 additions and 404 deletions

View File

@@ -25,7 +25,6 @@
android:layout_width="56dip"
android:layout_height="56dip"
android:layout_gravity="bottom"
android:layout_marginEnd="6dp"
android:contentDescription="@string/user_image_photo_selector"
android:background="@*android:drawable/spinner_background_holo_dark"
android:scaleType="fitCenter"/>

View File

@@ -1,129 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical"
android:orientation="vertical"
android:focusable="true"
android:clickable="true" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:id="@+id/app_restrictions_pref"
android:gravity="center_vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:background="?android:attr/selectableItemBackground" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:minWidth="@*android:dimen/preference_icon_minWidth"
android:orientation="horizontal">
<ImageView
android:id="@android:id/icon"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:minWidth="48dp"
android:scaleType="centerInside"
android:layout_marginEnd="@*android:dimen/preference_item_padding_inner"/>
</LinearLayout>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="6dip"
android:layout_marginTop="6dip"
android:layout_marginBottom="6dip"
android:layout_weight="1">
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
tools:text="Richard"
android:textAppearance="?android:attr/textAppearanceMedium"
android:ellipsize="marquee"
android:fadingEdge="horizontal"/>
<TextView
android:id="@android:id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@android:id/title"
android:layout_alignStart="@android:id/title"
android:paddingBottom="3dip"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="13sp"
android:textColor="?android:attr/textColorSecondary"
android:focusable="false"
android:text="@string/user_summary_restricted_profile"
android:maxLines="4" />
</RelativeLayout>
<ImageView
android:id="@+id/delete"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:paddingStart="16dip"
android:paddingEnd="16dip"
android:src="@drawable/ic_delete"
android:contentDescription="@string/user_delete_user_description"
android:layout_gravity="center"
android:background="?android:attr/selectableItemBackground" />
</LinearLayout>
<LinearLayout
android:id="@+id/switch_pref"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:visibility="gone"
tools:visibility="visible"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:background="?android:attr/selectableItemBackground" >
<ImageView
android:id="@+id/switchIcon"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:src="@drawable/ic_swap"
android:minWidth="48dp"
android:scaleType="centerInside"
android:layout_marginEnd="@*android:dimen/preference_item_padding_inner"/>
<TextView
android:id="@+id/switchTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
tools:text="Switch to Richard"
android:textAppearance="?android:attr/textAppearanceMedium"
android:ellipsize="marquee"
android:fadingEdge="horizontal"/>
</LinearLayout>
<View android:layout_width="match_parent"
android:layout_height="2dp"
android:background="@color/divider_color" />
</LinearLayout>

View File

@@ -25,6 +25,10 @@
android:key="enable_calling"
android:icon="@drawable/ic_phone"
android:title="@string/user_enable_calling_sms" />
<com.android.settingslib.RestrictedPreference
android:key="app_and_content_access"
android:icon="@drawable/ic_lock_closed"
android:title="@string/user_restrictions_title" />
<com.android.settingslib.RestrictedPreference
android:key="remove_user"
android:icon="@drawable/ic_delete"

View File

@@ -30,7 +30,6 @@ import androidx.fragment.app.FragmentActivity;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import com.android.settings.users.RestrictedProfileSettings;
import com.android.settingslib.license.LicenseHtmlLoaderCompat;
import java.io.File;
@@ -78,7 +77,7 @@ public class SettingsLicenseActivity extends FragmentActivity implements
@VisibleForTesting
Uri getUriFromGeneratedHtmlFile(File generatedHtmlFile) {
return FileProvider.getUriForFile(this, RestrictedProfileSettings.FILE_PROVIDER_AUTHORITY,
return FileProvider.getUriForFile(this, Utils.FILE_PROVIDER_AUTHORITY,
generatedHtmlFile);
}

View File

@@ -116,6 +116,8 @@ public final class Utils extends com.android.settingslib.Utils {
private static final String TAG = "Settings";
public static final String FILE_PROVIDER_AUTHORITY = "com.android.settings.files";
/**
* Set the preference's title to the matching activity's label.
*/

View File

@@ -97,15 +97,8 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen
/** Key for extra passed in from calling fragment to indicate if this is a newly created user */
public static final String EXTRA_NEW_USER = "new_user";
/**
* Key for extra passed in from calling fragment to indicate if
* switch to user should be shown
*/
public static final String EXTRA_SHOW_SWITCH_USER = "enable_switch";
private boolean mFirstTime = true;
private boolean mNewUser;
protected boolean mShowSwitchUser;
private boolean mAppListChanged;
protected boolean mRestrictedProfile;
@@ -216,6 +209,12 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen
}
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
init(icicle);
}
protected void init(Bundle icicle) {
if (icicle != null) {
mUser = new UserHandle(icicle.getInt(EXTRA_USER_ID));
@@ -226,7 +225,6 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen
mUser = new UserHandle(args.getInt(EXTRA_USER_ID));
}
mNewUser = args.getBoolean(EXTRA_NEW_USER, false);
mShowSwitchUser = args.getBoolean(EXTRA_SHOW_SWITCH_USER, false);
}
}

View File

@@ -18,8 +18,10 @@ package com.android.settings.users;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
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.Bundle;
@@ -77,7 +79,9 @@ public class EditUserInfoController {
}
public void clear() {
if (mEditUserPhotoController != null) {
mEditUserPhotoController.removeNewUserPhotoBitmapFile();
}
mEditUserInfoDialog = null;
mSavedPhoto = null;
}
@@ -116,7 +120,7 @@ public class EditUserInfoController {
public void onActivityResult(int requestCode, int resultCode, Intent data) {
mWaitingForActivityResult = false;
if (mEditUserInfoDialog != null) {
if (mEditUserPhotoController != null && mEditUserInfoDialog != null) {
mEditUserPhotoController.onActivityResult(requestCode, resultCode, data);
}
}
@@ -137,6 +141,14 @@ public class EditUserInfoController {
userNameView.setText(currentUserName);
final ImageView userPhotoView = (ImageView) content.findViewById(R.id.user_photo);
boolean canChangePhoto = mUserManager != null &&
canChangePhoto(activity, mUserManager.getUserInfo(user.getIdentifier()));
if (!canChangePhoto) {
// some users can't change their photos so we need to remove suggestive
// background from the photoView
userPhotoView.setBackground(null);
}
Drawable drawable = null;
if (mSavedPhoto != null) {
drawable = CircleFramedDrawable.getInstance(activity, mSavedPhoto);
@@ -144,7 +156,10 @@ public class EditUserInfoController {
drawable = currentUserIcon;
}
userPhotoView.setImageDrawable(drawable);
mEditUserPhotoController = createEditUserPhotoController(fragment, userPhotoView, drawable);
if (canChangePhoto) {
mEditUserPhotoController =
createEditUserPhotoController(fragment, userPhotoView, drawable);
}
mEditUserInfoDialog = new AlertDialog.Builder(activity)
.setTitle(title)
.setView(content)
@@ -165,14 +180,15 @@ public class EditUserInfoController {
}
}
// Update the photo if changed.
Drawable drawable = mEditUserPhotoController.getNewUserPhotoDrawable();
if (mEditUserPhotoController != null) {
Drawable drawable =
mEditUserPhotoController.getNewUserPhotoDrawable();
if (drawable != null && !drawable.equals(currentUserIcon)) {
if (callback != null) {
callback.onPhotoChanged(mUser, drawable);
}
}
fragment.getActivity().removeDialog(
RestrictedProfileSettings.DIALOG_ID_EDIT_USER_INFO);
}
}
clear();
if (completeCallback != null) {
@@ -207,6 +223,13 @@ public class EditUserInfoController {
return mEditUserInfoDialog;
}
@VisibleForTesting
boolean canChangePhoto(Context context, UserInfo user) {
return PhotoCapabilityUtils.canCropPhoto(context) &&
(PhotoCapabilityUtils.canChoosePhoto(context)
|| PhotoCapabilityUtils.canTakePhoto(context));
}
@VisibleForTesting
EditUserPhotoController createEditUserPhotoController(Fragment fragment,
ImageView userPhotoView, Drawable drawable) {

View File

@@ -21,7 +21,6 @@ import android.content.ClipData;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
@@ -52,6 +51,7 @@ import androidx.core.content.FileProvider;
import androidx.fragment.app.Fragment;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.drawable.CircleFramedDrawable;
@@ -141,14 +141,14 @@ public class EditUserPhotoController {
}
private void showUpdatePhotoPopup() {
final boolean canTakePhoto = canTakePhoto();
final boolean canChoosePhoto = canChoosePhoto();
final Context context = mImageView.getContext();
final boolean canTakePhoto = PhotoCapabilityUtils.canTakePhoto(context);
final boolean canChoosePhoto = PhotoCapabilityUtils.canChoosePhoto(context);
if (!canTakePhoto && !canChoosePhoto) {
return;
}
final Context context = mImageView.getContext();
final List<EditUserPhotoController.RestrictedMenuItem> items = new ArrayList<>();
if (canTakePhoto) {
@@ -200,19 +200,6 @@ public class EditUserPhotoController {
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);
@@ -369,8 +356,7 @@ public class EditUserPhotoController {
if (purge) {
fullPath.delete();
}
return FileProvider.getUriForFile(context,
RestrictedProfileSettings.FILE_PROVIDER_AUTHORITY, fullPath);
return FileProvider.getUriForFile(context, Utils.FILE_PROVIDER_AUTHORITY, fullPath);
}
File saveNewUserPhotoBitmap() {

View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) 2020 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.Intent;
import android.content.pm.PackageManager;
import android.provider.MediaStore;
class PhotoCapabilityUtils {
/**
* Check if the current user can perform any activity for
* android.media.action.IMAGE_CAPTURE action.
*/
static boolean canTakePhoto(Context context) {
return context.getPackageManager().queryIntentActivities(
new Intent(MediaStore.ACTION_IMAGE_CAPTURE),
PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
}
/**
* Check if the current user can perform any activity for
* android.intent.action.GET_CONTENT action for images.
*/
static boolean canChoosePhoto(Context context) {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
return context.getPackageManager().queryIntentActivities(intent, 0).size() > 0;
}
/**
* Check if the current user can perform any activity for
* com.android.camera.action.CROP action for images.
*/
static boolean canCropPhoto(Context context) {
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setType("image/*");
return context.getPackageManager().queryIntentActivities(intent, 0).size() > 0;
}
}

View File

@@ -1,208 +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.app.ActivityManager;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.UserInfo;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.internal.util.UserIcons;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settingslib.utils.ThreadUtils;
public class RestrictedProfileSettings extends AppRestrictionsFragment
implements EditUserInfoController.OnContentChangedCallback {
private static final String TAG = RestrictedProfileSettings.class.getSimpleName();
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 View mSwitchUserView;
private TextView mSwitchTitle;
private EditUserInfoController mEditUserInfoController =
new EditUserInfoController();
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
if (icicle != null) {
mEditUserInfoController.onRestoreInstanceState(icicle);
}
init(icicle);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
mHeaderView = setPinnedHeaderView(R.layout.user_info_header);
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);
mSwitchTitle = mHeaderView.findViewById(R.id.switchTitle);
mSwitchUserView = mHeaderView.findViewById(R.id.switch_pref);
mSwitchUserView.setOnClickListener(v -> switchUser());
// This is going to bind the preferences.
super.onActivityCreated(savedInstanceState);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mEditUserInfoController.onSaveInstanceState(outState);
}
@Override
public void onResume() {
super.onResume();
// Check if user still exists
UserInfo info = Utils.getExistingUser(mUserManager, mUser);
if (info == null) {
finishFragment();
} else {
((TextView) mHeaderView.findViewById(android.R.id.title)).setText(info.name);
((ImageView) mHeaderView.findViewById(android.R.id.icon)).setImageDrawable(
com.android.settingslib.Utils.getUserIcon(getActivity(), mUserManager, info));
boolean canSwitchUser =
mUserManager.getUserSwitchability() == UserManager.SWITCHABILITY_STATUS_OK;
if (mShowSwitchUser && canSwitchUser) {
mSwitchUserView.setVisibility(View.VISIBLE);
mSwitchTitle.setText(getString(com.android.settingslib.R.string.user_switch_to_user,
info.name));
} else {
mSwitchUserView.setVisibility(View.GONE);
}
}
}
@Override
public void startActivityForResult(Intent intent, int requestCode) {
mEditUserInfoController.startingActivityForResult();
super.startActivityForResult(intent, requestCode);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
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
}
}
@Override
public Dialog onCreateDialog(int dialogId) {
if (dialogId == DIALOG_ID_EDIT_USER_INFO) {
return mEditUserInfoController.createDialog(this, mUserIconView.getDrawable(),
mUserNameView.getText(), getString(R.string.profile_info_settings_title),
this, mUser, null);
} else if (dialogId == DIALOG_CONFIRM_REMOVE) {
Dialog dlg =
UserDialogs.createRemoveDialog(getActivity(), mUser.getIdentifier(),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
removeUser();
}
}
);
return dlg;
}
return null;
}
@Override
public int getDialogMetricsCategory(int dialogId) {
switch (dialogId) {
case DIALOG_ID_EDIT_USER_INFO:
return SettingsEnums.DIALOG_USER_EDIT;
case DIALOG_CONFIRM_REMOVE:
return SettingsEnums.DIALOG_USER_REMOVE;
default:
return 0;
}
}
private void removeUser() {
getView().post(new Runnable() {
public void run() {
mUserManager.removeUser(mUser.getIdentifier());
finishFragment();
}
});
}
private void switchUser() {
try {
ActivityManager.getService().switchUser(mUser.getIdentifier());
} catch (RemoteException re) {
Log.e(TAG, "Error while switching to other user.");
} finally {
finishFragment();
}
}
@Override
public void onPhotoChanged(UserHandle user, Drawable photo) {
mUserIconView.setImageDrawable(photo);
ThreadUtils.postOnBackgroundThread(new Runnable() {
@Override
public void run() {
mUserManager.setUserIcon(user.getIdentifier(), UserIcons.convertToBitmap(photo));
}
});
}
@Override
public void onLabelChanged(UserHandle user, CharSequence label) {
mUserNameView.setText(label);
mUserManager.setUserName(user.getIdentifier(), label.toString());
}
}

View File

@@ -36,6 +36,7 @@ import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.Utils;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtilsInternal;
@@ -56,6 +57,7 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
private static final String KEY_SWITCH_USER = "switch_user";
private static final String KEY_ENABLE_TELEPHONY = "enable_calling";
private static final String KEY_REMOVE_USER = "remove_user";
private static final String KEY_APP_AND_CONTENT_ACCESS = "app_and_content_access";
/** Integer extra containing the userId to manage */
static final String EXTRA_USER_ID = "user_id";
@@ -69,6 +71,8 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
Preference mSwitchUserPref;
private SwitchPreference mPhonePref;
@VisibleForTesting
Preference mAppAndContentAccessPref;
@VisibleForTesting
Preference mRemoveUserPref;
@VisibleForTesting
@@ -109,6 +113,8 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
switchUser();
}
return true;
} else if (preference == mAppAndContentAccessPref) {
openAppAndContentAccessScreen(false);
}
return false;
}
@@ -170,11 +176,14 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
if (userId == USER_NULL) {
throw new IllegalStateException("Arguments to this fragment must contain the user id");
}
boolean isNewUser =
arguments.getBoolean(AppRestrictionsFragment.EXTRA_NEW_USER, false);
mUserInfo = mUserManager.getUserInfo(userId);
mSwitchUserPref = findPreference(KEY_SWITCH_USER);
mPhonePref = findPreference(KEY_ENABLE_TELEPHONY);
mRemoveUserPref = findPreference(KEY_REMOVE_USER);
mAppAndContentAccessPref = findPreference(KEY_APP_AND_CONTENT_ACCESS);
mSwitchUserPref.setTitle(
context.getString(com.android.settingslib.R.string.user_switch_to_user,
@@ -184,16 +193,24 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
if (!mUserManager.isAdminUser()) { // non admin users can't remove users and allow calls
removePreference(KEY_ENABLE_TELEPHONY);
removePreference(KEY_REMOVE_USER);
removePreference(KEY_APP_AND_CONTENT_ACCESS);
} else {
if (!Utils.isVoiceCapable(context)) { // no telephony
removePreference(KEY_ENABLE_TELEPHONY);
}
if (!mUserInfo.isGuest()) {
mPhonePref.setChecked(!mUserManager.hasUserRestriction(
UserManager.DISALLOW_OUTGOING_CALLS, new UserHandle(userId)));
mRemoveUserPref.setTitle(R.string.user_remove_user);
if (mUserInfo.isRestricted()) {
removePreference(KEY_ENABLE_TELEPHONY);
if (isNewUser) {
// for newly created restricted users we should open the apps and content access
// screen to initialize the default restrictions
openAppAndContentAccessScreen(true);
}
} else {
removePreference(KEY_APP_AND_CONTENT_ACCESS);
}
if (mUserInfo.isGuest()) {
// These are not for an existing user, just general Guest settings.
// Default title is for calling and SMS. Change to calling-only here
mPhonePref.setTitle(R.string.user_enable_calling);
@@ -201,6 +218,10 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
mPhonePref.setChecked(
!mDefaultGuestRestrictions.getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
mRemoveUserPref.setTitle(R.string.user_exit_guest_title);
} else {
mPhonePref.setChecked(!mUserManager.hasUserRestriction(
UserManager.DISALLOW_OUTGOING_CALLS, new UserHandle(userId)));
mRemoveUserPref.setTitle(R.string.user_remove_user);
}
if (RestrictedLockUtilsInternal.hasBaseUserRestriction(context,
UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId())) {
@@ -209,6 +230,7 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
mRemoveUserPref.setOnPreferenceClickListener(this);
mPhonePref.setOnPreferenceChangeListener(this);
mAppAndContentAccessPref.setOnPreferenceClickListener(this);
}
}
@@ -283,4 +305,20 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
mUserManager.removeUser(mUserInfo.id);
finishFragment();
}
/**
* @param isNewUser indicates if a user was created recently, for new users
* AppRestrictionsFragment should set the default restrictions
*/
private void openAppAndContentAccessScreen(boolean isNewUser) {
Bundle extras = new Bundle();
extras.putInt(AppRestrictionsFragment.EXTRA_USER_ID, mUserInfo.id);
extras.putBoolean(AppRestrictionsFragment.EXTRA_NEW_USER, isNewUser);
new SubSettingLauncher(getContext())
.setDestination(AppRestrictionsFragment.class.getName())
.setArguments(extras)
.setTitleRes(R.string.user_restrictions_title)
.setSourceMetricsCategory(getMetricsCategory())
.launch();
}
}

View File

@@ -485,23 +485,13 @@ public class UserSettings extends SettingsPreferenceFragment
private void onManageUserClicked(int userId, boolean newUser) {
mAddingUser = false;
UserInfo userInfo = mUserManager.getUserInfo(userId);
if (userInfo.isRestricted() && mUserCaps.mIsAdmin) {
Bundle extras = new Bundle();
extras.putInt(RestrictedProfileSettings.EXTRA_USER_ID, userId);
extras.putBoolean(RestrictedProfileSettings.EXTRA_NEW_USER, newUser);
extras.putBoolean(RestrictedProfileSettings.EXTRA_SHOW_SWITCH_USER, canSwitchUserNow());
new SubSettingLauncher(getContext())
.setDestination(RestrictedProfileSettings.class.getName())
.setArguments(extras)
.setTitleRes(R.string.user_restrictions_title)
.setSourceMetricsCategory(getMetricsCategory())
.launch();
} else if (userId == UserHandle.myUserId()) {
if (userId == UserHandle.myUserId()) {
// Jump to owner info panel
OwnerInfoSettings.show(this);
} else {
Bundle extras = new Bundle();
extras.putInt(UserDetailsSettings.EXTRA_USER_ID, userId);
extras.putBoolean(AppRestrictionsFragment.EXTRA_NEW_USER, newUser);
new SubSettingLauncher(getContext())
.setDestination(UserDetailsSettings.class.getName())
.setArguments(extras)
@@ -963,10 +953,10 @@ public class UserSettings extends SettingsPreferenceFragment
pref.setSummary(R.string.user_summary_restricted_not_set_up);
} else {
pref.setSummary(R.string.user_summary_not_set_up);
}
// Disallow setting up user which results in user switching when the restriction is
// set.
// Disallow setting up user which results in user switching when the
// restriction is set.
pref.setEnabled(!mUserCaps.mDisallowSwitchUser && canSwitchUserNow());
}
} else if (user.isRestricted()) {
pref.setSummary(R.string.user_summary_restricted_profile);
}
@@ -1137,17 +1127,14 @@ public class UserSettings extends SettingsPreferenceFragment
showDialog(DIALOG_CONFIRM_EXIT_GUEST);
return true;
}
// If this is a limited user, launch the user info settings instead of profile editor
if (mUserManager.isRestrictedProfile()) {
onManageUserClicked(UserHandle.myUserId(), false);
} else {
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 (!user.isInitialized()) {
if (!user.isInitialized() && isSecondaryUser(user)) {
// for uninitialized secondary users we should show a prompt dialog before
// starting the setup
mHandler.sendMessage(mHandler.obtainMessage(
MESSAGE_SETUP_USER, user.id, user.serialNumber));
} else {
@@ -1279,4 +1266,7 @@ public class UserSettings extends SettingsPreferenceFragment
}
};
private boolean isSecondaryUser(UserInfo user) {
return UserManager.USER_TYPE_FULL_SECONDARY.equals(user.userType);
}
}

View File

@@ -28,7 +28,9 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.content.pm.UserInfo;
import android.graphics.drawable.Drawable;
import android.widget.EditText;
import android.widget.ImageView;
@@ -62,6 +64,8 @@ public class EditUserInfoControllerTest {
@Mock
private Drawable mCurrentIcon;
private boolean mCanChangePhoto;
private FragmentActivity mActivity;
private TestEditUserInfoController mController;
@@ -78,6 +82,11 @@ public class EditUserInfoControllerTest {
mPhotoController = mock(EditUserPhotoController.class, Answers.RETURNS_DEEP_STUBS);
return mPhotoController;
}
@Override
boolean canChangePhoto(Context context, UserInfo user) {
return mCanChangePhoto;
}
}
@Before
@@ -86,6 +95,7 @@ public class EditUserInfoControllerTest {
mActivity = spy(ActivityController.of(new FragmentActivity()).get());
when(mFragment.getActivity()).thenReturn(mActivity);
mController = new TestEditUserInfoController();
mCanChangePhoto = true;
}
@Test
@@ -256,4 +266,17 @@ public class EditUserInfoControllerTest {
verify(dialogCompleteCallback, times(1)).onPositive();
verify(dialogCompleteCallback, times(0)).onNegativeOrCancel();
}
@Test
public void createDialog_canNotChangePhoto_nullPhotoController() {
mCanChangePhoto = false;
mController.createDialog(
mFragment, mCurrentIcon, "test",
"title", null,
android.os.Process.myUserHandle(),
null);
assertThat(mController.mPhotoController).isNull();
}
}

View File

@@ -32,9 +32,11 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.robolectric.Shadows.shadowOf;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.UserHandle;
@@ -47,6 +49,8 @@ import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.SubSettings;
import com.android.settings.testutils.shadow.ShadowDevicePolicyManager;
import com.android.settings.testutils.shadow.ShadowUserManager;
@@ -61,6 +65,7 @@ import org.robolectric.RuntimeEnvironment;
import org.robolectric.android.controller.ActivityController;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowIntent;
import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
@@ -76,6 +81,7 @@ public class UserDetailsSettingsTest {
private static final String KEY_SWITCH_USER = "switch_user";
private static final String KEY_ENABLE_TELEPHONY = "enable_calling";
private static final String KEY_REMOVE_USER = "remove_user";
private static final String KEY_APP_AND_CONTENT_ACCESS = "app_and_content_access";
private static final int DIALOG_CONFIRM_REMOVE = 1;
@@ -90,6 +96,8 @@ public class UserDetailsSettingsTest {
private SwitchPreference mPhonePref;
@Mock
private Preference mRemoveUserPref;
@Mock
private Preference mAppAndContentAccessPref;
private FragmentActivity mActivity;
private Context mContext;
@@ -114,14 +122,15 @@ public class UserDetailsSettingsTest {
ReflectionHelpers.setField(mFragment, "mUserManager", userManager);
doReturn(mActivity).when(mFragment).getActivity();
doReturn(mContext).when(mFragment).getContext();
doReturn(mActivity).when(mFragment).getContext();
doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen();
doReturn("").when(mActivity).getString(anyInt(), anyString());
doReturn(mSwitchUserPref).when(mFragment).findPreference(KEY_SWITCH_USER);
doReturn(mPhonePref).when(mFragment).findPreference(KEY_ENABLE_TELEPHONY);
doReturn(mRemoveUserPref).when(mFragment).findPreference(KEY_REMOVE_USER);
doReturn(mAppAndContentAccessPref)
.when(mFragment).findPreference(KEY_APP_AND_CONTENT_ACCESS);
}
@After
@@ -169,6 +178,24 @@ public class UserDetailsSettingsTest {
verify(mFragment, never()).removePreference(KEY_SWITCH_USER);
}
@Test
public void initialize_userSelected_shouldNotShowAppAndContentPref() {
setupSelectedUser();
mFragment.initialize(mActivity, mArguments);
verify(mFragment).removePreference(KEY_APP_AND_CONTENT_ACCESS);
}
@Test
public void initialize_guestSelected_shouldNotShowAppAndContentPref() {
setupSelectedGuest();
mFragment.initialize(mActivity, mArguments);
verify(mFragment).removePreference(KEY_APP_AND_CONTENT_ACCESS);
}
@Test
public void onResume_canSwitch_shouldEnableSwitchPref() {
mUserManager.setSwitchabilityStatus(SWITCHABILITY_STATUS_OK);
@@ -248,6 +275,16 @@ public class UserDetailsSettingsTest {
verify(mFragment).removePreference(KEY_ENABLE_TELEPHONY);
}
@Test
public void initialize_nonAdmin_shouldNotShowAppAndContentPref() {
setupSelectedUser();
mUserManager.setIsAdminUser(false);
mFragment.initialize(mActivity, mArguments);
verify(mFragment).removePreference(KEY_APP_AND_CONTENT_ACCESS);
}
@Test
public void initialize_adminSelectsSecondaryUser_shouldShowRemovePreference() {
setupSelectedUser();
@@ -260,6 +297,57 @@ public class UserDetailsSettingsTest {
verify(mFragment, never()).removePreference(KEY_REMOVE_USER);
}
@Test
public void initialize_adminSelectsNewRestrictedUser_shouldOpenAppContentScreen() {
setupSelectedRestrictedUser();
mUserManager.setIsAdminUser(true);
mArguments.putBoolean(AppRestrictionsFragment.EXTRA_NEW_USER, true);
mFragment.initialize(mActivity, mArguments);
Intent startedIntent = shadowOf(mActivity).getNextStartedActivity();
ShadowIntent shadowIntent = shadowOf(startedIntent);
assertThat(shadowIntent.getIntentClass()).isEqualTo(SubSettings.class);
assertThat(startedIntent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
.isEqualTo(AppRestrictionsFragment.class.getName());
Bundle arguments = startedIntent.getBundleExtra(
SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
assertThat(arguments).isNotNull();
assertThat(arguments.getInt(AppRestrictionsFragment.EXTRA_USER_ID, 0))
.isEqualTo(mUserInfo.id);
assertThat(arguments.getBoolean(AppRestrictionsFragment.EXTRA_NEW_USER, false))
.isEqualTo(true);
}
@Test
public void initialize_adminSelectsRestrictedUser_shouldSetupPreferences() {
setupSelectedRestrictedUser();
mUserManager.setIsAdminUser(true);
doReturn(true).when(mTelephonyManager).isVoiceCapable();
mFragment.initialize(mActivity, mArguments);
verify(mFragment, never()).removePreference(KEY_REMOVE_USER);
verify(mFragment, never()).removePreference(KEY_SWITCH_USER);
verify(mFragment, never()).removePreference(KEY_APP_AND_CONTENT_ACCESS);
verify(mFragment).removePreference(KEY_ENABLE_TELEPHONY);
verify(mSwitchUserPref).setTitle("Switch to " + mUserInfo.name);
verify(mAppAndContentAccessPref).setOnPreferenceClickListener(mFragment);
verify(mSwitchUserPref).setOnPreferenceClickListener(mFragment);
verify(mRemoveUserPref).setOnPreferenceClickListener(mFragment);
}
@Test
public void initialize_adminSelectsExistingRestrictedUser_shouldNotStartAppAndContentAccess() {
setupSelectedRestrictedUser();
mUserManager.setIsAdminUser(true);
mArguments.putBoolean(AppRestrictionsFragment.EXTRA_NEW_USER, false);
mFragment.initialize(mActivity, mArguments);
verify(mActivity, never()).startActivity(any(Intent.class));
}
@Test
public void initialize_adminSelectsGuest_shouldShowRemovePreference() {
setupSelectedGuest();
@@ -344,6 +432,7 @@ public class UserDetailsSettingsTest {
mUserManager.setSwitchabilityStatus(SWITCHABILITY_STATUS_OK);
mFragment.mSwitchUserPref = mSwitchUserPref;
mFragment.mRemoveUserPref = mRemoveUserPref;
mFragment.mAppAndContentAccessPref = mAppAndContentAccessPref;
mFragment.mUserInfo = mUserInfo;
mFragment.onPreferenceClick(mSwitchUserPref);
@@ -357,6 +446,7 @@ public class UserDetailsSettingsTest {
mUserManager.setSwitchabilityStatus(SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED);
mFragment.mSwitchUserPref = mSwitchUserPref;
mFragment.mRemoveUserPref = mRemoveUserPref;
mFragment.mAppAndContentAccessPref = mAppAndContentAccessPref;
mFragment.mUserInfo = mUserInfo;
mFragment.onPreferenceClick(mSwitchUserPref);
@@ -371,6 +461,7 @@ public class UserDetailsSettingsTest {
mUserManager.setIsAdminUser(true);
mFragment.mSwitchUserPref = mSwitchUserPref;
mFragment.mRemoveUserPref = mRemoveUserPref;
mFragment.mAppAndContentAccessPref = mAppAndContentAccessPref;
doNothing().when(mFragment).showDialog(anyInt());
mFragment.onPreferenceClick(mRemoveUserPref);
@@ -386,6 +477,7 @@ public class UserDetailsSettingsTest {
mUserManager.setIsAdminUser(false);
mFragment.mSwitchUserPref = mSwitchUserPref;
mFragment.mRemoveUserPref = mRemoveUserPref;
mFragment.mAppAndContentAccessPref = mAppAndContentAccessPref;
doNothing().when(mFragment).showDialog(anyInt());
mFragment.onPreferenceClick(mRemoveUserPref);
@@ -394,12 +486,37 @@ public class UserDetailsSettingsTest {
verify(mFragment, never()).showDialog(DIALOG_CONFIRM_REMOVE);
}
@Test
public void onPreferenceClick_selectRestrictedUser_appAndContentAccessClicked_startActivity() {
setupSelectedRestrictedUser();
mFragment.mUserInfo = mUserInfo;
mUserManager.setIsAdminUser(true);
mFragment.mSwitchUserPref = mSwitchUserPref;
mFragment.mRemoveUserPref = mRemoveUserPref;
mFragment.mAppAndContentAccessPref = mAppAndContentAccessPref;
mFragment.onPreferenceClick(mAppAndContentAccessPref);
Intent startedIntent = shadowOf(mActivity).getNextStartedActivity();
ShadowIntent shadowIntent = shadowOf(startedIntent);
assertThat(shadowIntent.getIntentClass()).isEqualTo(SubSettings.class);
assertThat(startedIntent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
.isEqualTo(AppRestrictionsFragment.class.getName());
Bundle arguments = startedIntent.getBundleExtra(
SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
assertThat(arguments.getInt(AppRestrictionsFragment.EXTRA_USER_ID, 0))
.isEqualTo(mUserInfo.id);
assertThat(arguments.getBoolean(AppRestrictionsFragment.EXTRA_NEW_USER, true))
.isEqualTo(false);
}
@Test
public void onPreferenceClick_unknownPreferenceClicked_doNothing() {
setupSelectedUser();
mFragment.mUserInfo = mUserInfo;
mFragment.mSwitchUserPref = mSwitchUserPref;
mFragment.mRemoveUserPref = mRemoveUserPref;
mFragment.mAppAndContentAccessPref = mAppAndContentAccessPref;
mFragment.onPreferenceClick(mock(UserPreference.class));
@@ -464,4 +581,13 @@ public class UserDetailsSettingsTest {
mUserManager.addProfile(mUserInfo);
}
private void setupSelectedRestrictedUser() {
mArguments.putInt("user_id", 21);
mUserInfo = new UserInfo(21, "Bob", null,
UserInfo.FLAG_FULL | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_RESTRICTED,
UserManager.USER_TYPE_FULL_RESTRICTED);
mUserManager.addProfile(mUserInfo);
}
}