From 106431e525e6f99bb8711ee8a846d95f35eedaaa Mon Sep 17 00:00:00 2001 From: Andras Kloczl Date: Mon, 18 May 2020 14:53:07 +0100 Subject: [PATCH] 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 f4759e00d53671b273dee3d6c9e62203df6f629b) --- res/layout/edit_user_info_dialog_content.xml | 1 - res/layout/user_info_header.xml | 129 ----------- res/xml/user_details_settings.xml | 4 + .../settings/SettingsLicenseActivity.java | 3 +- src/com/android/settings/Utils.java | 2 + .../users/AppRestrictionsFragment.java | 14 +- .../users/EditUserInfoController.java | 41 +++- .../users/EditUserPhotoController.java | 24 +- .../settings/users/PhotoCapabilityUtils.java | 56 +++++ .../users/RestrictedProfileSettings.java | 208 ------------------ .../settings/users/UserDetailsSettings.java | 46 +++- .../android/settings/users/UserSettings.java | 34 +-- .../users/EditUserInfoControllerTest.java | 23 ++ .../users/UserDetailsSettingsTest.java | 130 ++++++++++- 14 files changed, 311 insertions(+), 404 deletions(-) delete mode 100644 res/layout/user_info_header.xml create mode 100644 src/com/android/settings/users/PhotoCapabilityUtils.java delete mode 100644 src/com/android/settings/users/RestrictedProfileSettings.java diff --git a/res/layout/edit_user_info_dialog_content.xml b/res/layout/edit_user_info_dialog_content.xml index 895cef63fb9..2bd464b4233 100644 --- a/res/layout/edit_user_info_dialog_content.xml +++ b/res/layout/edit_user_info_dialog_content.xml @@ -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"/> diff --git a/res/layout/user_info_header.xml b/res/layout/user_info_header.xml deleted file mode 100644 index bfdf3fc7324..00000000000 --- a/res/layout/user_info_header.xml +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/xml/user_details_settings.xml b/res/xml/user_details_settings.xml index d336395d02a..9280ff11632 100644 --- a/res/xml/user_details_settings.xml +++ b/res/xml/user_details_settings.xml @@ -25,6 +25,10 @@ android:key="enable_calling" android:icon="@drawable/ic_phone" android:title="@string/user_enable_calling_sms" /> + 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() { diff --git a/src/com/android/settings/users/PhotoCapabilityUtils.java b/src/com/android/settings/users/PhotoCapabilityUtils.java new file mode 100644 index 00000000000..1e0985737b0 --- /dev/null +++ b/src/com/android/settings/users/PhotoCapabilityUtils.java @@ -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; + } + +} diff --git a/src/com/android/settings/users/RestrictedProfileSettings.java b/src/com/android/settings/users/RestrictedProfileSettings.java deleted file mode 100644 index 44657cf9590..00000000000 --- a/src/com/android/settings/users/RestrictedProfileSettings.java +++ /dev/null @@ -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()); - } -} diff --git a/src/com/android/settings/users/UserDetailsSettings.java b/src/com/android/settings/users/UserDetailsSettings.java index 2696ddc1319..c7cf90d725c 100644 --- a/src/com/android/settings/users/UserDetailsSettings.java +++ b/src/com/android/settings/users/UserDetailsSettings.java @@ -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(); + } } diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java index 7d4ab5d5312..719ed4aa921 100644 --- a/src/com/android/settings/users/UserSettings.java +++ b/src/com/android/settings/users/UserSettings.java @@ -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. + pref.setEnabled(!mUserCaps.mDisallowSwitchUser && canSwitchUserNow()); } - // 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); - } + 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); + } } diff --git a/tests/robotests/src/com/android/settings/users/EditUserInfoControllerTest.java b/tests/robotests/src/com/android/settings/users/EditUserInfoControllerTest.java index 1c191fa5eed..db9872fc49f 100644 --- a/tests/robotests/src/com/android/settings/users/EditUserInfoControllerTest.java +++ b/tests/robotests/src/com/android/settings/users/EditUserInfoControllerTest.java @@ -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(); + } } diff --git a/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java b/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java index 6c5478262aa..56e495786fc 100644 --- a/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java +++ b/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java @@ -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); + } }