From 106431e525e6f99bb8711ee8a846d95f35eedaaa Mon Sep 17 00:00:00 2001 From: Andras Kloczl Date: Mon, 18 May 2020 14:53:07 +0100 Subject: [PATCH 01/10] 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); + } } From d0d07ddcebc250266462dc9b6d02a01c9d2cb097 Mon Sep 17 00:00:00 2001 From: Fabian Kozynski Date: Thu, 28 May 2020 13:49:40 -0400 Subject: [PATCH 02/10] Use PackageManager.FEATURE_CONTROLS to decide availability Using this features, some of the Preference in Power Menu Settings can change their availability and their summary. Test: Robotest Settings Fixes: 157244528 Change-Id: I704438dda181aa6347c3f168ac5ef6bd16148993 --- res/values/strings.xml | 3 + .../DeviceControlsPreferenceController.java | 5 +- .../PowerMenuPreferenceController.java | 28 ++-- .../PowerMenuPrivacyPreferenceController.java | 18 ++- ...eviceControlsPreferenceControllerTest.java | 17 ++- ...erMenuPreferenceControllerSummaryTest.java | 113 +++++++++++++++ .../PowerMenuPreferenceControllerTest.java | 93 ++++--------- ...yPreferenceControllerAvailabilityTest.java | 129 ++++++++++++++++++ ...erMenuPrivacyPreferenceControllerTest.java | 79 ++++------- 9 files changed, 348 insertions(+), 137 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/gestures/PowerMenuPreferenceControllerSummaryTest.java create mode 100644 tests/robotests/src/com/android/settings/gestures/PowerMenuPrivacyPreferenceControllerAvailabilityTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 812ff86e20e..f027e91a65c 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -12056,6 +12056,9 @@ Show controls when locked + + Show cards when locked + Hide cards and controls when locked diff --git a/src/com/android/settings/gestures/DeviceControlsPreferenceController.java b/src/com/android/settings/gestures/DeviceControlsPreferenceController.java index e588e86354d..0e0c04bddbd 100644 --- a/src/com/android/settings/gestures/DeviceControlsPreferenceController.java +++ b/src/com/android/settings/gestures/DeviceControlsPreferenceController.java @@ -17,6 +17,7 @@ package com.android.settings.gestures; import android.content.Context; +import android.content.pm.PackageManager; import android.provider.Settings; import android.text.TextUtils; @@ -37,7 +38,9 @@ public class DeviceControlsPreferenceController extends GesturePreferenceControl @Override public int getAvailabilityStatus() { - return AVAILABLE; + boolean available = mContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_CONTROLS); + return available ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; } @Override diff --git a/src/com/android/settings/gestures/PowerMenuPreferenceController.java b/src/com/android/settings/gestures/PowerMenuPreferenceController.java index e65d140dbdb..6ef583b7037 100644 --- a/src/com/android/settings/gestures/PowerMenuPreferenceController.java +++ b/src/com/android/settings/gestures/PowerMenuPreferenceController.java @@ -17,6 +17,7 @@ package com.android.settings.gestures; import android.content.Context; +import android.content.pm.PackageManager; import android.provider.Settings; import com.android.settings.R; @@ -37,15 +38,15 @@ public class PowerMenuPreferenceController extends BasePreferenceController { @Override public CharSequence getSummary() { - boolean controlsEnabled = Settings.Secure.getInt(mContext.getContentResolver(), - CONTROLS_ENABLED_SETTING, 1) == 1; - boolean cardsEnabled = Settings.Secure.getInt(mContext.getContentResolver(), - CARDS_ENABLED_SETTING, 0) == 1; - boolean cardsVisible = cardsEnabled && Settings.Secure.getInt(mContext.getContentResolver(), - CARDS_AVAILABLE_SETTING, 0) == 1; - if (controlsEnabled && cardsVisible) { + boolean controlsVisible = isControlsAvailable() + && Settings.Secure.getInt(mContext.getContentResolver(), + CONTROLS_ENABLED_SETTING, 1) == 1; + boolean cardsVisible = isCardsAvailable() + && Settings.Secure.getInt(mContext.getContentResolver(), + CARDS_ENABLED_SETTING, 0) == 1; + if (controlsVisible && cardsVisible) { return mContext.getText(R.string.power_menu_cards_passes_device_controls); - } else if (controlsEnabled) { + } else if (controlsVisible) { return mContext.getText(R.string.power_menu_device_controls); } else if (cardsVisible) { return mContext.getText(R.string.power_menu_cards_passes); @@ -56,6 +57,15 @@ public class PowerMenuPreferenceController extends BasePreferenceController { @Override public int getAvailabilityStatus() { - return AVAILABLE; + return isCardsAvailable() || isControlsAvailable() ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; + } + + private boolean isControlsAvailable() { + return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CONTROLS); + } + + private boolean isCardsAvailable() { + return Settings.Secure.getInt(mContext.getContentResolver(), + CARDS_AVAILABLE_SETTING, 0) == 1; } } diff --git a/src/com/android/settings/gestures/PowerMenuPrivacyPreferenceController.java b/src/com/android/settings/gestures/PowerMenuPrivacyPreferenceController.java index ec88ddc01cf..0cd71ee8657 100644 --- a/src/com/android/settings/gestures/PowerMenuPrivacyPreferenceController.java +++ b/src/com/android/settings/gestures/PowerMenuPrivacyPreferenceController.java @@ -18,6 +18,7 @@ package com.android.settings.gestures; import android.content.ContentResolver; import android.content.Context; +import android.content.pm.PackageManager; import android.os.UserHandle; import android.provider.Settings; @@ -57,13 +58,20 @@ public class PowerMenuPrivacyPreferenceController extends TogglePreferenceContro public CharSequence getSummary() { boolean cardsAvailable = Settings.Secure.getInt(mContext.getContentResolver(), CARDS_AVAILABLE_KEY, 0) != 0; + boolean controlsAvailable = isControlsAvailable(); final int res; if (!isSecure()) { res = R.string.power_menu_privacy_not_secure; - } else if (cardsAvailable) { + } else if (cardsAvailable && controlsAvailable) { res = R.string.power_menu_privacy_show; - } else { + } else if (!cardsAvailable && controlsAvailable) { res = R.string.power_menu_privacy_show_controls; + } else if (cardsAvailable) { + res = R.string.power_menu_privacy_show_cards; + } else { + // In this case, neither cards nor controls are available. This preference should not + // be accessible as the power menu setting is not accessible + return ""; } return mContext.getText(res); } @@ -87,7 +95,7 @@ public class PowerMenuPrivacyPreferenceController extends TogglePreferenceContro boolean cardsAvailable = Settings.Secure.getInt(resolver, CARDS_AVAILABLE_KEY, 0) != 0; boolean cardsEnabled = Settings.Secure.getInt(resolver, CARDS_ENABLED_KEY, 0) != 0; boolean controlsEnabled = Settings.Secure.getInt(resolver, CONTROLS_ENABLED_KEY, 1) != 0; - return (cardsAvailable && cardsEnabled) || controlsEnabled; + return (cardsAvailable && cardsEnabled) || (isControlsAvailable() && controlsEnabled); } private boolean isSecure() { @@ -97,4 +105,8 @@ public class PowerMenuPrivacyPreferenceController extends TogglePreferenceContro int userId = UserHandle.myUserId(); return utils.isSecure(userId); } + + private boolean isControlsAvailable() { + return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CONTROLS); + } } diff --git a/tests/robotests/src/com/android/settings/gestures/DeviceControlsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/gestures/DeviceControlsPreferenceControllerTest.java index c31971a991b..432a68d0a8b 100644 --- a/tests/robotests/src/com/android/settings/gestures/DeviceControlsPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/gestures/DeviceControlsPreferenceControllerTest.java @@ -19,6 +19,7 @@ package com.android.settings.gestures; import static com.google.common.truth.Truth.assertThat; import android.content.Context; +import android.content.pm.PackageManager; import android.provider.Settings; import com.android.settings.core.BasePreferenceController; @@ -28,12 +29,15 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.shadows.ShadowPackageManager; @RunWith(RobolectricTestRunner.class) public class DeviceControlsPreferenceControllerTest { private Context mContext; private DeviceControlsPreferenceController mController; + private ShadowPackageManager mShadowPackageManager; private static final String KEY_GESTURE_PANEL = "gesture_device_controls"; private static final String ENABLED_SETTING = @@ -42,6 +46,7 @@ public class DeviceControlsPreferenceControllerTest { @Before public void setUp() { mContext = RuntimeEnvironment.application; + mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager()); mController = new DeviceControlsPreferenceController(mContext, KEY_GESTURE_PANEL); } @@ -60,11 +65,21 @@ public class DeviceControlsPreferenceControllerTest { } @Test - public void getAvailabilityStatus_panelAvailable() { + public void getAvailabilityStatus_hasSystemFeature_panelAvailable() { + mShadowPackageManager.setSystemFeature(PackageManager.FEATURE_CONTROLS, true); + assertThat(mController.getAvailabilityStatus()) .isEqualTo(BasePreferenceController.AVAILABLE); } + @Test + public void getAvailabilityStatus_hasntSystemFeature_panelUnsupported() { + mShadowPackageManager.setSystemFeature(PackageManager.FEATURE_CONTROLS, false); + + assertThat(mController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + } + @Test public void isSliceable_correctKey() { final DeviceControlsPreferenceController controller = diff --git a/tests/robotests/src/com/android/settings/gestures/PowerMenuPreferenceControllerSummaryTest.java b/tests/robotests/src/com/android/settings/gestures/PowerMenuPreferenceControllerSummaryTest.java new file mode 100644 index 00000000000..7fa248c7c93 --- /dev/null +++ b/tests/robotests/src/com/android/settings/gestures/PowerMenuPreferenceControllerSummaryTest.java @@ -0,0 +1,113 @@ +/* + * 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.gestures; + +import static com.google.common.truth.Truth.assertThat; + +import android.annotation.StringRes; +import android.content.ContentResolver; +import android.content.Context; +import android.content.pm.PackageManager; +import android.provider.Settings; + +import com.android.settings.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.ParameterizedRobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.shadows.ShadowPackageManager; + +import java.util.Arrays; +import java.util.Collection; + +@RunWith(ParameterizedRobolectricTestRunner.class) +public class PowerMenuPreferenceControllerSummaryTest { + + private static final String KEY_GESTURE_POWER_MENU = "gesture_power_menu"; + private static final String CONTROLS_ENABLED = Settings.Secure.CONTROLS_ENABLED; + private static final String CONTROLS_FEATURE = PackageManager.FEATURE_CONTROLS; + private static final String CARDS_ENABLED = Settings.Secure.GLOBAL_ACTIONS_PANEL_ENABLED; + private static final String CARDS_AVAILABLE = Settings.Secure.GLOBAL_ACTIONS_PANEL_AVAILABLE; + + @ParameterizedRobolectricTestRunner.Parameters( + name = "ctrls available={0}, ctrls enabled={1}, cards available={2}, cards enabled={3}") + public static Collection data() { + return Arrays.asList(new Object[][]{ + // controls available, controls enabled, cards available, cards enabled, summary + {false, false, false, false, R.string.power_menu_none}, + {false, false, false, true, R.string.power_menu_none}, + {false, false, true, false, R.string.power_menu_none}, + {false, false, true, true, R.string.power_menu_cards_passes}, + {false, true, false, false, R.string.power_menu_none}, + {false, true, false, true, R.string.power_menu_none}, + {false, true, true, false, R.string.power_menu_none}, + {false, true, true, true, R.string.power_menu_cards_passes}, + {true, false, false, false, R.string.power_menu_none}, + {true, false, false, true, R.string.power_menu_none}, + {true, false, true, false, R.string.power_menu_none}, + {true, false, true, true, R.string.power_menu_cards_passes}, + {true, true, false, false, R.string.power_menu_device_controls}, + {true, true, false, true, R.string.power_menu_device_controls}, + {true, true, true, false, R.string.power_menu_device_controls}, + {true, true, true, true, R.string.power_menu_cards_passes_device_controls} + }); + } + + private Context mContext; + private PowerMenuPreferenceController mController; + private ShadowPackageManager mShadowPackageManager; + + private boolean mControlsAvailable; + private boolean mControlsEnabled; + private boolean mCardsAvailable; + private boolean mCardsEnabled; + private @StringRes int mSummaryRes; + + public PowerMenuPreferenceControllerSummaryTest( + boolean controlsAvailable, + boolean controlsEnabled, + boolean cardsAvailable, + boolean cardsEnabled, + @StringRes int summaryRes) { + mControlsAvailable = controlsAvailable; + mControlsEnabled = controlsEnabled; + mCardsAvailable = cardsAvailable; + mCardsEnabled = cardsEnabled; + mSummaryRes = summaryRes; + } + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager()); + mController = new PowerMenuPreferenceController(mContext, KEY_GESTURE_POWER_MENU); + } + + @Test + public void getSummary_possiblyAvailableAndEnabled() { + mShadowPackageManager.setSystemFeature(CONTROLS_FEATURE, mControlsAvailable); + ContentResolver cr = mContext.getContentResolver(); + Settings.Secure.putInt(cr, CONTROLS_ENABLED, mControlsEnabled ? 1 : 0); + Settings.Secure.putInt(cr, CARDS_AVAILABLE, mCardsAvailable ? 1 : 0); + Settings.Secure.putInt(cr, CARDS_ENABLED, mCardsEnabled ? 1 : 0); + + assertThat(mController.getSummary()).isEqualTo(mContext.getText(mSummaryRes)); + } +} diff --git a/tests/robotests/src/com/android/settings/gestures/PowerMenuPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/gestures/PowerMenuPreferenceControllerTest.java index e14293eb82a..570a68040f7 100644 --- a/tests/robotests/src/com/android/settings/gestures/PowerMenuPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/gestures/PowerMenuPreferenceControllerTest.java @@ -19,118 +19,71 @@ package com.android.settings.gestures; import static com.google.common.truth.Truth.assertThat; import android.content.Context; +import android.content.pm.PackageManager; import android.provider.Settings; -import com.android.settings.R; import com.android.settings.core.BasePreferenceController; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.shadows.ShadowPackageManager; @RunWith(RobolectricTestRunner.class) public class PowerMenuPreferenceControllerTest { private Context mContext; private PowerMenuPreferenceController mController; + private ShadowPackageManager mShadowPackageManager; private static final String KEY_GESTURE_POWER_MENU = "gesture_power_menu"; private static final String CONTROLS_ENABLED = Settings.Secure.CONTROLS_ENABLED; + private static final String CONTROLS_FEATURE = PackageManager.FEATURE_CONTROLS; private static final String CARDS_ENABLED = Settings.Secure.GLOBAL_ACTIONS_PANEL_ENABLED; private static final String CARDS_AVAILABLE = Settings.Secure.GLOBAL_ACTIONS_PANEL_AVAILABLE; @Before public void setUp() { - MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; + mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager()); mController = new PowerMenuPreferenceController(mContext, KEY_GESTURE_POWER_MENU); } @Test - public void getAvailabilityStatus_available() { + public void getAvailabilityStatus_bothAvailable_available() { + Settings.Secure.putInt(mContext.getContentResolver(), CARDS_AVAILABLE, 1); + mShadowPackageManager.setSystemFeature(CONTROLS_FEATURE, true); + assertThat(mController.getAvailabilityStatus()).isEqualTo( BasePreferenceController.AVAILABLE); } @Test - public void getSummary_allDisabled() { - Settings.Secure.putInt(mContext.getContentResolver(), CONTROLS_ENABLED, 0); - Settings.Secure.putInt(mContext.getContentResolver(), CARDS_ENABLED, 0); - Settings.Secure.putInt(mContext.getContentResolver(), CARDS_AVAILABLE, 0); - - assertThat(mController.getSummary()) - .isEqualTo(mContext.getText(R.string.power_menu_none)); - } - - @Test - public void getSummary_onlyControlsEnabled() { - Settings.Secure.putInt(mContext.getContentResolver(), CONTROLS_ENABLED, 1); - Settings.Secure.putInt(mContext.getContentResolver(), CARDS_ENABLED, 0); - Settings.Secure.putInt(mContext.getContentResolver(), CARDS_AVAILABLE, 0); - - assertThat(mController.getSummary()) - .isEqualTo(mContext.getText(R.string.power_menu_device_controls)); - } - - @Test - public void getSummary_onlyCardsEnabled_notAvailable() { - Settings.Secure.putInt(mContext.getContentResolver(), CONTROLS_ENABLED, 0); - Settings.Secure.putInt(mContext.getContentResolver(), CARDS_ENABLED, 1); - Settings.Secure.putInt(mContext.getContentResolver(), CARDS_AVAILABLE, 0); - - assertThat(mController.getSummary()) - .isEqualTo(mContext.getText(R.string.power_menu_none)); - } - - @Test - public void getSummary_cardsAvailable_notEnabled() { - Settings.Secure.putInt(mContext.getContentResolver(), CONTROLS_ENABLED, 0); - Settings.Secure.putInt(mContext.getContentResolver(), CARDS_ENABLED, 0); + public void getAvailabilityStatus_onlyCardsAvailable_available() { Settings.Secure.putInt(mContext.getContentResolver(), CARDS_AVAILABLE, 1); + mShadowPackageManager.setSystemFeature(CONTROLS_FEATURE, false); - assertThat(mController.getSummary()) - .isEqualTo(mContext.getText(R.string.power_menu_none)); + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.AVAILABLE); } @Test - public void getSummary_allEnabled_cardsNotAvailable() { - Settings.Secure.putInt(mContext.getContentResolver(), CONTROLS_ENABLED, 1); - Settings.Secure.putInt(mContext.getContentResolver(), CARDS_ENABLED, 1); + public void getAvailabilityStatus_onlyControlsAvailable_available() { Settings.Secure.putInt(mContext.getContentResolver(), CARDS_AVAILABLE, 0); + mShadowPackageManager.setSystemFeature(CONTROLS_FEATURE, true); - assertThat(mController.getSummary()) - .isEqualTo(mContext.getText(R.string.power_menu_device_controls)); + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.AVAILABLE); } @Test - public void getSummary_controlsEnabled_cardsDisabledAvailable() { - Settings.Secure.putInt(mContext.getContentResolver(), CONTROLS_ENABLED, 1); - Settings.Secure.putInt(mContext.getContentResolver(), CARDS_ENABLED, 0); - Settings.Secure.putInt(mContext.getContentResolver(), CARDS_AVAILABLE, 1); + public void getAvailabilityStatus_bothUnavailable_unavailable() { + Settings.Secure.putInt(mContext.getContentResolver(), CARDS_AVAILABLE, 0); + mShadowPackageManager.setSystemFeature(CONTROLS_FEATURE, false); - assertThat(mController.getSummary()) - .isEqualTo(mContext.getText(R.string.power_menu_device_controls)); - } - - @Test - public void getSummary_controlsDisabled() { - Settings.Secure.putInt(mContext.getContentResolver(), CONTROLS_ENABLED, 0); - Settings.Secure.putInt(mContext.getContentResolver(), CARDS_ENABLED, 1); - Settings.Secure.putInt(mContext.getContentResolver(), CARDS_AVAILABLE, 1); - - assertThat(mController.getSummary()) - .isEqualTo(mContext.getText(R.string.power_menu_cards_passes)); - } - - @Test - public void getSummary_allEnabled() { - Settings.Secure.putInt(mContext.getContentResolver(), CONTROLS_ENABLED, 1); - Settings.Secure.putInt(mContext.getContentResolver(), CARDS_ENABLED, 1); - Settings.Secure.putInt(mContext.getContentResolver(), CARDS_AVAILABLE, 1); - - assertThat(mController.getSummary()) - .isEqualTo(mContext.getText(R.string.power_menu_cards_passes_device_controls)); + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.CONDITIONALLY_UNAVAILABLE); } } diff --git a/tests/robotests/src/com/android/settings/gestures/PowerMenuPrivacyPreferenceControllerAvailabilityTest.java b/tests/robotests/src/com/android/settings/gestures/PowerMenuPrivacyPreferenceControllerAvailabilityTest.java new file mode 100644 index 00000000000..40b94ebe33d --- /dev/null +++ b/tests/robotests/src/com/android/settings/gestures/PowerMenuPrivacyPreferenceControllerAvailabilityTest.java @@ -0,0 +1,129 @@ +/* + * 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.gestures; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.pm.PackageManager; +import android.provider.Settings; + +import com.android.internal.widget.LockPatternUtils; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.testutils.FakeFeatureFactory; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.ParameterizedRobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.shadows.ShadowPackageManager; + +import java.util.Arrays; +import java.util.Collection; + +@RunWith(ParameterizedRobolectricTestRunner.class) +public class PowerMenuPrivacyPreferenceControllerAvailabilityTest { + + private static final String CONTROLS_ENABLED = Settings.Secure.CONTROLS_ENABLED; + private static final String CONTROLS_FEATURE = PackageManager.FEATURE_CONTROLS; + private static final String CARDS_ENABLED = Settings.Secure.GLOBAL_ACTIONS_PANEL_ENABLED; + private static final String CARDS_AVAILABLE = Settings.Secure.GLOBAL_ACTIONS_PANEL_AVAILABLE; + + @ParameterizedRobolectricTestRunner.Parameters( + name = "ctrls available={0}, ctrls enabled={1}, cards available={2}, cards enabled={3}") + public static Collection data() { + return Arrays.asList(new Object[][]{ + // controls available, controls enabled, cards available, cards enabled, available + {false, false, false, false, BasePreferenceController.DISABLED_DEPENDENT_SETTING}, + {false, false, false, true, BasePreferenceController.DISABLED_DEPENDENT_SETTING}, + {false, false, true, false, BasePreferenceController.DISABLED_DEPENDENT_SETTING}, + {false, false, true, true, BasePreferenceController.AVAILABLE}, + {false, true, false, false, BasePreferenceController.DISABLED_DEPENDENT_SETTING}, + {false, true, false, true, BasePreferenceController.DISABLED_DEPENDENT_SETTING}, + {false, true, true, false, BasePreferenceController.DISABLED_DEPENDENT_SETTING}, + {false, true, true, true, BasePreferenceController.AVAILABLE}, + {true, false, false, false, BasePreferenceController.DISABLED_DEPENDENT_SETTING}, + {true, false, false, true, BasePreferenceController.DISABLED_DEPENDENT_SETTING}, + {true, false, true, false, BasePreferenceController.DISABLED_DEPENDENT_SETTING}, + {true, false, true, true, BasePreferenceController.AVAILABLE}, + {true, true, false, false, BasePreferenceController.AVAILABLE}, + {true, true, false, true, BasePreferenceController.AVAILABLE}, + {true, true, true, false, BasePreferenceController.AVAILABLE}, + {true, true, true, true, BasePreferenceController.AVAILABLE} + }); + } + + private Context mContext; + private PowerMenuPrivacyPreferenceController mController; + private ShadowPackageManager mShadowPackageManager; + + @Mock + private LockPatternUtils mLockPatternUtils; + + private boolean mControlsAvailable; + private boolean mControlsEnabled; + private boolean mCardsAvailable; + private boolean mCardsEnabled; + private int mAvailable; + + public PowerMenuPrivacyPreferenceControllerAvailabilityTest( + boolean controlsAvailable, + boolean controlsEnabled, + boolean cardsAvailable, + boolean cardsEnabled, + int available) { + mControlsAvailable = controlsAvailable; + mControlsEnabled = controlsEnabled; + mCardsAvailable = cardsAvailable; + mCardsEnabled = cardsEnabled; + mAvailable = available; + } + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager()); + FakeFeatureFactory featureFactory = FakeFeatureFactory.setupForTest(); + + // For these tests we assume the device has a secure lock. + when(featureFactory.securityFeatureProvider.getLockPatternUtils(mContext)) + .thenReturn(mLockPatternUtils); + when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true); + + mController = new PowerMenuPrivacyPreferenceController(mContext, "TEST_KEY"); + } + + @Test + public void getAvailabilityStatus_possiblyAvailableAndEnabled() { + mShadowPackageManager.setSystemFeature(CONTROLS_FEATURE, mControlsAvailable); + ContentResolver cr = mContext.getContentResolver(); + Settings.Secure.putInt(cr, CONTROLS_ENABLED, mControlsEnabled ? 1 : 0); + Settings.Secure.putInt(cr, CARDS_AVAILABLE, mCardsAvailable ? 1 : 0); + Settings.Secure.putInt(cr, CARDS_ENABLED, mCardsEnabled ? 1 : 0); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(mAvailable); + } +} diff --git a/tests/robotests/src/com/android/settings/gestures/PowerMenuPrivacyPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/gestures/PowerMenuPrivacyPreferenceControllerTest.java index 32048a157d8..994a3eece31 100644 --- a/tests/robotests/src/com/android/settings/gestures/PowerMenuPrivacyPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/gestures/PowerMenuPrivacyPreferenceControllerTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.when; import android.content.ContentResolver; import android.content.Context; +import android.content.pm.PackageManager; import android.provider.Settings; import androidx.preference.Preference; @@ -42,6 +43,8 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.shadows.ShadowPackageManager; @RunWith(RobolectricTestRunner.class) public class PowerMenuPrivacyPreferenceControllerTest { @@ -55,6 +58,7 @@ public class PowerMenuPrivacyPreferenceControllerTest { private Context mContext; private ContentResolver mContentResolver; + private ShadowPackageManager mShadowPackageManager; private PowerMenuPrivacyPreferenceController mController; @Mock @@ -66,6 +70,8 @@ public class PowerMenuPrivacyPreferenceControllerTest { public void setUp() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; + mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager()); + mContentResolver = mContext.getContentResolver(); FakeFeatureFactory featureFactory = FakeFeatureFactory.setupForTest(); when(featureFactory.securityFeatureProvider.getLockPatternUtils(mContext)) @@ -119,82 +125,50 @@ public class PowerMenuPrivacyPreferenceControllerTest { } @Test - public void getSummary_cardsAvailable_isPower_menu_privacy_showString() { + public void getSummary_cardsControlsAvailable_isPower_menu_privacy_showString() { Settings.Secure.putInt(mContentResolver, CARDS_AVAILABLE_KEY, 1); + mShadowPackageManager.setSystemFeature(PackageManager.FEATURE_CONTROLS, true); assertThat(mController.getSummary()).isEqualTo( mContext.getText(R.string.power_menu_privacy_show)); } @Test - public void getSummary_cardsUnavailable_isPower_menu_privacy_show_controlsString() { + public void + getSummary_cardsUnavailableControlsAvailable_isPower_menu_privacy_show_controlsString() { Settings.Secure.putInt(mContentResolver, CARDS_AVAILABLE_KEY, 0); + mShadowPackageManager.setSystemFeature(PackageManager.FEATURE_CONTROLS, true); assertThat(mController.getSummary()).isEqualTo( mContext.getText(R.string.power_menu_privacy_show_controls)); } + @Test + public void + getSummary_cardsAvailableControlsUnavailable_isPower_menu_privacy_show_cardsString() { + Settings.Secure.putInt(mContentResolver, CARDS_AVAILABLE_KEY, 1); + mShadowPackageManager.setSystemFeature(PackageManager.FEATURE_CONTROLS, false); + + assertThat(mController.getSummary()).isEqualTo( + mContext.getText(R.string.power_menu_privacy_show_cards)); + } + @Test public void updateState_onPreferenceRefreshed_preferenceEnabledAndSummaryChanged() { + mShadowPackageManager.setSystemFeature(PackageManager.FEATURE_CONTROLS, true); + Settings.Secure.putInt(mContentResolver, CARDS_AVAILABLE_KEY, 1); + mController.updateState(mPreference); verify(mPreference).setEnabled(anyBoolean()); verify(mPreference, atLeastOnce()).setSummary(mController.getSummary()); } - @Test - public void getAvailabilityStatus_allOff_returnsDisabled() { - Settings.Secure.putInt(mContentResolver, CARDS_AVAILABLE_KEY, 1); - Settings.Secure.putInt(mContentResolver, CARDS_ENABLED_KEY, 0); - Settings.Secure.putInt(mContentResolver, CONTROLS_ENABLED_KEY, 0); - - assertThat(mController.getAvailabilityStatus()).isEqualTo( - BasePreferenceController.DISABLED_DEPENDENT_SETTING); - } - - @Test - public void getAvailabilityStatus_cardsUnavailableControlsOff_returnsDisabled() { - Settings.Secure.putInt(mContentResolver, CARDS_AVAILABLE_KEY, 0); - Settings.Secure.putInt(mContentResolver, CONTROLS_ENABLED_KEY, 0); - - assertThat(mController.getAvailabilityStatus()).isEqualTo( - BasePreferenceController.DISABLED_DEPENDENT_SETTING); - } - - @Test - public void testAvailabilityStatus_cardsOnControlsOff_returnsAvailable() { - Settings.Secure.putInt(mContentResolver, CARDS_AVAILABLE_KEY, 1); - Settings.Secure.putInt(mContentResolver, CARDS_ENABLED_KEY, 1); - Settings.Secure.putInt(mContentResolver, CONTROLS_ENABLED_KEY, 0); - - assertThat(mController.getAvailabilityStatus()).isEqualTo( - BasePreferenceController.AVAILABLE); - } - - @Test - public void getAvailabilityStatus_cardsOffControlsOn_returnsAvailable() { - Settings.Secure.putInt(mContentResolver, CARDS_AVAILABLE_KEY, 1); - Settings.Secure.putInt(mContentResolver, CARDS_ENABLED_KEY, 0); - Settings.Secure.putInt(mContentResolver, CONTROLS_ENABLED_KEY, 1); - - assertThat(mController.getAvailabilityStatus()).isEqualTo( - BasePreferenceController.AVAILABLE); - } - - @Test - public void getAvailabilityStatus_allOn_returnsAvailable() { - Settings.Secure.putInt(mContentResolver, CARDS_AVAILABLE_KEY, 1); - Settings.Secure.putInt(mContentResolver, CARDS_ENABLED_KEY, 1); - Settings.Secure.putInt(mContentResolver, CONTROLS_ENABLED_KEY, 1); - - assertThat(mController.getAvailabilityStatus()).isEqualTo( - BasePreferenceController.AVAILABLE); - } - @Test public void getAvailabilityStatus_allOnNotSecure_returnsDisabled() { when(mLockPatternUtils.isSecure(anyInt())).thenReturn(false); + mShadowPackageManager.setSystemFeature(PackageManager.FEATURE_CONTROLS, true); Settings.Secure.putInt(mContentResolver, CARDS_AVAILABLE_KEY, 1); Settings.Secure.putInt(mContentResolver, CARDS_ENABLED_KEY, 1); Settings.Secure.putInt(mContentResolver, CONTROLS_ENABLED_KEY, 1); @@ -205,9 +179,8 @@ public class PowerMenuPrivacyPreferenceControllerTest { @Test public void getAvailabilityStatus_controlsDeletedSecure_retursAvailable() { - when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true); - Settings.Secure.putString(mContentResolver, CONTROLS_ENABLED_KEY, null); + mShadowPackageManager.setSystemFeature(PackageManager.FEATURE_CONTROLS, true); assertThat(mController.getAvailabilityStatus()).isEqualTo( BasePreferenceController.AVAILABLE); From 985129596144404341f9204ef282117717f5d17b Mon Sep 17 00:00:00 2001 From: Robert Luo Date: Sun, 31 May 2020 12:30:14 +0800 Subject: [PATCH 03/10] Fix RTL alignment issue in the "How it works" page of Contactless payment settings Bug: 156717675 Screenshot: https://screenshot.googleplex.com/RpqdmjhmTaP.png Test: make -j42 RunSettingsRoboTests Change-Id: I12064adb2c510d281358f22e6ee1de9682079fcd --- res/layout/nfc_payment_how_it_works.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/layout/nfc_payment_how_it_works.xml b/res/layout/nfc_payment_how_it_works.xml index 87a009550f1..230a28b8669 100644 --- a/res/layout/nfc_payment_how_it_works.xml +++ b/res/layout/nfc_payment_how_it_works.xml @@ -69,10 +69,10 @@