From 3dec052a06fe13dbe0aa308d092da002eb28e3c3 Mon Sep 17 00:00:00 2001 From: Bonian Chen Date: Tue, 5 May 2020 14:28:08 +0800 Subject: [PATCH 01/14] [Settings] Mobile network not updated based on SIM change SIM status and configuration change need to update the screen. Bug: 155520761 Test: manual Change-Id: I18f0f599b2299b1a93115a00025f0c0dd7ecb450 --- .../network/ActiveSubsciptionsListener.java | 19 +++++ .../AbstractMobileNetworkSettings.java | 82 +++++++++++++++---- .../telephony/MobileNetworkActivity.java | 3 +- .../telephony/MobileNetworkSettings.java | 38 +++++++++ 4 files changed, 123 insertions(+), 19 deletions(-) diff --git a/src/com/android/settings/network/ActiveSubsciptionsListener.java b/src/com/android/settings/network/ActiveSubsciptionsListener.java index 3d150258418..26f644145b2 100644 --- a/src/com/android/settings/network/ActiveSubsciptionsListener.java +++ b/src/com/android/settings/network/ActiveSubsciptionsListener.java @@ -62,6 +62,7 @@ public abstract class ActiveSubsciptionsListener private BroadcastReceiver mSubscriptionChangeReceiver; private static final int MAX_SUBSCRIPTION_UNKNOWN = -1; + private final int mTargetSubscriptionId; private AtomicInteger mMaxActiveSubscriptionInfos; private List mCachedActiveSubscriptionInfo; @@ -73,9 +74,21 @@ public abstract class ActiveSubsciptionsListener * @param context {@code Context} of this listener */ public ActiveSubsciptionsListener(Looper looper, Context context) { + this(looper, context, SubscriptionManager.INVALID_SUBSCRIPTION_ID); + } + + /** + * Constructor + * + * @param looper {@code Looper} of this listener + * @param context {@code Context} of this listener + * @param subscriptionId for subscription on this listener + */ + public ActiveSubsciptionsListener(Looper looper, Context context, int subscriptionId) { super(looper); mLooper = looper; mContext = context; + mTargetSubscriptionId = subscriptionId; mCacheState = new AtomicInteger(STATE_NOT_LISTENING); mMaxActiveSubscriptionInfos = new AtomicInteger(MAX_SUBSCRIPTION_UNKNOWN); @@ -108,6 +121,12 @@ public abstract class ActiveSubsciptionsListener if (!clearCachedSubId(subId)) { return; } + if (SubscriptionManager.isValidSubscriptionId(mTargetSubscriptionId)) { + if (SubscriptionManager.isValidSubscriptionId(subId) + && (mTargetSubscriptionId != subId)) { + return; + } + } } onSubscriptionsChanged(); } diff --git a/src/com/android/settings/network/telephony/AbstractMobileNetworkSettings.java b/src/com/android/settings/network/telephony/AbstractMobileNetworkSettings.java index e92cdfcd9d1..aa84cc14acd 100644 --- a/src/com/android/settings/network/telephony/AbstractMobileNetworkSettings.java +++ b/src/com/android/settings/network/telephony/AbstractMobileNetworkSettings.java @@ -16,7 +16,9 @@ package com.android.settings.network.telephony; +import android.os.SystemClock; import android.text.TextUtils; +import android.util.Log; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; @@ -34,6 +36,7 @@ abstract class AbstractMobileNetworkSettings extends RestrictedDashboardFragment private List mHiddenControllerList = new ArrayList(); + private boolean mIsRedrawRequired; /** * @param restrictionKey The restriction key to check before pin protecting @@ -52,6 +55,15 @@ abstract class AbstractMobileNetworkSettings extends RestrictedDashboardFragment return result; } + Preference searchForPreference(PreferenceScreen screen, + AbstractPreferenceController controller) { + final String key = controller.getPreferenceKey(); + if (TextUtils.isEmpty(key)) { + return null; + } + return screen.findPreference(key); + } + TelephonyStatusControlSession setTelephonyAvailabilityStatus( Collection listOfPrefControllers) { return (new TelephonyStatusControlSession.Builder(listOfPrefControllers)) @@ -78,26 +90,60 @@ abstract class AbstractMobileNetworkSettings extends RestrictedDashboardFragment protected void updatePreferenceStates() { mHiddenControllerList.clear(); + if (mIsRedrawRequired) { + redrawPreferenceControllers(); + return; + } + final PreferenceScreen screen = getPreferenceScreen(); - getPreferenceControllersAsList().forEach(controller -> { - final String key = controller.getPreferenceKey(); - if (TextUtils.isEmpty(key)) { - return; - } - final Preference preference = screen.findPreference(key); - if (preference == null) { - return; - } - if (!isPreferenceExpanded(preference)) { - mHiddenControllerList.add(controller); - return; - } - if (!controller.isAvailable()) { - return; - } - controller.updateState(preference); - }); + getPreferenceControllersAsList().forEach(controller -> + updateVisiblePreferenceControllers(screen, controller)); } + private void updateVisiblePreferenceControllers(PreferenceScreen screen, + AbstractPreferenceController controller) { + final Preference preference = searchForPreference(screen, controller); + if (preference == null) { + return; + } + if (!isPreferenceExpanded(preference)) { + mHiddenControllerList.add(controller); + return; + } + if (!controller.isAvailable()) { + return; + } + controller.updateState(preference); + } + + void redrawPreferenceControllers() { + mHiddenControllerList.clear(); + + if (!isResumed()) { + mIsRedrawRequired = true; + return; + } + mIsRedrawRequired = false; + + final long startTime = SystemClock.elapsedRealtime(); + + final List controllers = + getPreferenceControllersAsList(); + final TelephonyStatusControlSession session = + setTelephonyAvailabilityStatus(controllers); + + + final PreferenceScreen screen = getPreferenceScreen(); + controllers.forEach(controller -> { + controller.displayPreference(screen); + updateVisiblePreferenceControllers(screen, controller); + }); + + final long endTime = SystemClock.elapsedRealtime(); + + Log.d(LOG_TAG, "redraw fragment: +" + (endTime - startTime) + "ms"); + + session.close(); + } } diff --git a/src/com/android/settings/network/telephony/MobileNetworkActivity.java b/src/com/android/settings/network/telephony/MobileNetworkActivity.java index fd7ab97cf85..7f7dc3a192c 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkActivity.java +++ b/src/com/android/settings/network/telephony/MobileNetworkActivity.java @@ -126,12 +126,13 @@ public class MobileNetworkActivity extends SettingsBaseActivity : SUB_ID_NULL); final SubscriptionInfo subscription = getSubscription(); - updateTitleAndNavigation(subscription); maybeShowContactDiscoveryDialog(subscription); // Since onChanged() will take place immediately when addActiveSubscriptionsListener(), // perform registration after mCurSubscriptionId been configured. registerActiveSubscriptionsListener(); + + updateSubscriptions(subscription); } @VisibleForTesting diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java index d84d15409f0..49f7981c28b 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java +++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java @@ -37,6 +37,7 @@ import androidx.preference.Preference; import com.android.settings.R; import com.android.settings.datausage.BillingCyclePreferenceController; import com.android.settings.datausage.DataUsageSummaryPreferenceController; +import com.android.settings.network.ActiveSubsciptionsListener; import com.android.settings.network.telephony.cdma.CdmaSubscriptionPreferenceController; import com.android.settings.network.telephony.cdma.CdmaSystemSelectPreferenceController; import com.android.settings.network.telephony.gsm.AutoSelectPreferenceController; @@ -44,6 +45,7 @@ import com.android.settings.network.telephony.gsm.OpenNetworkSelectPagePreferenc import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.search.SearchIndexable; +import com.android.settingslib.utils.ThreadUtils; import java.util.Arrays; import java.util.List; @@ -70,6 +72,10 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings { private UserManager mUserManager; private String mClickedPrefKey; + private ActiveSubsciptionsListener mActiveSubsciptionsListener; + private boolean mActiveSubsciptionsListenerStarting; + private int mActiveSubsciptionsListenerCount; + public MobileNetworkSettings() { super(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS); } @@ -197,6 +203,38 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings { onRestoreInstance(icicle); } + @Override + public void onResume() { + super.onResume(); + if (mActiveSubsciptionsListener == null) { + mActiveSubsciptionsListenerStarting = true; + mActiveSubsciptionsListener = new ActiveSubsciptionsListener( + getContext().getMainLooper(), getContext(), mSubId) { + public void onChanged() { + onSubscriptionDetailChanged(); + } + }; + mActiveSubsciptionsListenerStarting = false; + } + mActiveSubsciptionsListener.start(); + } + + private void onSubscriptionDetailChanged() { + if (mActiveSubsciptionsListenerStarting) { + Log.d(LOG_TAG, "Callback during onResume()"); + return; + } + mActiveSubsciptionsListenerCount++; + if (mActiveSubsciptionsListenerCount != 1) { + return; + } + + ThreadUtils.postOnMainThread(() -> { + mActiveSubsciptionsListenerCount = 0; + redrawPreferenceControllers(); + }); + } + @VisibleForTesting void onRestoreInstance(Bundle icicle) { if (icicle != null) { From 172f4064a90b7692eebd19f8b5b26b4f7ee75ff5 Mon Sep 17 00:00:00 2001 From: Yanting Yang Date: Wed, 6 May 2020 20:56:51 +0800 Subject: [PATCH 02/14] Hide the app details preference for mainline module apps For mainline module apps, user can not install them from the play store. We should not provide the play store link for them in app info page. Therefore, we decide to hide their app details preference which is to provide the play store link from the app info page. Bug: 152718521 Test: visual and robotests Change-Id: I46b64be0d725740c2b80a5251a42e9fc834e4596 --- .../AppInstallerInfoPreferenceController.java | 5 +++++ .../AppInstallerInfoPreferenceControllerTest.java | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceController.java b/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceController.java index bf8567684ec..2e119537a2c 100644 --- a/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceController.java +++ b/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceController.java @@ -42,6 +42,11 @@ public class AppInstallerInfoPreferenceController extends AppInfoPreferenceContr if (UserManager.get(mContext).isManagedProfile()) { return DISABLED_FOR_USER; } + + if (AppUtils.isMainlineModule(mContext, mPackageName)) { + return DISABLED_FOR_USER; + } + return mInstallerLabel != null ? AVAILABLE : DISABLED_FOR_USER; } diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceControllerTest.java index 1f85477c02e..00bd5f80724 100644 --- a/tests/robotests/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceControllerTest.java @@ -32,6 +32,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.ModuleInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -148,4 +149,16 @@ public class AppInstallerInfoPreferenceControllerTest { verify(mPreference, never()).setEnabled(false); verify(mPreference).setIntent(any(Intent.class)); } + + @Test + public void getAvailabilityStatus_isMainlineModule_shouldReturnDisabled() + throws PackageManager.NameNotFoundException { + when(mUserManager.isManagedProfile()).thenReturn(false); + when(mAppInfo.loadLabel(mPackageManager)).thenReturn("Label"); + mController.setPackageName("Package"); + when(mPackageManager.getModuleInfo("Package", 0 /* flags */)).thenReturn(new ModuleInfo()); + + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.DISABLED_FOR_USER); + } } From 69ee49c8d1f5e375ab1a2cc445e6dd26edcef236 Mon Sep 17 00:00:00 2001 From: Fabian Kozynski Date: Wed, 6 May 2020 12:36:18 -0400 Subject: [PATCH 03/14] Fix default for CONTROLS_ENABLED The default value for this setting if it doesn't exist is 1. Test: Robotest PowerMenuPrivacyPreferenceControllerTest Fixes: 155877682 Change-Id: I9025ea9c4f67492220f9ed67e11a167a65a99ec7 --- .../gestures/PowerMenuPrivacyPreferenceController.java | 2 +- .../PowerMenuPrivacyPreferenceControllerTest.java | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/gestures/PowerMenuPrivacyPreferenceController.java b/src/com/android/settings/gestures/PowerMenuPrivacyPreferenceController.java index 29c6176a4f8..ec88ddc01cf 100644 --- a/src/com/android/settings/gestures/PowerMenuPrivacyPreferenceController.java +++ b/src/com/android/settings/gestures/PowerMenuPrivacyPreferenceController.java @@ -86,7 +86,7 @@ public class PowerMenuPrivacyPreferenceController extends TogglePreferenceContro final ContentResolver resolver = mContext.getContentResolver(); 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, 0) != 0; + boolean controlsEnabled = Settings.Secure.getInt(resolver, CONTROLS_ENABLED_KEY, 1) != 0; return (cardsAvailable && cardsEnabled) || controlsEnabled; } diff --git a/tests/robotests/src/com/android/settings/gestures/PowerMenuPrivacyPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/gestures/PowerMenuPrivacyPreferenceControllerTest.java index 7891d2f9427..32048a157d8 100644 --- a/tests/robotests/src/com/android/settings/gestures/PowerMenuPrivacyPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/gestures/PowerMenuPrivacyPreferenceControllerTest.java @@ -202,4 +202,14 @@ public class PowerMenuPrivacyPreferenceControllerTest { assertThat(mController.getAvailabilityStatus()).isEqualTo( BasePreferenceController.DISABLED_DEPENDENT_SETTING); } + + @Test + public void getAvailabilityStatus_controlsDeletedSecure_retursAvailable() { + when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true); + + Settings.Secure.putString(mContentResolver, CONTROLS_ENABLED_KEY, null); + + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.AVAILABLE); + } } From 5e7f190a65a44454bf74e0a6fd1936cdfa37c563 Mon Sep 17 00:00:00 2001 From: Amit Mahajan Date: Fri, 1 May 2020 15:29:46 -0700 Subject: [PATCH 04/14] Remove hard-coding cellbroadcastreceiver package name. Test: manual Bug: 154436403 Change-Id: Id634a88060c63d57316cb8cdb923966a8409db78 Merged-in: Id634a88060c63d57316cb8cdb923966a8409db78 --- res/values/strings.xml | 3 +++ res/xml/app_and_notification.xml | 2 +- .../notification/EmergencyBroadcastPreferenceController.java | 5 ++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 18673e490c5..ae9b37dda9f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -7310,6 +7310,9 @@ Send & receive when mobile data is off + + com.android.cellbroadcastreceiver + diff --git a/res/xml/app_and_notification.xml b/res/xml/app_and_notification.xml index f75bd8629ae..b49918024a3 100644 --- a/res/xml/app_and_notification.xml +++ b/res/xml/app_and_notification.xml @@ -82,7 +82,7 @@ settings:useAdminDisabledSummary="true"> diff --git a/src/com/android/settings/notification/EmergencyBroadcastPreferenceController.java b/src/com/android/settings/notification/EmergencyBroadcastPreferenceController.java index 7add167ab8e..de7e7801ac2 100644 --- a/src/com/android/settings/notification/EmergencyBroadcastPreferenceController.java +++ b/src/com/android/settings/notification/EmergencyBroadcastPreferenceController.java @@ -24,6 +24,7 @@ import android.os.UserManager; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; +import com.android.internal.telephony.CellBroadcastUtils; import com.android.settings.accounts.AccountRestrictionHelper; import com.android.settings.core.PreferenceControllerMixin; import com.android.settingslib.RestrictedPreference; @@ -88,7 +89,9 @@ public class EmergencyBroadcastPreferenceController extends AbstractPreferenceCo com.android.internal.R.bool.config_cellBroadcastAppLinks); if (enabled) { try { - if (mPm.getApplicationEnabledSetting("com.android.cellbroadcastreceiver") + String packageName = CellBroadcastUtils + .getDefaultCellBroadcastReceiverPackageName(mContext); + if (packageName == null || mPm.getApplicationEnabledSetting(packageName) == PackageManager.COMPONENT_ENABLED_STATE_DISABLED) { enabled = false; // CMAS app disabled } From 1e0d40f2bd087ced87c62f7aed0871be44822dc0 Mon Sep 17 00:00:00 2001 From: govenliu Date: Wed, 6 May 2020 15:32:54 +0800 Subject: [PATCH 05/14] [Wi-Fi] Enhance Wifi Settings unit test cases. Add new unit test cases in 1. WifiP2pSettingsTest.java 2. WifiConfigController2Test.java 3. WifiP2PPreferenceControllerTest.java 4. WifiP2pPeerTest.java the coverage rate of 1. com.android.settings.wifi will raise from 44% to 45% 2. com.android.settings.wifi.P2p will raise from 69% to 85% Bug: 151696220 Test: make RunSettingsRoboTests ROBOTEST_FILTER=WifiP2pSettingsTest make RunSettingsRoboTests ROBOTEST_FILTER=WifiConfigController2Test make RunSettingsRoboTests ROBOTEST_FILTER=WifiP2pPeerTest make RunSettingsRoboTests ROBOTEST_FILTER=WifiP2PPreferenceControllerTest Change-Id: I365494e1654376b23b08f18b1a799ffc59323a4c --- .../settings/wifi/p2p/WifiP2pPeer.java | 9 +- .../wifi/p2p/WifiP2pPreferenceController.java | 3 +- .../settings/wifi/p2p/WifiP2pSettings.java | 8 +- .../wifi/WifiConfigController2Test.java | 212 +++++++++++++++++- .../p2p/WifiP2PPreferenceControllerTest.java | 14 +- .../settings/wifi/p2p/WifiP2pPeerTest.java | 120 ++++++++++ .../wifi/p2p/WifiP2pSettingsTest.java | 157 +++++++++++++ 7 files changed, 504 insertions(+), 19 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/wifi/p2p/WifiP2pPeerTest.java diff --git a/src/com/android/settings/wifi/p2p/WifiP2pPeer.java b/src/com/android/settings/wifi/p2p/WifiP2pPeer.java index cea7200e10a..00cb36b2670 100644 --- a/src/com/android/settings/wifi/p2p/WifiP2pPeer.java +++ b/src/com/android/settings/wifi/p2p/WifiP2pPeer.java @@ -22,6 +22,7 @@ import android.net.wifi.p2p.WifiP2pDevice; import android.text.TextUtils; import android.widget.ImageView; +import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; @@ -29,19 +30,21 @@ import com.android.settings.R; public class WifiP2pPeer extends Preference { + private static final int FIXED_RSSI = 60; private static final int[] STATE_SECURED = {R.attr.state_encrypted}; public WifiP2pDevice device; - private final int mRssi; + @VisibleForTesting final int mRssi; private ImageView mSignal; - private static final int SIGNAL_LEVELS = 4; + @VisibleForTesting + static final int SIGNAL_LEVELS = 4; public WifiP2pPeer(Context context, WifiP2pDevice dev) { super(context); device = dev; setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); - mRssi = 60; //TODO: fix + mRssi = FIXED_RSSI; //TODO: fix if (TextUtils.isEmpty(device.deviceName)) { setTitle(device.deviceAddress); } else { diff --git a/src/com/android/settings/wifi/p2p/WifiP2pPreferenceController.java b/src/com/android/settings/wifi/p2p/WifiP2pPreferenceController.java index 86cce1ef1c8..96044100647 100644 --- a/src/com/android/settings/wifi/p2p/WifiP2pPreferenceController.java +++ b/src/com/android/settings/wifi/p2p/WifiP2pPreferenceController.java @@ -52,7 +52,8 @@ public class WifiP2pPreferenceController extends AbstractPreferenceController }; private final IntentFilter mFilter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION); private final LocationManager mLocationManager; - private final BroadcastReceiver mLocationReceiver = new BroadcastReceiver() { + @VisibleForTesting + final BroadcastReceiver mLocationReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (mWifiDirectPref != null) { diff --git a/src/com/android/settings/wifi/p2p/WifiP2pSettings.java b/src/com/android/settings/wifi/p2p/WifiP2pSettings.java index 07bee3bc30a..9578c802ab0 100644 --- a/src/com/android/settings/wifi/p2p/WifiP2pSettings.java +++ b/src/com/android/settings/wifi/p2p/WifiP2pSettings.java @@ -91,16 +91,16 @@ public class WifiP2pSettings extends DashboardFragment @VisibleForTesting boolean mLastGroupFormed = false; private boolean mIsIgnoreInitConnectionInfoCallback = false; - private P2pPeerCategoryPreferenceController mPeerCategoryController; - private P2pPersistentCategoryPreferenceController mPersistentCategoryController; - private P2pThisDevicePreferenceController mThisDevicePreferenceController; + @VisibleForTesting P2pPeerCategoryPreferenceController mPeerCategoryController; + @VisibleForTesting P2pPersistentCategoryPreferenceController mPersistentCategoryController; + @VisibleForTesting P2pThisDevicePreferenceController mThisDevicePreferenceController; @VisibleForTesting static final int DIALOG_DISCONNECT = 1; @VisibleForTesting static final int DIALOG_CANCEL_CONNECT = 2; @VisibleForTesting static final int DIALOG_RENAME = 3; @VisibleForTesting static final int DIALOG_DELETE_GROUP = 4; - private static final String SAVE_DIALOG_PEER = "PEER_STATE"; + @VisibleForTesting static final String SAVE_DIALOG_PEER = "PEER_STATE"; @VisibleForTesting static final String SAVE_DEVICE_NAME = "DEV_NAME"; @VisibleForTesting static final String SAVE_SELECTED_GROUP = "GROUP_NAME"; diff --git a/tests/robotests/src/com/android/settings/wifi/WifiConfigController2Test.java b/tests/robotests/src/com/android/settings/wifi/WifiConfigController2Test.java index c9bc3465467..e588799d6a4 100644 --- a/tests/robotests/src/com/android/settings/wifi/WifiConfigController2Test.java +++ b/tests/robotests/src/com/android/settings/wifi/WifiConfigController2Test.java @@ -83,6 +83,12 @@ public class WifiConfigController2Test { // Valid PSK pass phrase private static final String GOOD_PSK = "abcdefghijklmnopqrstuvwxyz"; private static final String GOOD_SSID = "abc"; + private static final String VALID_HEX_PSK = + "123456789012345678901234567890123456789012345678901234567890abcd"; + private static final String INVALID_HEX_PSK = + "123456789012345678901234567890123456789012345678901234567890ghij"; + private static final String NUMBER_AND_CHARACTER_KEY = "123456abcd"; + private static final String PARTIAL_NUMBER_AND_CHARACTER_KEY = "123456abc?"; private static final int DHCP = 0; @Before @@ -518,16 +524,7 @@ public class WifiConfigController2Test { @Test public void selectEapMethod_savedWifiEntry_shouldGetCorrectPosition() { - when(mWifiEntry.isSaved()).thenReturn(true); - when(mWifiEntry.getSecurity()).thenReturn(WifiEntry.SECURITY_EAP); - final WifiConfiguration mockWifiConfig = mock(WifiConfiguration.class); - when(mockWifiConfig.getIpConfiguration()).thenReturn(mock(IpConfiguration.class)); - final WifiEnterpriseConfig mockWifiEnterpriseConfig = mock(WifiEnterpriseConfig.class); - when(mockWifiEnterpriseConfig.getEapMethod()).thenReturn(Eap.PEAP); - mockWifiConfig.enterpriseConfig = mockWifiEnterpriseConfig; - when(mWifiEntry.getWifiConfiguration()).thenReturn(mockWifiConfig); - mController = new TestWifiConfigController2(mConfigUiBase, mView, mWifiEntry, - WifiConfigUiBase2.MODE_MODIFY); + setUpModifyingSavedPeapConfigController(); final Spinner eapMethodSpinner = mView.findViewById(R.id.method); final Spinner phase2Spinner = mView.findViewById(R.id.phase2); WifiConfiguration wifiConfiguration; @@ -568,4 +565,199 @@ public class WifiConfigController2Test { assertThat(advButton.getContentDescription()).isEqualTo( mContext.getString(R.string.wifi_advanced_toggle_description)); } + + @Test + public void getWepConfig_withNumberAndCharacterKey_shouldContainTheSameKey() { + final TextView password = mView.findViewById(R.id.password); + password.setText(NUMBER_AND_CHARACTER_KEY); + mController.mWifiEntrySecurity = WifiEntry.SECURITY_WEP; + + WifiConfiguration wifiConfiguration = mController.getConfig(); + + assertThat(wifiConfiguration.wepKeys[0]).isEqualTo(NUMBER_AND_CHARACTER_KEY); + } + + @Test + public void getWepConfig_withPartialNumberAndCharacterKey_shouldContainDifferentKey() { + final TextView password = mView.findViewById(R.id.password); + password.setText(PARTIAL_NUMBER_AND_CHARACTER_KEY); + mController.mWifiEntrySecurity = WifiEntry.SECURITY_WEP; + + WifiConfiguration wifiConfiguration = mController.getConfig(); + + assertThat(wifiConfiguration.wepKeys[0]).isNotEqualTo(PARTIAL_NUMBER_AND_CHARACTER_KEY); + } + + @Test + public void getPskConfig_withValidHexKey_shouldContainTheSameKey() { + final TextView password = mView.findViewById(R.id.password); + password.setText(VALID_HEX_PSK); + mController.mWifiEntrySecurity = WifiEntry.SECURITY_PSK; + + WifiConfiguration wifiConfiguration = mController.getConfig(); + + assertThat(wifiConfiguration.preSharedKey).isEqualTo(VALID_HEX_PSK); + } + + @Test + public void getPskConfig_withInvalidHexKey_shouldContainDifferentKey() { + final TextView password = mView.findViewById(R.id.password); + password.setText(INVALID_HEX_PSK); + mController.mWifiEntrySecurity = WifiEntry.SECURITY_PSK; + + WifiConfiguration wifiConfiguration = mController.getConfig(); + + assertThat(wifiConfiguration.preSharedKey).isNotEqualTo(INVALID_HEX_PSK); + } + + @Test + public void getEapConfig_withPhase2Gtc_shouldContainGtcMethod() { + setUpModifyingSavedPeapConfigController(); + + // Test EAP method PEAP + final Spinner eapMethodSpinner = mView.findViewById(R.id.method); + eapMethodSpinner.setSelection(Eap.PEAP); + + // Test phase2 GTC + final Spinner phase2Spinner = mView.findViewById(R.id.phase2); + phase2Spinner.setSelection(WifiConfigController2.WIFI_PEAP_PHASE2_GTC); + + WifiConfiguration wifiConfiguration = mController.getConfig(); + + assertThat(wifiConfiguration.enterpriseConfig.getPhase2Method()).isEqualTo(Phase2.GTC); + } + + @Test + public void getEapConfig_withPhase2Sim_shouldContainSimMethod() { + setUpModifyingSavedPeapConfigController(); + + // Test EAP method PEAP + final Spinner eapMethodSpinner = mView.findViewById(R.id.method); + eapMethodSpinner.setSelection(Eap.PEAP); + + // Test phase2 SIM + final Spinner phase2Spinner = mView.findViewById(R.id.phase2); + phase2Spinner.setSelection(WifiConfigController2.WIFI_PEAP_PHASE2_SIM); + + WifiConfiguration wifiConfiguration = mController.getConfig(); + + assertThat(wifiConfiguration.enterpriseConfig.getPhase2Method()).isEqualTo(Phase2.SIM); + } + + @Test + public void getEapConfig_withPhase2Aka_shouldContainAkaMethod() { + setUpModifyingSavedPeapConfigController(); + + // Test EAP method PEAP + final Spinner eapMethodSpinner = mView.findViewById(R.id.method); + eapMethodSpinner.setSelection(Eap.PEAP); + + // Test phase2 AKA + final Spinner phase2Spinner = mView.findViewById(R.id.phase2); + phase2Spinner.setSelection(WifiConfigController2.WIFI_PEAP_PHASE2_AKA); + + WifiConfiguration wifiConfiguration = mController.getConfig(); + + assertThat(wifiConfiguration.enterpriseConfig.getPhase2Method()).isEqualTo(Phase2.AKA); + } + + @Test + public void getEapConfig_withPhase2AkaPrime_shouldContainAkaPrimeMethod() { + setUpModifyingSavedPeapConfigController(); + + // Test EAP method PEAP + final Spinner eapMethodSpinner = mView.findViewById(R.id.method); + eapMethodSpinner.setSelection(Eap.PEAP); + + // Test phase2 AKA PRIME + final Spinner phase2Spinner = mView.findViewById(R.id.phase2); + phase2Spinner.setSelection(WifiConfigController2.WIFI_PEAP_PHASE2_AKA_PRIME); + + WifiConfiguration wifiConfiguration = mController.getConfig(); + + assertThat(wifiConfiguration.enterpriseConfig.getPhase2Method()).isEqualTo( + Phase2.AKA_PRIME); + } + + + @Test + public void getEapConfig_withPeapPhase2Unknown_shouldContainNoneMethod() { + setUpModifyingSavedPeapConfigController(); + + // Test EAP method PEAP + final Spinner eapMethodSpinner = mView.findViewById(R.id.method); + eapMethodSpinner.setSelection(Eap.PEAP); + + // Test phase2 Unknown + final Spinner phase2Spinner = mView.findViewById(R.id.phase2); + phase2Spinner.setSelection(-1); + + WifiConfiguration wifiConfiguration = mController.getConfig(); + + assertThat(wifiConfiguration.enterpriseConfig.getPhase2Method()).isEqualTo(Phase2.NONE); + } + + @Test + public void getEapConfig_withTTLSPhase2Pap_shouldContainPapMethod() { + setUpModifyingSavedPeapConfigController(); + + // Test EAP method TTLS + final Spinner eapMethodSpinner = mView.findViewById(R.id.method); + eapMethodSpinner.setSelection(Eap.TTLS); + + // Test phase2 PAP + final Spinner phase2Spinner = mView.findViewById(R.id.phase2); + phase2Spinner.setSelection(WifiConfigController2.WIFI_TTLS_PHASE2_PAP); + + WifiConfiguration wifiConfiguration = mController.getConfig(); + + assertThat(wifiConfiguration.enterpriseConfig.getPhase2Method()).isEqualTo(Phase2.PAP); + } + + @Test + public void getEapConfig_withTTLSPhase2Mschap_shouldContainMschapMethod() { + setUpModifyingSavedPeapConfigController(); + + // Test EAP method TTLS + final Spinner eapMethodSpinner = mView.findViewById(R.id.method); + eapMethodSpinner.setSelection(Eap.TTLS); + + // Test phase2 MSCHAP + final Spinner phase2Spinner = mView.findViewById(R.id.phase2); + phase2Spinner.setSelection(WifiConfigController2.WIFI_TTLS_PHASE2_MSCHAP); + + WifiConfiguration wifiConfiguration = mController.getConfig(); + + assertThat(wifiConfiguration.enterpriseConfig.getPhase2Method()).isEqualTo(Phase2.MSCHAP); + } + + @Test + public void getEapConfig_withTTLSPhase2Gtc_shouldContainGtcMethod() { + setUpModifyingSavedPeapConfigController(); + + // Test EAP method TTLS + final Spinner eapMethodSpinner = mView.findViewById(R.id.method); + eapMethodSpinner.setSelection(Eap.TTLS); + + // Test phase2 GTC + final Spinner phase2Spinner = mView.findViewById(R.id.phase2); + phase2Spinner.setSelection(WifiConfigController2.WIFI_TTLS_PHASE2_GTC); + + WifiConfiguration wifiConfiguration = mController.getConfig(); + + assertThat(wifiConfiguration.enterpriseConfig.getPhase2Method()).isEqualTo(Phase2.GTC); + } + + private void setUpModifyingSavedPeapConfigController() { + when(mWifiEntry.isSaved()).thenReturn(true); + when(mWifiEntry.getSecurity()).thenReturn(WifiEntry.SECURITY_EAP); + final WifiConfiguration mockWifiConfig = mock(WifiConfiguration.class); + when(mockWifiConfig.getIpConfiguration()).thenReturn(mock(IpConfiguration.class)); + final WifiEnterpriseConfig mockWifiEnterpriseConfig = mock(WifiEnterpriseConfig.class); + when(mockWifiEnterpriseConfig.getEapMethod()).thenReturn(Eap.PEAP); + mockWifiConfig.enterpriseConfig = mockWifiEnterpriseConfig; + when(mWifiEntry.getWifiConfiguration()).thenReturn(mockWifiConfig); + mController = new TestWifiConfigController2(mConfigUiBase, mView, mWifiEntry, + WifiConfigUiBase2.MODE_MODIFY); + } } diff --git a/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2PPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2PPreferenceControllerTest.java index ec8d16802d2..973168904c4 100644 --- a/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2PPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2PPreferenceControllerTest.java @@ -39,7 +39,6 @@ import androidx.lifecycle.LifecycleOwner; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; -import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.core.lifecycle.Lifecycle; import org.junit.Before; @@ -132,4 +131,17 @@ public class WifiP2PPreferenceControllerTest { mController.displayPreference(mScreen); verify(mWifiDirectPreference, times(2)).setEnabled(false); } + + @Test + public void updateState_withLocationDisabled_preferenceShouldBeDisable() { + when(mWifiManager.isWifiEnabled()).thenReturn(true); + when(mLocationManager.isLocationEnabled()).thenReturn(true); + Intent dummyIntent = new Intent(); + mController.displayPreference(mScreen); + verify(mWifiDirectPreference).setEnabled(true); + + when(mLocationManager.isLocationEnabled()).thenReturn(false); + mController.mLocationReceiver.onReceive(mContext, dummyIntent); + verify(mWifiDirectPreference).setEnabled(false); + } } diff --git a/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2pPeerTest.java b/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2pPeerTest.java new file mode 100644 index 00000000000..c3ae0690ba0 --- /dev/null +++ b/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2pPeerTest.java @@ -0,0 +1,120 @@ +/* + * 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.wifi.p2p; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; + +import android.content.Context; +import android.net.wifi.WifiManager; +import android.net.wifi.p2p.WifiP2pDevice; + +import androidx.preference.Preference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class WifiP2pPeerTest { + + private static final String DEVICE_NAME = "fakeName"; + private static final String OTHER_NAME = "otherName"; + private static final String MAC_ADDRESS = "00:11:22:33:44:55"; + + private Context mContext; + private WifiP2pPeer mPreference; + + @Mock + private WifiP2pDevice mWifiP2pDevice; + @Mock + private WifiP2pPeer mOtherWifiP2pPeer; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + } + + @Test + public void compareTo_withSameDeviceName_shouldBeZero() { + setupOneOtherP2pPeer(DEVICE_NAME, null /* address */); + mWifiP2pDevice.deviceName = DEVICE_NAME; + mPreference = new WifiP2pPeer(mContext, mWifiP2pDevice); + + assertThat(mPreference.compareTo(mOtherWifiP2pPeer)).isEqualTo(0); + } + + @Test + public void compareTo_withDifferentDeviceName_shouldNotZero() { + setupOneOtherP2pPeer(DEVICE_NAME, null /* address */); + mWifiP2pDevice.deviceName = OTHER_NAME; + mPreference = new WifiP2pPeer(mContext, mWifiP2pDevice); + + assertThat(mPreference.compareTo(mOtherWifiP2pPeer)).isNotEqualTo(0); + } + + @Test + public void compareTo_withSameDeviceAddress_shouldBeZero() { + setupOneOtherP2pPeer(null /* name */, MAC_ADDRESS); + mWifiP2pDevice.deviceAddress = MAC_ADDRESS; + mPreference = new WifiP2pPeer(mContext, mWifiP2pDevice); + + assertThat(mPreference.compareTo(mOtherWifiP2pPeer)).isEqualTo(0); + } + + @Test + public void compareTo_withLowerDeviceStatus_shouldBeOne() { + setupOneOtherP2pPeer(DEVICE_NAME, null /* address */); + mWifiP2pDevice.status = WifiP2pDevice.FAILED; + mPreference = new WifiP2pPeer(mContext, mWifiP2pDevice); + + assertThat(mPreference.compareTo(mOtherWifiP2pPeer)).isEqualTo(1); + } + + @Test + public void compareTo_withNotPeerParameter_shouldBeOne() { + final Preference fakePreference = mock(Preference.class); + setupOneOtherP2pPeer(DEVICE_NAME, null /* address */); + mPreference = new WifiP2pPeer(mContext, mWifiP2pDevice); + + assertThat(mPreference.compareTo(fakePreference)).isEqualTo(1); + } + + @Test + public void signalLevel_afterNewPreference_shouldBeExpected() { + mPreference = new WifiP2pPeer(mContext, mWifiP2pDevice); + + final int expectSignalLevel = WifiManager.calculateSignalLevel(mPreference.mRssi, + WifiP2pPeer.SIGNAL_LEVELS); + + assertThat(mPreference.getLevel()).isEqualTo(expectSignalLevel); + } + + private void setupOneOtherP2pPeer(String name, String address) { + final WifiP2pDevice wifiP2pDevice = mock(WifiP2pDevice.class); + wifiP2pDevice.status = WifiP2pDevice.CONNECTED; + wifiP2pDevice.deviceAddress = address; + wifiP2pDevice.deviceName = name; + mOtherWifiP2pPeer.device = wifiP2pDevice; + } +} diff --git a/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2pSettingsTest.java b/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2pSettingsTest.java index d212ddb6e3c..81615f231b4 100644 --- a/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2pSettingsTest.java +++ b/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2pSettingsTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -33,6 +34,7 @@ import android.net.NetworkInfo; import android.net.wifi.p2p.WifiP2pDevice; import android.net.wifi.p2p.WifiP2pDeviceList; import android.net.wifi.p2p.WifiP2pGroup; +import android.net.wifi.p2p.WifiP2pGroupList; import android.net.wifi.p2p.WifiP2pInfo; import android.net.wifi.p2p.WifiP2pManager; import android.os.Bundle; @@ -360,6 +362,161 @@ public class WifiP2pSettingsTest { assertThat(mFragment.getDialogMetricsCategory(-1 /* dialogId */)).isEqualTo(0); } + @Test + public void onSaveInstanceState_withWiFiPeer_shouldGetP2pDeviceType() { + setupOneP2pPeer(WifiP2pDevice.CONNECTED); + mFragment.onPreferenceTreeClick(mWifiP2pPeer); + final Bundle outBundle = new Bundle(); + + mFragment.onSaveInstanceState(outBundle); + + final Object object = outBundle.getParcelable(WifiP2pSettings.SAVE_DIALOG_PEER); + assertThat(object instanceof WifiP2pDevice).isTrue(); + } + + @Test + public void onSaveInstanceState_withDeviceNameText_shouldSaveName() { + final String fakeDeviceName = "fakeName"; + final Bundle createBundle = new Bundle(); + createBundle.putString(WifiP2pSettings.SAVE_DEVICE_NAME, fakeDeviceName); + mFragment.onActivityCreated(createBundle); + final Bundle outBundle = new Bundle(); + final Dialog dialog = mFragment.onCreateDialog(WifiP2pSettings.DIALOG_RENAME); + + mFragment.onSaveInstanceState(outBundle); + + final String string = outBundle.getString(WifiP2pSettings.SAVE_DEVICE_NAME); + assertThat(string).isEqualTo(fakeDeviceName); + } + + @Test + public void onSaveInstanceState_withSelectedGroup_shouldSaveGroupName() { + final String fakeGroupName = "fakeGroupName"; + final WifiP2pPersistentGroup wifiP2pPersistentGroup = spy( + new WifiP2pPersistentGroup(mContext, + mWifiP2pGroup)); + doReturn(fakeGroupName).when(wifiP2pPersistentGroup).getGroupName(); + mFragment.mSelectedGroup = wifiP2pPersistentGroup; + final Bundle outBundle = new Bundle(); + + mFragment.onSaveInstanceState(outBundle); + + assertThat(outBundle.getString(WifiP2pSettings.SAVE_SELECTED_GROUP)).isEqualTo( + fakeGroupName); + } + + @Test + public void persistentController_withOneGroup_shouldBeAvailable() { + final String fakeGroupName = new String("fakeGroupName"); + doReturn(fakeGroupName).when(mWifiP2pGroup).getNetworkName(); + final List groupList = new ArrayList<>(); + groupList.add(mWifiP2pGroup); + final WifiP2pGroupList wifiP2pGroupList = mock(WifiP2pGroupList.class); + doReturn(groupList).when(wifiP2pGroupList).getGroupList(); + final Bundle bundle = new Bundle(); + bundle.putString(WifiP2pSettings.SAVE_SELECTED_GROUP, fakeGroupName); + mFragment.onActivityCreated(bundle); + + mFragment.onPersistentGroupInfoAvailable(wifiP2pGroupList); + + assertThat(mFragment.mPersistentCategoryController.isAvailable()).isTrue(); + } + + @Test + public void persistentController_withNoGroup_shouldBeUnavailable() { + final WifiP2pGroupList wifiP2pGroupList = mock(WifiP2pGroupList.class); + final List groupList = new ArrayList<>(); + doReturn(groupList).when(wifiP2pGroupList).getGroupList(); + + mFragment.onPersistentGroupInfoAvailable(wifiP2pGroupList); + + assertThat(mFragment.mPersistentCategoryController.isAvailable()).isFalse(); + } + + @Test + public void peersCategoryController_withOnePeerDevice_shouldBeAvailable() { + final WifiP2pDevice wifiP2pDevice = mock(WifiP2pDevice.class); + final ArrayList deviceList = new ArrayList<>(); + deviceList.add(wifiP2pDevice); + final WifiP2pDeviceList peers = mock(WifiP2pDeviceList.class); + doReturn(deviceList).when(peers).getDeviceList(); + + mFragment.onPeersAvailable(peers); + + assertThat(mFragment.mPeerCategoryController.isAvailable()).isTrue(); + } + + @Test + public void peersCategoryController_withNoPeerDevice_shouldBeUnavailable() { + final ArrayList deviceList = new ArrayList<>(); + final WifiP2pDeviceList peers = mock(WifiP2pDeviceList.class); + doReturn(deviceList).when(peers).getDeviceList(); + + mFragment.onPeersAvailable(peers); + + assertThat(mFragment.mPeerCategoryController.isAvailable()).isFalse(); + } + + @Test + public void thisDeviceController_onDeviceInfoAvailable_shouldUpdateDeviceName() { + final WifiP2pDevice wifiP2pDevice = mock(WifiP2pDevice.class); + final P2pThisDevicePreferenceController thisDevicePreferenceController = mock( + P2pThisDevicePreferenceController.class); + mFragment.mThisDevicePreferenceController = thisDevicePreferenceController; + + mFragment.onDeviceInfoAvailable(wifiP2pDevice); + + verify(thisDevicePreferenceController, times(1)).updateDeviceName(any()); + } + + @Test + public void p2pThisDeviceChange_shouldRequestDeviceInfoAgain() { + final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); + + mFragment.mReceiver.onReceive(mContext, intent); + + verify(mWifiP2pManager, times(2)).requestDeviceInfo(any(), any()); + } + + @Test + public void p2pPersistentGroupChange_shouldRequestGroupInfo() { + final Intent intent = new Intent(WifiP2pManager.ACTION_WIFI_P2P_PERSISTENT_GROUPS_CHANGED); + + mFragment.mReceiver.onReceive(mContext, intent); + + verify(mWifiP2pManager, times(1)).requestPersistentGroupInfo(any(), any()); + } + + @Test + public void onActivityCreate_withNullP2pManager_shouldGetP2pManagerAgain() { + mFragment.mWifiP2pManager = null; + + mFragment.onActivityCreated(new Bundle()); + + assertThat(mFragment.mWifiP2pManager).isNotNull(); + } + + @Test + public void onActivityCreate_withNullChannel_shouldSetP2pManagerNull() { + doReturn(null).when(mWifiP2pManager).initialize(any(), any(), any()); + + mFragment.onActivityCreated(new Bundle()); + + assertThat(mFragment.mWifiP2pManager).isNull(); + } + + @Test + public void clickNegativeButton_whenDeleteGroupDialogShow_shouldSetGroupNull() { + final WifiP2pPersistentGroup wifiP2pPersistentGroup = new WifiP2pPersistentGroup(mContext, + mWifiP2pGroup); + mFragment.mSelectedGroup = wifiP2pPersistentGroup; + final Dialog dialog = mFragment.onCreateDialog(WifiP2pSettings.DIALOG_DELETE_GROUP); + + mFragment.mDeleteGroupListener.onClick(dialog, DialogInterface.BUTTON_NEGATIVE); + + assertThat(mFragment.mSelectedGroup).isNull(); + } + private void setupOneP2pPeer(int status) { final WifiP2pDevice wifiP2pDevice = mock(WifiP2pDevice.class); wifiP2pDevice.status = status; From cabe76debcc1666e8d90949d42206b065dea7ba8 Mon Sep 17 00:00:00 2001 From: Kevin Maurin Date: Wed, 22 Apr 2020 13:53:55 -0700 Subject: [PATCH 06/14] Display "Android Auto" in Connected Devices summary text. Android Auto will be exposing a Settings tile under Connected Devices -> Connection Preferences. If the Setting tile is available, then the summary text will reflect that. Tested with additional unit tests and manually validated the scenarios by flashing crosshatch-eng and disabling/enabling the relevant features. Fix: 153896447 Test: Added unit coverage for new scenarios Test: make RunSettingsRoboTests Test: manually verified by flashing crosshatch-eng and disabling/enabling relevant components Change-Id: I5a129c41b98ba435bc159a72c9a7c2dfd646ecd3 --- res/values/strings.xml | 8 ++ .../AdvancedConnectedDeviceController.java | 61 +++++++++--- ...AdvancedConnectedDeviceControllerTest.java | 94 +++++++++++++++++-- 3 files changed, 144 insertions(+), 19 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 6897c656dea..244e8738bbb 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -7485,6 +7485,14 @@ Bluetooth, NFC Bluetooth + + Bluetooth, Android Auto, driving mode, NFC + Bluetooth, Android Auto, driving mode + + Bluetooth, Android Auto, NFC + + Bluetooth, Android Auto Unavailable because NFC is off diff --git a/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceController.java b/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceController.java index 26c3e345558..2855f0ab16a 100644 --- a/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceController.java +++ b/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceController.java @@ -15,7 +15,10 @@ */ package com.android.settings.connecteddevice; +import static com.android.settingslib.drawer.TileUtils.IA_SETTINGS_ACTION; + import android.content.Context; +import android.content.Intent; import android.provider.Settings; import androidx.annotation.VisibleForTesting; @@ -31,6 +34,7 @@ public class AdvancedConnectedDeviceController extends BasePreferenceController private static final String DRIVING_MODE_SETTINGS_ENABLED = "gearhead:driving_mode_settings_enabled"; + private static final String GEARHEAD_PACKAGE = "com.google.android.projection.gearhead"; public AdvancedConnectedDeviceController(Context context, String preferenceKey) { super(context, preferenceKey); @@ -55,7 +59,7 @@ public class AdvancedConnectedDeviceController extends BasePreferenceController new NfcPreferenceController(context, NfcPreferenceController.KEY_TOGGLE_NFC); return getConnectedDevicesSummaryResourceId(nfcPreferenceController, - isDrivingModeAvailable(context)); + isDrivingModeAvailable(context), isAndroidAutoSettingAvailable(context)); } @VisibleForTesting @@ -64,26 +68,57 @@ public class AdvancedConnectedDeviceController extends BasePreferenceController getInt(context.getContentResolver(), DRIVING_MODE_SETTINGS_ENABLED, 0) == 1; } + @VisibleForTesting + static boolean isAndroidAutoSettingAvailable(Context context) { + final Intent intent = new Intent(IA_SETTINGS_ACTION); + intent.setPackage(GEARHEAD_PACKAGE); + return intent.resolveActivity(context.getPackageManager()) != null; + } + @VisibleForTesting static int getConnectedDevicesSummaryResourceId(NfcPreferenceController - nfcPreferenceController, boolean isDrivingModeAvailable) { + nfcPreferenceController, + boolean isDrivingModeAvailable, + boolean isAndroidAutoAvailable) { final int resId; - if (nfcPreferenceController.isAvailable()) { - if (isDrivingModeAvailable) { - // NFC available, driving mode available - resId = R.string.connected_devices_dashboard_summary; + if (isAndroidAutoAvailable) { + if (nfcPreferenceController.isAvailable()) { + if (isDrivingModeAvailable) { + // NFC available, driving mode available + resId = R.string.connected_devices_dashboard_android_auto_summary; + } else { + // NFC available, driving mode not available + resId = + R.string.connected_devices_dashboard_android_auto_no_driving_mode_summary; + } } else { - // NFC available, driving mode not available - resId = R.string.connected_devices_dashboard_no_driving_mode_summary; + if (isDrivingModeAvailable) { + // NFC not available, driving mode available + resId = R.string.connected_devices_dashboard_android_auto_no_nfc_summary; + } else { + // NFC not available, driving mode not available + resId = + R.string.connected_devices_dashboard_android_auto_no_nfc_no_driving_mode; + } } } else { - if (isDrivingModeAvailable) { - // NFC not available, driving mode available - resId = R.string.connected_devices_dashboard_no_nfc_summary; + if (nfcPreferenceController.isAvailable()) { + if (isDrivingModeAvailable) { + // NFC available, driving mode available + resId = R.string.connected_devices_dashboard_summary; + } else { + // NFC available, driving mode not available + resId = R.string.connected_devices_dashboard_no_driving_mode_summary; + } } else { - // NFC not available, driving mode not available - resId = R.string.connected_devices_dashboard_no_driving_mode_no_nfc_summary; + if (isDrivingModeAvailable) { + // NFC not available, driving mode available + resId = R.string.connected_devices_dashboard_no_nfc_summary; + } else { + // NFC not available, driving mode not available + resId = R.string.connected_devices_dashboard_no_driving_mode_no_nfc_summary; + } } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceControllerTest.java index 08f937b2788..974a52b519d 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceControllerTest.java @@ -23,9 +23,15 @@ import static org.mockito.Mockito.spy; import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.ResolveInfo; import android.nfc.NfcAdapter; import android.provider.Settings; +import androidx.test.core.content.pm.ApplicationInfoBuilder; + import com.android.settings.R; import com.android.settings.nfc.NfcPreferenceController; @@ -38,6 +44,7 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.Shadows; import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowNfcAdapter; +import org.robolectric.shadows.ShadowPackageManager; import org.robolectric.util.ReflectionHelpers; @RunWith(RobolectricTestRunner.class) @@ -47,11 +54,13 @@ public class AdvancedConnectedDeviceControllerTest { private static final String KEY = "test_key"; private static final String DRIVING_MODE_SETTINGS_ENABLED = "gearhead:driving_mode_settings_enabled"; + private static final String ANDROID_AUTO_PACKAGE = "com.google.android.projection.gearhead"; private Context mContext; private NfcPreferenceController mNfcController; private ShadowNfcAdapter mShadowNfcAdapter; private ContentResolver mContentResolver; + private ShadowPackageManager mShadowPackageManager; @Before public void setUp() { @@ -62,6 +71,7 @@ public class AdvancedConnectedDeviceControllerTest { mNfcController = new NfcPreferenceController(mContext, NfcPreferenceController.KEY_TOGGLE_NFC); mShadowNfcAdapter = Shadows.shadowOf(NfcAdapter.getNfcAdapter(mContext)); + mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager()); } @Test @@ -76,14 +86,41 @@ public class AdvancedConnectedDeviceControllerTest { public void isDrivingModeAvailable_returnTrue() { Settings.System.putInt(mContentResolver, DRIVING_MODE_SETTINGS_ENABLED, 1); - assertThat(AdvancedConnectedDeviceController.isDrivingModeAvailable(mContext)).isTrue(); + assertThat( + AdvancedConnectedDeviceController.isDrivingModeAvailable(mContext)).isTrue(); } @Test public void isDrivingModeAvailable_returnFalse() { Settings.System.putInt(mContentResolver, DRIVING_MODE_SETTINGS_ENABLED, 0); - assertThat(AdvancedConnectedDeviceController.isDrivingModeAvailable(mContext)).isFalse(); + assertThat( + AdvancedConnectedDeviceController.isDrivingModeAvailable(mContext)).isFalse(); + } + + @Test + public void isAndroidAutoSettingAvailable_returnTrue() { + final ApplicationInfo appInfo = + ApplicationInfoBuilder.newBuilder().setPackageName(ANDROID_AUTO_PACKAGE).build(); + final ActivityInfo activityInfo = new ActivityInfo(); + activityInfo.packageName = ANDROID_AUTO_PACKAGE; + activityInfo.name = ANDROID_AUTO_PACKAGE; + activityInfo.applicationInfo = appInfo; + final ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.activityInfo = activityInfo; + mShadowPackageManager.addResolveInfoForIntent( + buildAndroidAutoSettingsIntent(), + resolveInfo); + + assertThat( + AdvancedConnectedDeviceController.isAndroidAutoSettingAvailable(mContext)).isTrue(); + } + + @Test + public void isAndroidAutoSettingAvailable_returnFalse() { + // No ResolveInfo for Android Auto, expect false. + assertThat( + AdvancedConnectedDeviceController.isAndroidAutoSettingAvailable(mContext)).isFalse(); } @Test @@ -91,7 +128,7 @@ public class AdvancedConnectedDeviceControllerTest { // NFC available, driving mode available mShadowNfcAdapter.setEnabled(true); assertThat(AdvancedConnectedDeviceController - .getConnectedDevicesSummaryResourceId(mNfcController, true)) + .getConnectedDevicesSummaryResourceId(mNfcController, true, false)) .isEqualTo(R.string.connected_devices_dashboard_summary); } @@ -100,7 +137,7 @@ public class AdvancedConnectedDeviceControllerTest { // NFC is available, driving mode not available mShadowNfcAdapter.setEnabled(true); assertThat(AdvancedConnectedDeviceController - .getConnectedDevicesSummaryResourceId(mNfcController, false)) + .getConnectedDevicesSummaryResourceId(mNfcController, false, false)) .isEqualTo(R.string.connected_devices_dashboard_no_driving_mode_summary); } @@ -109,7 +146,7 @@ public class AdvancedConnectedDeviceControllerTest { // NFC not available, driving mode available ReflectionHelpers.setField(mNfcController, "mNfcAdapter", null); assertThat(AdvancedConnectedDeviceController - .getConnectedDevicesSummaryResourceId(mNfcController, true)) + .getConnectedDevicesSummaryResourceId(mNfcController, true, false)) .isEqualTo(R.string.connected_devices_dashboard_no_nfc_summary); } @@ -118,7 +155,52 @@ public class AdvancedConnectedDeviceControllerTest { // NFC not available, driving mode not available ReflectionHelpers.setField(mNfcController, "mNfcAdapter", null); assertThat(AdvancedConnectedDeviceController - .getConnectedDevicesSummaryResourceId(mNfcController, false)) + .getConnectedDevicesSummaryResourceId(mNfcController, false, false)) .isEqualTo(R.string.connected_devices_dashboard_no_driving_mode_no_nfc_summary); } + + @Test + public void getConnectedDevicesSummaryResourceId_Auto_NFC_DrivingMode_Available() { + // NFC available, driving mode available + mShadowNfcAdapter.setEnabled(true); + assertThat(AdvancedConnectedDeviceController + .getConnectedDevicesSummaryResourceId(mNfcController, true, true)) + .isEqualTo(R.string.connected_devices_dashboard_android_auto_summary); + } + + @Test + public void getConnectedDevicesSummaryResourceId_Auto_NFC_Available() { + // NFC is available, driving mode not available + mShadowNfcAdapter.setEnabled(true); + assertThat(AdvancedConnectedDeviceController + .getConnectedDevicesSummaryResourceId(mNfcController, false, true)) + .isEqualTo( + R.string.connected_devices_dashboard_android_auto_no_driving_mode_summary); + } + + @Test + public void getConnectedDevicesSummaryResourceId_Auto_DrivingMode_Available() { + // NFC not available, driving mode available + ReflectionHelpers.setField(mNfcController, "mNfcAdapter", null); + assertThat(AdvancedConnectedDeviceController + .getConnectedDevicesSummaryResourceId(mNfcController, true, true)) + .isEqualTo(R.string.connected_devices_dashboard_android_auto_no_nfc_summary); + } + + @Test + public void getConnectedDevicesSummaryResourceId_Auto_Available() { + // NFC not available, driving mode not available + ReflectionHelpers.setField(mNfcController, "mNfcAdapter", null); + assertThat(AdvancedConnectedDeviceController + .getConnectedDevicesSummaryResourceId(mNfcController, false, true)) + .isEqualTo( + R.string.connected_devices_dashboard_android_auto_no_nfc_no_driving_mode); + } + + private Intent buildAndroidAutoSettingsIntent() { + final Intent intent = new Intent("com.android.settings.action.IA_SETTINGS"); + intent.setPackage(ANDROID_AUTO_PACKAGE); + return intent; + } } + From d1c8d2a77121b82e026408eda500ff286c09811e Mon Sep 17 00:00:00 2001 From: Bonian Chen Date: Thu, 7 May 2020 14:14:04 +0800 Subject: [PATCH 07/14] [Settings] Update EID in background Access EID in background thread to avoid blocking UI. Bug: 153407357 Test: make RunSettingsRoboTests -j ROBOTEST_FILTER=SimStatusDialogControllerTest Change-Id: Id30851bbd0b0fe080ed703586f9718be8b97abdd --- .../simstatus/SimStatusDialogController.java | 37 ++++++++++++++----- .../SimStatusDialogControllerTest.java | 18 +++++++++ 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogController.java b/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogController.java index f9aef2cd41e..fc6811a81a9 100644 --- a/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogController.java +++ b/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogController.java @@ -61,9 +61,11 @@ import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnPause; import com.android.settingslib.core.lifecycle.events.OnResume; +import com.android.settingslib.utils.ThreadUtils; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; public class SimStatusDialogController implements LifecycleObserver, OnResume, OnPause { @@ -218,7 +220,7 @@ public class SimStatusDialogController implements LifecycleObserver, OnResume, O } public void initialize() { - updateEid(); + requestForUpdateEid(); if (mSubscriptionInfo == null) { return; @@ -560,25 +562,33 @@ public class SimStatusDialogController implements LifecycleObserver, OnResume, O } } - private void updateEid() { + @VisibleForTesting + void requestForUpdateEid() { + ThreadUtils.postOnBackgroundThread(() -> { + final AtomicReference eid = getEid(mSlotIndex); + ThreadUtils.postOnMainThread(() -> updateEid(eid)); + }); + } + + @VisibleForTesting + AtomicReference getEid(int slotIndex) { boolean shouldHaveEid = false; String eid = null; - if (mTelephonyManager.getActiveModemCount() > MAX_PHONE_COUNT_SINGLE_SIM) { // Get EID per-SIM in multi-SIM mode - Map mapping = mTelephonyManager.getLogicalToPhysicalSlotMapping(); - int pSlotId = mapping.getOrDefault(mSlotIndex, + final Map mapping = mTelephonyManager + .getLogicalToPhysicalSlotMapping(); + final int pSlotId = mapping.getOrDefault(slotIndex, SubscriptionManager.INVALID_SIM_SLOT_INDEX); if (pSlotId != SubscriptionManager.INVALID_SIM_SLOT_INDEX) { - List infos = mTelephonyManager.getUiccCardsInfo(); + final List infos = mTelephonyManager.getUiccCardsInfo(); for (UiccCardInfo info : infos) { if (info.getSlotIndex() == pSlotId) { if (info.isEuicc()) { shouldHaveEid = true; eid = info.getEid(); - if (TextUtils.isEmpty(eid)) { eid = mEuiccManager.createForCardId(info.getCardId()).getEid(); } @@ -592,12 +602,19 @@ public class SimStatusDialogController implements LifecycleObserver, OnResume, O shouldHaveEid = true; eid = mEuiccManager.getEid(); } + if ((!shouldHaveEid) && (eid == null)) { + return null; + } + return new AtomicReference(eid); + } - if (!shouldHaveEid) { + @VisibleForTesting + void updateEid(AtomicReference eid) { + if (eid == null) { mDialog.removeSettingFromScreen(EID_INFO_LABEL_ID); mDialog.removeSettingFromScreen(EID_INFO_VALUE_ID); - } else if (!TextUtils.isEmpty(eid)) { - mDialog.setText(EID_INFO_VALUE_ID, eid); + } else if (eid.get() != null) { + mDialog.setText(EID_INFO_VALUE_ID, eid.get()); } } diff --git a/tests/robotests/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogControllerTest.java index 395c8a47219..bbab2592707 100644 --- a/tests/robotests/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogControllerTest.java +++ b/tests/robotests/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogControllerTest.java @@ -38,6 +38,7 @@ import static com.android.settings.deviceinfo.simstatus.SimStatusDialogControlle import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -452,6 +453,8 @@ public class SimStatusDialogControllerTest { when(mEuiccManager.isEnabled()).thenReturn(true); when(mEuiccManager.getEid()).thenReturn(null); + doNothing().when(mController).requestForUpdateEid(); + mController.updateEid(mController.getEid(0)); mController.initialize(); // Keep 'Not available' if neither the card nor the associated manager can provide EID. @@ -489,7 +492,10 @@ public class SimStatusDialogControllerTest { when(mEuiccManager.isEnabled()).thenReturn(true); when(mEuiccManager.getEid()).thenReturn(TEST_EID_FROM_MANAGER); + when(mEuiccManager.createForCardId(0)).thenReturn(mEuiccManager); + doNothing().when(mController).requestForUpdateEid(); + mController.updateEid(mController.getEid(0)); mController.initialize(); // Set EID retrieved from the card. @@ -531,6 +537,8 @@ public class SimStatusDialogControllerTest { new RuntimeException("Unexpected card ID was specified")); when(mEuiccManager.createForCardId(1)).thenReturn(mEuiccManager); + doNothing().when(mController).requestForUpdateEid(); + mController.updateEid(mController.getEid(0)); mController.initialize(); // Set EID retrieved from the manager associated with the card which cannot provide EID. @@ -569,6 +577,8 @@ public class SimStatusDialogControllerTest { when(mEuiccManager.isEnabled()).thenReturn(true); when(mEuiccManager.getEid()).thenReturn(TEST_EID_FROM_MANAGER); + doNothing().when(mController).requestForUpdateEid(); + mController.updateEid(mController.getEid(0)); mController.initialize(); // Remove EID if the card is not eUICC. @@ -599,6 +609,8 @@ public class SimStatusDialogControllerTest { when(mEuiccManager.isEnabled()).thenReturn(true); when(mEuiccManager.getEid()).thenReturn(null); + doNothing().when(mController).requestForUpdateEid(); + mController.updateEid(mController.getEid(0)); mController.initialize(); // Keep 'Not available' if the default eUICC manager cannot provide EID in Single SIM mode. @@ -630,6 +642,8 @@ public class SimStatusDialogControllerTest { when(mEuiccManager.createForCardId(anyInt())).thenThrow( new RuntimeException("EID shall be retrieved from the default eUICC manager")); + doNothing().when(mController).requestForUpdateEid(); + mController.updateEid(mController.getEid(0)); mController.initialize(); // Set EID retrieved from the default eUICC manager in Single SIM mode. @@ -661,6 +675,8 @@ public class SimStatusDialogControllerTest { when(mEuiccManager.createForCardId(anyInt())).thenThrow( new RuntimeException("EID shall be retrieved from the default eUICC manager")); + doNothing().when(mController).requestForUpdateEid(); + mController.updateEid(mController.getEid(0)); mController.initialize(); // Set EID retrieved from the default eUICC manager in Single SIM mode. @@ -690,6 +706,8 @@ public class SimStatusDialogControllerTest { when(mEuiccManager.isEnabled()).thenReturn(false); when(mEuiccManager.getEid()).thenReturn(null); + doNothing().when(mController).requestForUpdateEid(); + mController.updateEid(mController.getEid(0)); mController.initialize(); // Remove EID if the default eUICC manager indicates that eSIM is not enabled. From 7fcfac49cf4889eac238d7531085358421eadfef Mon Sep 17 00:00:00 2001 From: Tim Peng Date: Wed, 6 May 2020 11:46:22 +0800 Subject: [PATCH 08/14] Settings > Sound crashes if no BT -Replace androidx.lifecycle.LifecycleObserver with com.android.settingslib.core.lifecycle.LifecycleObserver -Observer is added in DashboardFragment Bug: 155195625 Test: make -j50 RunSettingsRoboTests Change-Id: I99c587e4227e81ba521782f92b159051a1cba2d2 --- .../notification/RemoteVolumeGroupController.java | 14 +++++--------- .../settings/notification/SoundSettings.java | 1 - 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/com/android/settings/notification/RemoteVolumeGroupController.java b/src/com/android/settings/notification/RemoteVolumeGroupController.java index 137f7803d8b..6a9ac808ecb 100644 --- a/src/com/android/settings/notification/RemoteVolumeGroupController.java +++ b/src/com/android/settings/notification/RemoteVolumeGroupController.java @@ -23,15 +23,14 @@ import android.text.TextUtils; import android.util.Log; import androidx.annotation.VisibleForTesting; -import androidx.lifecycle.LifecycleObserver; -import androidx.lifecycle.OnLifecycleEvent; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.core.BasePreferenceController; -import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnDestroy; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; import com.android.settingslib.media.MediaOutputSliceConstants; @@ -45,7 +44,8 @@ import java.util.List; * {@link com.android.settings.notification.RemoteVolumeSeekBarPreference} **/ public class RemoteVolumeGroupController extends BasePreferenceController implements - Preference.OnPreferenceChangeListener, LifecycleObserver, LocalMediaManager.DeviceCallback { + Preference.OnPreferenceChangeListener, LifecycleObserver, OnDestroy, + LocalMediaManager.DeviceCallback { private static final String KEY_REMOTE_VOLUME_GROUP = "remote_media_group"; private static final String TAG = "RemoteVolumePrefCtr"; @@ -92,11 +92,7 @@ public class RemoteVolumeGroupController extends BasePreferenceController implem } } - /** - * onDestroy() - * {@link androidx.lifecycle.OnLifecycleEvent} - **/ - @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) + @Override public void onDestroy() { mLocalMediaManager.unregisterCallback(this); mLocalMediaManager.stopScan(); diff --git a/src/com/android/settings/notification/SoundSettings.java b/src/com/android/settings/notification/SoundSettings.java index 746a2df8ab2..9e7f3ed5489 100644 --- a/src/com/android/settings/notification/SoundSettings.java +++ b/src/com/android/settings/notification/SoundSettings.java @@ -190,7 +190,6 @@ public class SoundSettings extends DashboardFragment implements OnActivityResult controller.setCallback(mVolumeCallback); getSettingsLifecycle().addObserver(controller); } - getSettingsLifecycle().addObserver(use(RemoteVolumeGroupController.class)); } // === Volumes === From a87a7009b0e1d0bc8ae04a780fd013eba5a54d1c Mon Sep 17 00:00:00 2001 From: Peter_Liang Date: Thu, 7 May 2020 15:31:22 +0800 Subject: [PATCH 09/14] Refines the animated image to fit the screen display. Root cause: The width and height of animated image are wrap_content. It causes the gif or drawable which are non-transparent background to have a little weird display under dark mode. Next: 1. Revise the width from wrap_content to match_parent, and add the fit center scale type. 2. Correct the function usage Bug: 155946967 Test: manual test Change-Id: I7470efb945a2b3bedb695e1b6f34475c6f190f9d --- res/layout/preference_animated_image.xml | 3 ++- .../settings/accessibility/AnimatedImagePreference.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/res/layout/preference_animated_image.xml b/res/layout/preference_animated_image.xml index 305b03630e1..e7d9b52518a 100644 --- a/res/layout/preference_animated_image.xml +++ b/res/layout/preference_animated_image.xml @@ -24,9 +24,10 @@ diff --git a/src/com/android/settings/accessibility/AnimatedImagePreference.java b/src/com/android/settings/accessibility/AnimatedImagePreference.java index 61e439f6ca5..2ca13f33fe2 100644 --- a/src/com/android/settings/accessibility/AnimatedImagePreference.java +++ b/src/com/android/settings/accessibility/AnimatedImagePreference.java @@ -60,7 +60,7 @@ public class AnimatedImagePreference extends Preference { } if (mMaxHeight > -1) { - imageView.setMaxWidth(mMaxHeight); + imageView.setMaxHeight(mMaxHeight); } } From 833fae0387703c07dcdcf4ee9940a2bee5268dd5 Mon Sep 17 00:00:00 2001 From: Peter_Liang Date: Thu, 7 May 2020 16:14:15 +0800 Subject: [PATCH 10/14] Revises the strings to meet the the activity-based accessibility feature. Bug: 155249850 Test: manual test Change-Id: I4c493fb6a0123be87b17d392853d3ae9f3351879 --- res/values/strings.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 6897c656dea..a5e3e725dcf 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4953,19 +4953,19 @@ Use new accessibility gesture - To turn this feature on or off, tap the accessibility button %s on the bottom of your screen.\n\nTo switch between features, touch & hold the accessibility button. + To use this feature, tap the accessibility button %s on the bottom of your screen.\n\nTo switch between features, touch & hold the accessibility button. - To turn this feature on or off, press & hold both volume keys. + To use this feature, press & hold both volume keys. To start and stop magnification, triple-tap anywhere on your screen. - To turn this feature on or off, swipe up from the bottom of the screen with 2 fingers.\n\nTo switch between features, swipe up with 2 fingers and hold. + To use this feature, swipe up from the bottom of the screen with 2 fingers.\n\nTo switch between features, swipe up with 2 fingers and hold. - To turn this feature on or off, swipe up from the bottom of the screen with 3 fingers.\n\nTo switch between features, swipe up with 3 fingers and hold. + To use this feature, swipe up from the bottom of the screen with 3 fingers.\n\nTo switch between features, swipe up with 3 fingers and hold. - To turn an accessibility feature on or off, swipe up from the bottom of the screen with 2 fingers.\n\nTo switch between features, swipe up with 2 fingers and hold. + To use an accessibility feature, swipe up from the bottom of the screen with 2 fingers.\n\nTo switch between features, swipe up with 2 fingers and hold. - To turn an accessibility feature on or off, swipe up from the bottom of the screen with 3 fingers.\n\nTo switch between features, swipe up with 3 fingers and hold. + To use an accessibility feature, swipe up from the bottom of the screen with 3 fingers.\n\nTo switch between features, swipe up with 3 fingers and hold. Got it From a2f5497d69030110c6e99e04bc22112d2cdf3702 Mon Sep 17 00:00:00 2001 From: Peter_Liang Date: Thu, 7 May 2020 17:15:15 +0800 Subject: [PATCH 11/14] Correct the string relate to volume shortcut keys. Bug: 155948561 Test: manual test Change-Id: I1e7a78fd1feeb87ee5f82157906137c4e169edeb --- res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 6897c656dea..9b43f3978b1 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4945,7 +4945,7 @@ Use accessibility button to open - Hold volume key to open + Hold volume keys to open Triple tap screen to open From fff8c56de2866198d202c28a91484c49ef61137e Mon Sep 17 00:00:00 2001 From: Edgar Wang Date: Thu, 7 May 2020 08:44:12 +0000 Subject: [PATCH 12/14] Revert "Improve multi user settings screen" This reverts commit d19dc306ddbb0289063acf860d78a7c194b7a6d6. Reason for revert: this CL made Settings the test cases called shadowUserManager.hasUserRestriction() https://sponge.corp.google.com/target?show=FAILED&sortBy=STATUS&id=07150153-ccb7-4215-a9f8-eed31e44c66f&target=RunSettingsRoboTests1-test-output Bug: 142798722 Change-Id: I3937a07ee35a472cadd6db8cd0177e08e63516a4 --- res/drawable/ic_add_40dp.xml | 36 -- res/drawable/ic_phone.xml | 29 - res/drawable/ic_swap.xml | 25 - ...stricted_preference_user_delete_widget.xml | 60 ++ res/layout/user_info_header.xml | 39 +- res/xml/user_details_settings.xml | 7 +- res/xml/user_settings.xml | 7 +- .../users/AppRestrictionsFragment.java | 8 - .../users/RestrictedProfileSettings.java | 33 +- .../settings/users/UserDetailsSettings.java | 252 +++----- .../settings/users/UserPreference.java | 79 ++- .../android/settings/users/UserSettings.java | 256 ++++---- .../testutils/shadow/ShadowUserManager.java | 50 +- .../users/UserDetailsSettingsTest.java | 466 -------------- .../settings/users/UserPreferenceTest.java | 24 +- .../settings/users/UserSettingsTest.java | 598 ++++-------------- 16 files changed, 507 insertions(+), 1462 deletions(-) delete mode 100644 res/drawable/ic_add_40dp.xml delete mode 100644 res/drawable/ic_phone.xml delete mode 100644 res/drawable/ic_swap.xml create mode 100644 res/layout/restricted_preference_user_delete_widget.xml delete mode 100644 tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java diff --git a/res/drawable/ic_add_40dp.xml b/res/drawable/ic_add_40dp.xml deleted file mode 100644 index 7245823749b..00000000000 --- a/res/drawable/ic_add_40dp.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - diff --git a/res/drawable/ic_phone.xml b/res/drawable/ic_phone.xml deleted file mode 100644 index 28f47fd3486..00000000000 --- a/res/drawable/ic_phone.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - \ No newline at end of file diff --git a/res/drawable/ic_swap.xml b/res/drawable/ic_swap.xml deleted file mode 100644 index 1c43d974428..00000000000 --- a/res/drawable/ic_swap.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - diff --git a/res/layout/restricted_preference_user_delete_widget.xml b/res/layout/restricted_preference_user_delete_widget.xml new file mode 100644 index 00000000000..71f1dd7c8bf --- /dev/null +++ b/res/layout/restricted_preference_user_delete_widget.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/user_info_header.xml b/res/layout/user_info_header.xml index bfdf3fc7324..5135e0e2d27 100644 --- a/res/layout/user_info_header.xml +++ b/res/layout/user_info_header.xml @@ -15,7 +15,6 @@ --> + android:layout_marginEnd="@*android:dimen/preference_item_padding_inner" + /> @@ -89,40 +88,6 @@ android:layout_gravity="center" android:background="?android:attr/selectableItemBackground" /> - - - - - - - - - diff --git a/res/xml/user_details_settings.xml b/res/xml/user_details_settings.xml index d336395d02a..09154c483d7 100644 --- a/res/xml/user_details_settings.xml +++ b/res/xml/user_details_settings.xml @@ -17,17 +17,12 @@ - - diff --git a/res/xml/user_settings.xml b/res/xml/user_settings.xml index 7726a18ea94..eb8803b73ee 100644 --- a/res/xml/user_settings.xml +++ b/res/xml/user_settings.xml @@ -27,15 +27,10 @@ settings:searchable="false"> - - switchUser()); - // This is going to bind the preferences. super.onActivityCreated(savedInstanceState); } @@ -92,6 +80,7 @@ public class RestrictedProfileSettings extends AppRestrictionsFragment @Override public void onResume() { super.onResume(); + // Check if user still exists UserInfo info = Utils.getExistingUser(mUserManager, mUser); if (info == null) { @@ -100,16 +89,6 @@ public class RestrictedProfileSettings extends AppRestrictionsFragment ((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); - } } } @@ -179,16 +158,6 @@ public class RestrictedProfileSettings extends AppRestrictionsFragment }); } - 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); diff --git a/src/com/android/settings/users/UserDetailsSettings.java b/src/com/android/settings/users/UserDetailsSettings.java index 2696ddc1319..371c152e157 100644 --- a/src/com/android/settings/users/UserDetailsSettings.java +++ b/src/com/android/settings/users/UserDetailsSettings.java @@ -16,63 +16,55 @@ package com.android.settings.users; -import static android.os.UserHandle.USER_NULL; - -import android.app.ActivityManager; import android.app.Dialog; import android.app.settings.SettingsEnums; import android.content.Context; +import android.content.DialogInterface; import android.content.pm.UserInfo; import android.os.Bundle; -import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; -import android.util.Log; -import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.SwitchPreference; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; -import com.android.settings.Utils; -import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtilsInternal; import java.util.List; /** - * Settings screen for configuring, deleting or switching to a specific user. - * It is shown when you tap on a user in the user management (UserSettings) screen. + * Settings screen for configuring a specific user. It can contain user restrictions + * and deletion controls. It is shown when you tap on the settings icon in the + * user management (UserSettings) screen. * * Arguments to this fragment must include the userId of the user (in EXTRA_USER_ID) for whom - * to display controls. + * to display controls, or should contain the EXTRA_USER_GUEST = true. */ public class UserDetailsSettings extends SettingsPreferenceFragment implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener { private static final String TAG = UserDetailsSettings.class.getSimpleName(); - private static final String KEY_SWITCH_USER = "switch_user"; private static final String KEY_ENABLE_TELEPHONY = "enable_calling"; private static final String KEY_REMOVE_USER = "remove_user"; /** Integer extra containing the userId to manage */ static final String EXTRA_USER_ID = "user_id"; + /** Boolean extra to indicate guest preferences */ + static final String EXTRA_USER_GUEST = "guest_user"; private static final int DIALOG_CONFIRM_REMOVE = 1; private static final int DIALOG_CONFIRM_ENABLE_CALLING = 2; private static final int DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS = 3; private UserManager mUserManager; - @VisibleForTesting - Preference mSwitchUserPref; private SwitchPreference mPhonePref; - @VisibleForTesting - Preference mRemoveUserPref; + private Preference mRemoveUserPref; - @VisibleForTesting - UserInfo mUserInfo; + private UserInfo mUserInfo; + private boolean mGuestUser; private Bundle mDefaultGuestRestrictions; @Override @@ -86,28 +78,46 @@ public class UserDetailsSettings extends SettingsPreferenceFragment final Context context = getActivity(); mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); + addPreferencesFromResource(R.xml.user_details_settings); + mPhonePref = (SwitchPreference) findPreference(KEY_ENABLE_TELEPHONY); + mRemoveUserPref = findPreference(KEY_REMOVE_USER); - initialize(context, getArguments()); - } + mGuestUser = getArguments().getBoolean(EXTRA_USER_GUEST, false); - @Override - public void onResume() { - super.onResume(); - mSwitchUserPref.setEnabled(canSwitchUserNow()); + if (!mGuestUser) { + // Regular user. Get the user id from the caller. + final int userId = getArguments().getInt(EXTRA_USER_ID, -1); + if (userId == -1) { + throw new RuntimeException("Arguments to this fragment must contain the user id"); + } + mUserInfo = mUserManager.getUserInfo(userId); + mPhonePref.setChecked(!mUserManager.hasUserRestriction( + UserManager.DISALLOW_OUTGOING_CALLS, new UserHandle(userId))); + mRemoveUserPref.setOnPreferenceClickListener(this); + } else { + // These are not for an existing user, just general Guest settings. + removePreference(KEY_REMOVE_USER); + // Default title is for calling and SMS. Change to calling-only here + mPhonePref.setTitle(R.string.user_enable_calling); + mDefaultGuestRestrictions = mUserManager.getDefaultGuestRestrictions(); + mPhonePref.setChecked( + !mDefaultGuestRestrictions.getBoolean(UserManager.DISALLOW_OUTGOING_CALLS)); + } + if (RestrictedLockUtilsInternal.hasBaseUserRestriction(context, + UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId())) { + removePreference(KEY_REMOVE_USER); + } + mPhonePref.setOnPreferenceChangeListener(this); } @Override public boolean onPreferenceClick(Preference preference) { if (preference == mRemoveUserPref) { - if (canDeleteUser()) { - showDialog(DIALOG_CONFIRM_REMOVE); - } - return true; - } else if (preference == mSwitchUserPref) { - if (canSwitchUserNow()) { - switchUser(); + if (!mUserManager.isAdminUser()) { + throw new RuntimeException("Only admins can remove a user"); } + showDialog(DIALOG_CONFIRM_REMOVE); return true; } return false; @@ -116,7 +126,7 @@ public class UserDetailsSettings extends SettingsPreferenceFragment @Override public boolean onPreferenceChange(Preference preference, Object newValue) { if (Boolean.TRUE.equals(newValue)) { - showDialog(mUserInfo.isGuest() ? DIALOG_CONFIRM_ENABLE_CALLING + showDialog(mGuestUser ? DIALOG_CONFIRM_ENABLE_CALLING : DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS); return false; } @@ -124,135 +134,9 @@ public class UserDetailsSettings extends SettingsPreferenceFragment return true; } - @Override - public int getDialogMetricsCategory(int dialogId) { - switch (dialogId) { - case DIALOG_CONFIRM_REMOVE: - return SettingsEnums.DIALOG_USER_REMOVE; - case DIALOG_CONFIRM_ENABLE_CALLING: - return SettingsEnums.DIALOG_USER_ENABLE_CALLING; - case DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS: - return SettingsEnums.DIALOG_USER_ENABLE_CALLING_AND_SMS; - default: - return 0; - } - } - - @Override - public Dialog onCreateDialog(int dialogId) { - Context context = getActivity(); - if (context == null) { - return null; - } - switch (dialogId) { - case DIALOG_CONFIRM_REMOVE: - return UserDialogs.createRemoveDialog(getActivity(), mUserInfo.id, - (dialog, which) -> removeUser()); - case DIALOG_CONFIRM_ENABLE_CALLING: - return UserDialogs.createEnablePhoneCallsDialog(getActivity(), - (dialog, which) -> enableCallsAndSms(true)); - case DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS: - return UserDialogs.createEnablePhoneCallsAndSmsDialog(getActivity(), - (dialog, which) -> enableCallsAndSms(true)); - } - throw new IllegalArgumentException("Unsupported dialogId " + dialogId); - } - - @VisibleForTesting - @Override - protected void showDialog(int dialogId) { - super.showDialog(dialogId); - } - - @VisibleForTesting - void initialize(Context context, Bundle arguments) { - int userId = arguments != null ? arguments.getInt(EXTRA_USER_ID, USER_NULL) : USER_NULL; - if (userId == USER_NULL) { - throw new IllegalStateException("Arguments to this fragment must contain the user id"); - } - mUserInfo = mUserManager.getUserInfo(userId); - - mSwitchUserPref = findPreference(KEY_SWITCH_USER); - mPhonePref = findPreference(KEY_ENABLE_TELEPHONY); - mRemoveUserPref = findPreference(KEY_REMOVE_USER); - - mSwitchUserPref.setTitle( - context.getString(com.android.settingslib.R.string.user_switch_to_user, - mUserInfo.name)); - mSwitchUserPref.setOnPreferenceClickListener(this); - - if (!mUserManager.isAdminUser()) { // non admin users can't remove users and allow calls - removePreference(KEY_ENABLE_TELEPHONY); - removePreference(KEY_REMOVE_USER); - } 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); - } else { - // 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); - mDefaultGuestRestrictions = mUserManager.getDefaultGuestRestrictions(); - mPhonePref.setChecked( - !mDefaultGuestRestrictions.getBoolean(UserManager.DISALLOW_OUTGOING_CALLS)); - mRemoveUserPref.setTitle(R.string.user_exit_guest_title); - } - if (RestrictedLockUtilsInternal.hasBaseUserRestriction(context, - UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId())) { - removePreference(KEY_REMOVE_USER); - } - - mRemoveUserPref.setOnPreferenceClickListener(this); - mPhonePref.setOnPreferenceChangeListener(this); - } - } - - @VisibleForTesting - boolean canDeleteUser() { - if (!mUserManager.isAdminUser()) { - return false; - } - - Context context = getActivity(); - if (context == null) { - return false; - } - - final RestrictedLockUtils.EnforcedAdmin removeDisallowedAdmin = - RestrictedLockUtilsInternal.checkIfRestrictionEnforced(context, - UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId()); - if (removeDisallowedAdmin != null) { - RestrictedLockUtils.sendShowAdminSupportDetailsIntent(context, - removeDisallowedAdmin); - return false; - } - return true; - } - - @VisibleForTesting - boolean canSwitchUserNow() { - return mUserManager.getUserSwitchability() == UserManager.SWITCHABILITY_STATUS_OK; - } - - @VisibleForTesting - void switchUser() { - try { - ActivityManager.getService().switchUser(mUserInfo.id); - } catch (RemoteException re) { - Log.e(TAG, "Error while switching to other user."); - } finally { - finishFragment(); - } - } - - private void enableCallsAndSms(boolean enabled) { + void enableCallsAndSms(boolean enabled) { mPhonePref.setChecked(enabled); - if (mUserInfo.isGuest()) { + if (mGuestUser) { mDefaultGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, !enabled); // SMS is always disabled for guest mDefaultGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true); @@ -262,7 +146,7 @@ public class UserDetailsSettings extends SettingsPreferenceFragment // TODO: Maybe setDefaultGuestRestrictions() can internally just set the restrictions // on any existing guest rather than do it here with multiple Binder calls. List users = mUserManager.getUsers(true); - for (UserInfo user : users) { + for (UserInfo user: users) { if (user.isGuest()) { UserHandle userHandle = UserHandle.of(user.id); for (String key : mDefaultGuestRestrictions.keySet()) { @@ -279,7 +163,51 @@ public class UserDetailsSettings extends SettingsPreferenceFragment } } - private void removeUser() { + @Override + public Dialog onCreateDialog(int dialogId) { + Context context = getActivity(); + if (context == null) return null; + switch (dialogId) { + case DIALOG_CONFIRM_REMOVE: + return UserDialogs.createRemoveDialog(getActivity(), mUserInfo.id, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + removeUser(); + } + }); + case DIALOG_CONFIRM_ENABLE_CALLING: + return UserDialogs.createEnablePhoneCallsDialog(getActivity(), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + enableCallsAndSms(true); + } + }); + case DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS: + return UserDialogs.createEnablePhoneCallsAndSmsDialog(getActivity(), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + enableCallsAndSms(true); + } + }); + } + throw new IllegalArgumentException("Unsupported dialogId " + dialogId); + } + + @Override + public int getDialogMetricsCategory(int dialogId) { + switch (dialogId) { + case DIALOG_CONFIRM_REMOVE: + return SettingsEnums.DIALOG_USER_REMOVE; + case DIALOG_CONFIRM_ENABLE_CALLING: + return SettingsEnums.DIALOG_USER_ENABLE_CALLING; + case DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS: + return SettingsEnums.DIALOG_USER_ENABLE_CALLING_AND_SMS; + default: + return 0; + } + } + + void removeUser() { mUserManager.removeUser(mUserInfo.id); finishFragment(); } diff --git a/src/com/android/settings/users/UserPreference.java b/src/com/android/settings/users/UserPreference.java index 0b78d787608..3603d44ea09 100644 --- a/src/com/android/settings/users/UserPreference.java +++ b/src/com/android/settings/users/UserPreference.java @@ -21,16 +21,18 @@ import android.graphics.drawable.Drawable; import android.os.UserHandle; import android.os.UserManager; import android.util.AttributeSet; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.ImageView; import androidx.preference.PreferenceViewHolder; +import com.android.settings.R; +import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.RestrictedPreference; import java.util.Comparator; -/** - * Preference for a user that appear on {@link UserSettings} screen. - */ public class UserPreference extends RestrictedPreference { private static final int ALPHA_ENABLED = 255; private static final int ALPHA_DISABLED = 102; @@ -42,7 +44,8 @@ public class UserPreference extends RestrictedPreference { if (p1 == null) { return -1; - } else if (p2 == null) { + } + else if (p2 == null) { return 1; } int sn1 = p1.getSerialNumber(); @@ -55,15 +58,26 @@ public class UserPreference extends RestrictedPreference { return 0; }; + private OnClickListener mDeleteClickListener; + private OnClickListener mSettingsClickListener; private int mSerialNumber = -1; private int mUserId = USERID_UNKNOWN; + static final int SETTINGS_ID = R.id.manage_user; + static final int DELETE_ID = R.id.trash_user; public UserPreference(Context context, AttributeSet attrs) { - this(context, attrs, USERID_UNKNOWN); + this(context, attrs, USERID_UNKNOWN, null, null); } - UserPreference(Context context, AttributeSet attrs, int userId) { + UserPreference(Context context, AttributeSet attrs, int userId, + OnClickListener settingsListener, + OnClickListener deleteListener) { super(context, attrs); + if (deleteListener != null || settingsListener != null) { + setWidgetLayoutResource(R.layout.restricted_preference_user_delete_widget); + } + mDeleteClickListener = deleteListener; + mSettingsClickListener = settingsListener; mUserId = userId; useAdminDisabledSummary(true); } @@ -78,13 +92,62 @@ public class UserPreference extends RestrictedPreference { @Override protected boolean shouldHideSecondTarget() { - return true; + if (isDisabledByAdmin()) { + // Disabled by admin, show no secondary target. + return true; + } + if (canDeleteUser()) { + // Need to show delete user target so don't hide. + return false; + } + // Hide if don't have advanced setting listener. + return mSettingsClickListener == null; } @Override public void onBindViewHolder(PreferenceViewHolder view) { super.onBindViewHolder(view); - dimIcon(isDisabledByAdmin()); + final boolean disabledByAdmin = isDisabledByAdmin(); + dimIcon(disabledByAdmin); + View userDeleteWidget = view.findViewById(R.id.user_delete_widget); + if (userDeleteWidget != null) { + userDeleteWidget.setVisibility(disabledByAdmin ? View.GONE : View.VISIBLE); + } + if (!disabledByAdmin) { + View deleteDividerView = view.findViewById(R.id.divider_delete); + View manageDividerView = view.findViewById(R.id.divider_manage); + View deleteView = view.findViewById(R.id.trash_user); + if (deleteView != null) { + if (canDeleteUser()) { + deleteView.setVisibility(View.VISIBLE); + deleteDividerView.setVisibility(View.VISIBLE); + deleteView.setOnClickListener(mDeleteClickListener); + deleteView.setTag(this); + } else { + deleteView.setVisibility(View.GONE); + deleteDividerView.setVisibility(View.GONE); + } + } + ImageView manageView = (ImageView) view.findViewById(R.id.manage_user); + if (manageView != null) { + if (mSettingsClickListener != null) { + manageView.setVisibility(View.VISIBLE); + manageDividerView.setVisibility(mDeleteClickListener == null + ? View.VISIBLE : View.GONE); + manageView.setOnClickListener(mSettingsClickListener); + manageView.setTag(this); + } else { + manageView.setVisibility(View.GONE); + manageDividerView.setVisibility(View.GONE); + } + } + } + } + + private boolean canDeleteUser() { + return mDeleteClickListener != null + && !RestrictedLockUtilsInternal.hasBaseUserRestriction(getContext(), + UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId()); } private int getSerialNumber() { diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java index 7d4ab5d5312..38ef199c5cb 100644 --- a/src/com/android/settings/users/UserSettings.java +++ b/src/com/android/settings/users/UserSettings.java @@ -48,6 +48,7 @@ import android.util.SparseArray; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.view.View; import android.widget.SimpleAdapter; import androidx.annotation.VisibleForTesting; @@ -68,6 +69,7 @@ import com.android.settings.password.ChooseLockGeneric; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.widget.SwitchBar; import com.android.settings.widget.SwitchBarController; +import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.RestrictedPreference; @@ -87,14 +89,15 @@ import java.util.Random; /** * Screen that manages the list of users on the device. - * Secondary users and a guest user can be created if there is no restriction. + * Guest user is an always visible entry, even if the guest is not currently + * active/created. It is meant for controlling properties of a guest user. * - * The first user in the list is always the current user. + * The first one is always the current user. * Owner is the primary user. */ @SearchIndexable public class UserSettings extends SettingsPreferenceFragment - implements Preference.OnPreferenceClickListener, + implements Preference.OnPreferenceClickListener, View.OnClickListener, MultiUserSwitchBarController.OnMultiUserSwitchChangedListener, DialogInterface.OnDismissListener { @@ -108,7 +111,6 @@ public class UserSettings extends SettingsPreferenceFragment private static final String KEY_USER_LIST = "user_list"; private static final String KEY_USER_ME = "user_me"; private static final String KEY_USER_GUEST = "user_guest"; - private static final String KEY_ADD_GUEST = "guest_add"; private static final String KEY_ADD_USER = "user_add"; private static final String KEY_ADD_USER_WHEN_LOCKED = "user_settings_add_users_when_locked"; private static final String KEY_MULTIUSER_FOOTER = "multiuser_footer"; @@ -154,11 +156,7 @@ public class UserSettings extends SettingsPreferenceFragment @VisibleForTesting UserPreference mMePreference; @VisibleForTesting - RestrictedPreference mAddGuest; - @VisibleForTesting RestrictedPreference mAddUser; - @VisibleForTesting - SparseArray mUserIcons = new SparseArray<>(); private int mRemovingUserId = -1; private int mAddedUserId = 0; private boolean mAddingUser; @@ -167,6 +165,7 @@ public class UserSettings extends SettingsPreferenceFragment private boolean mShouldUpdateUserList = true; private final Object mUserLock = new Object(); private UserManager mUserManager; + private SparseArray mUserIcons = new SparseArray<>(); private static SparseArray sDarkDefaultUserBitmapCache = new SparseArray<>(); private MultiUserSwitchBarController mSwitchBarController; @@ -272,17 +271,15 @@ public class UserSettings extends SettingsPreferenceFragment final int myUserId = UserHandle.myUserId(); mUserListCategory = (PreferenceGroup) findPreference(KEY_USER_LIST); - mMePreference = new UserPreference(getPrefContext(), null /* attrs */, myUserId); + mMePreference = new UserPreference(getPrefContext(), null /* attrs */, myUserId, + null /* settings icon handler */, + null /* delete icon handler */); mMePreference.setKey(KEY_USER_ME); mMePreference.setOnPreferenceClickListener(this); if (mUserCaps.mIsAdmin) { mMePreference.setSummary(R.string.user_admin); } - - mAddGuest = findPreference(KEY_ADD_GUEST); - mAddGuest.setOnPreferenceClickListener(this); - - mAddUser = findPreference(KEY_ADD_USER); + mAddUser = (RestrictedPreference) findPreference(KEY_ADD_USER); if (!mUserCaps.mCanAddRestrictedProfile) { // Label should only mention adding a "user", not a "profile" mAddUser.setTitle(R.string.user_add_user_menu); @@ -347,7 +344,8 @@ public class UserSettings extends SettingsPreferenceFragment @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { int pos = 0; - if (!mUserCaps.mIsAdmin && canSwitchUserNow()) { + final boolean canSwitchUsers = mUserManager.canSwitchUsers(); + if (!mUserCaps.mIsAdmin && canSwitchUsers) { String nickname = mUserManager.getUserName(); MenuItem removeThisUser = menu.add(0, MENU_REMOVE_USER, pos++, getResources().getString(R.string.user_remove_user_menu, nickname)); @@ -388,13 +386,10 @@ public class UserSettings extends SettingsPreferenceFragment * Loads profile information for the current user. */ private void loadProfile() { - if (isCurrentUserGuest()) { + if (mUserCaps.mIsGuest) { // No need to load profile information mMePreference.setIcon(getEncircledDefaultIcon()); mMePreference.setTitle(R.string.user_exit_guest_title); - mMePreference.setSelectable(true); - // removing a guest will result in switching back to the admin user - mMePreference.setEnabled(canSwitchUserNow()); return; } @@ -417,9 +412,7 @@ public class UserSettings extends SettingsPreferenceFragment } private void finishLoadProfile(String profileName) { - if (getActivity() == null) { - return; - } + if (getActivity() == null) return; mMePreference.setTitle(getString(R.string.user_you, profileName)); int myUserId = UserHandle.myUserId(); Bitmap b = mUserManager.getUserIcon(myUserId); @@ -484,28 +477,38 @@ public class UserSettings extends SettingsPreferenceFragment private void onManageUserClicked(int userId, boolean newUser) { mAddingUser = false; - UserInfo userInfo = mUserManager.getUserInfo(userId); - if (userInfo.isRestricted() && mUserCaps.mIsAdmin) { + if (userId == UserPreference.USERID_GUEST_DEFAULTS) { + Bundle extras = new Bundle(); + extras.putBoolean(UserDetailsSettings.EXTRA_USER_GUEST, true); + new SubSettingLauncher(getContext()) + .setDestination(UserDetailsSettings.class.getName()) + .setArguments(extras) + .setTitleRes(R.string.user_guest) + .setSourceMetricsCategory(getMetricsCategory()) + .launch(); + return; + } + UserInfo info = mUserManager.getUserInfo(userId); + if (info.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()) { + } else if (info.id == UserHandle.myUserId()) { // Jump to owner info panel OwnerInfoSettings.show(this); - } else { - Bundle extras = new Bundle(); + } else if (mUserCaps.mIsAdmin) { + final Bundle extras = new Bundle(); extras.putInt(UserDetailsSettings.EXTRA_USER_ID, userId); new SubSettingLauncher(getContext()) .setDestination(UserDetailsSettings.class.getName()) .setArguments(extras) - .setTitleText(userInfo.name) + .setTitleText(info.name) .setSourceMetricsCategory(getMetricsCategory()) .launch(); } @@ -535,9 +538,7 @@ public class UserSettings extends SettingsPreferenceFragment @Override public Dialog onCreateDialog(int dialogId) { Context context = getActivity(); - if (context == null) { - return null; - } + if (context == null) return null; switch (dialogId) { case DIALOG_CONFIRM_REMOVE: { Dialog dlg = @@ -810,7 +811,7 @@ public class UserSettings extends SettingsPreferenceFragment } private void removeThisUser() { - if (!canSwitchUserNow()) { + if (!mUserManager.canSwitchUsers()) { Log.w(TAG, "Cannot remove current user when switching is disabled"); return; } @@ -881,14 +882,10 @@ public class UserSettings extends SettingsPreferenceFragment } private void switchUserNow(int userId) { - if (!canSwitchUserNow()) { - return; - } - try { ActivityManager.getService().switchUser(userId); } catch (RemoteException re) { - Log.e(TAG, "Error while switching to other user."); + // Nothing to do } } @@ -897,7 +894,7 @@ public class UserSettings extends SettingsPreferenceFragment */ private void exitGuest() { // Just to be safe - if (!isCurrentUserGuest()) { + if (!mUserCaps.mIsGuest) { return; } removeThisUser(); @@ -911,12 +908,12 @@ public class UserSettings extends SettingsPreferenceFragment } final List users = mUserManager.getUsers(true); + final boolean voiceCapable = Utils.isVoiceCapable(context); final ArrayList missingIcons = new ArrayList<>(); final ArrayList userPreferences = new ArrayList<>(); + int guestId = UserPreference.USERID_GUEST_DEFAULTS; userPreferences.add(mMePreference); - boolean canOpenUserDetails = - mUserCaps.mIsAdmin || (canSwitchUserNow() && !mUserCaps.mDisallowSwitchUser); for (UserInfo user : users) { if (!user.supportsSwitchToByUser()) { // Only users that can be switched to should show up here. @@ -927,38 +924,37 @@ public class UserSettings extends SettingsPreferenceFragment if (user.id == UserHandle.myUserId()) { pref = mMePreference; } else if (user.isGuest()) { - pref = new UserPreference(getPrefContext(), null, user.id); - pref.setTitle(R.string.user_guest); - pref.setIcon(getEncircledDefaultIcon()); - pref.setKey(KEY_USER_GUEST); - userPreferences.add(pref); - pref.setEnabled(canOpenUserDetails); - pref.setSelectable(true); - - if (mUserCaps.mDisallowSwitchUser) { - pref.setDisabledByAdmin(RestrictedLockUtilsInternal.getDeviceOwner(context)); - } else { - pref.setDisabledByAdmin(null); - } - pref.setOnPreferenceClickListener(this); + // Skip over Guest. We add generic Guest settings after this loop + guestId = user.id; + continue; } else { - pref = new UserPreference(getPrefContext(), null, user.id); + // With Telephony: + // Secondary user: Settings + // Guest: Settings + // Restricted Profile: There is no Restricted Profile + // Without Telephony: + // Secondary user: Delete + // Guest: Nothing + // Restricted Profile: Settings + final boolean showSettings = mUserCaps.mIsAdmin + && (voiceCapable || user.isRestricted()); + final boolean showDelete = mUserCaps.mIsAdmin + && (!voiceCapable && !user.isRestricted() && !user.isGuest()); + pref = new UserPreference(getPrefContext(), null, user.id, + showSettings ? this : null, + showDelete ? this : null); pref.setKey("id=" + user.id); userPreferences.add(pref); if (user.isAdmin()) { pref.setSummary(R.string.user_admin); } pref.setTitle(user.name); - pref.setOnPreferenceClickListener(this); - pref.setEnabled(canOpenUserDetails); - pref.setSelectable(true); + pref.setSelectable(false); } if (pref == null) { continue; } - if (user.id != UserHandle.myUserId() && !user.isGuest() && !user.isInitialized()) { - // sometimes after creating a guest the initialized flag isn't immediately set - // and we don't want to show "Not set up" summary for them + if (!isInitialized(user)) { if (user.isRestricted()) { pref.setSummary(R.string.user_summary_restricted_not_set_up); } else { @@ -966,7 +962,10 @@ public class UserSettings extends SettingsPreferenceFragment } // Disallow setting up user which results in user switching when the restriction is // set. - pref.setEnabled(!mUserCaps.mDisallowSwitchUser && canSwitchUserNow()); + if (!mUserCaps.mDisallowSwitchUser) { + pref.setOnPreferenceClickListener(this); + pref.setSelectable(mUserManager.canSwitchUsers()); + } } else if (user.isRestricted()) { pref.setSummary(R.string.user_summary_restricted_profile); } @@ -987,13 +986,53 @@ public class UserSettings extends SettingsPreferenceFragment // Add a temporary entry for the user being created if (mAddingUser) { UserPreference pref = new UserPreference(getPrefContext(), null, - UserPreference.USERID_UNKNOWN); + UserPreference.USERID_UNKNOWN, null, null); pref.setEnabled(false); pref.setTitle(mAddingUserName); pref.setIcon(getEncircledDefaultIcon()); userPreferences.add(pref); } + // Check if Guest tile should be added. + if (!mUserCaps.mIsGuest && (mUserCaps.mCanAddGuest || + mUserCaps.mDisallowAddUserSetByAdmin)) { + // Add a virtual Guest user for guest defaults + UserPreference pref = new UserPreference(getPrefContext(), null, + UserPreference.USERID_GUEST_DEFAULTS, + mUserCaps.mIsAdmin && voiceCapable ? this : null /* settings icon handler */, + null /* delete icon handler */); + pref.setTitle(R.string.user_guest); + pref.setIcon(getEncircledDefaultIcon()); + pref.setKey(KEY_USER_GUEST); + userPreferences.add(pref); + if (mUserCaps.mDisallowAddUser) { + pref.setDisabledByAdmin(mUserCaps.mEnforcedAdmin); + } else if (mUserCaps.mDisallowSwitchUser) { + pref.setDisabledByAdmin(RestrictedLockUtilsInternal.getDeviceOwner(context)); + } else { + pref.setDisabledByAdmin(null); + } + if (!mUserManager.canSwitchUsers()) { + pref.setSelectable(false); + } + int finalGuestId = guestId; + pref.setOnPreferenceClickListener(preference -> { + int id = finalGuestId; + if (id == UserPreference.USERID_GUEST_DEFAULTS) { + UserInfo guest = mUserManager.createGuest( + getContext(), preference.getTitle().toString()); + if (guest != null) { + id = guest.id; + } + } + try { + ActivityManager.getService().switchUser(id); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return true; + }); + } // Sort list of users by serialNum Collections.sort(userPreferences, UserPreference.SERIAL_NUMBER_COMPARATOR); @@ -1025,7 +1064,6 @@ public class UserSettings extends SettingsPreferenceFragment mMultiUserFooterPreferenceController.updateState(multiUserFooterPrefence); mUserListCategory.setVisible(mUserCaps.mUserSwitcherEnabled); - updateAddGuest(context, users.stream().anyMatch(UserInfo::isGuest)); updateAddUser(context); if (!mUserCaps.mUserSwitcherEnabled) { @@ -1039,38 +1077,15 @@ public class UserSettings extends SettingsPreferenceFragment } - private boolean isCurrentUserGuest() { - return mUserCaps.mIsGuest; - } - - private boolean canSwitchUserNow() { - return mUserManager.getUserSwitchability() == UserManager.SWITCHABILITY_STATUS_OK; - } - - private void updateAddGuest(Context context, boolean isGuestAlreadyCreated) { - if (!isGuestAlreadyCreated && mUserCaps.mCanAddGuest - && WizardManagerHelper.isDeviceProvisioned(context) - && mUserCaps.mUserSwitcherEnabled) { - mAddGuest.setVisible(true); - mAddGuest.setIcon(getEncircledDefaultIcon()); - mAddGuest.setEnabled(canSwitchUserNow()); - mAddGuest.setSelectable(true); - } else { - mAddGuest.setVisible(false); - } - } - private void updateAddUser(Context context) { if ((mUserCaps.mCanAddUser || mUserCaps.mDisallowAddUserSetByAdmin) && WizardManagerHelper.isDeviceProvisioned(context) && mUserCaps.mUserSwitcherEnabled) { mAddUser.setVisible(true); - mAddUser.setSelectable(true); - final boolean canAddMoreUsers = mUserManager.canAddMoreUsers(); - mAddUser.setEnabled(canAddMoreUsers && !mAddingUser && canSwitchUserNow()); - if (!canAddMoreUsers) { - mAddUser.setSummary( - getString(R.string.user_add_max_count, getRealUsersCount())); + final boolean moreUsers = mUserManager.canAddMoreUsers(); + mAddUser.setEnabled(moreUsers && !mAddingUser && mUserManager.canSwitchUsers()); + if (!moreUsers) { + mAddUser.setSummary(getString(R.string.user_add_max_count, getMaxRealUsers())); } else { mAddUser.setSummary(null); } @@ -1083,15 +1098,18 @@ public class UserSettings extends SettingsPreferenceFragment } } - /** - * @return number of non-guest non-managed users - */ - @VisibleForTesting - int getRealUsersCount() { - return (int) mUserManager.getUsers() - .stream() - .filter(user -> !user.isGuest() && !user.isProfile()) - .count(); + private int getMaxRealUsers() { + // guest is not counted against getMaxSupportedUsers() number + final int maxUsersAndGuest = UserManager.getMaxSupportedUsers() + 1; + final List users = mUserManager.getUsers(); + // managed profiles are counted against getMaxSupportedUsers() + int managedProfiles = 0; + for (UserInfo user : users) { + if (user.isManagedProfile()) { + managedProfiles++; + } + } + return maxUsersAndGuest - managedProfiles; } private void loadIconsAsync(List missingIcons) { @@ -1133,12 +1151,12 @@ public class UserSettings extends SettingsPreferenceFragment @Override public boolean onPreferenceClick(Preference pref) { if (pref == mMePreference) { - if (isCurrentUserGuest()) { + if (mUserCaps.mIsGuest) { 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()) { + if (mUserManager.isLinkedUser()) { onManageUserClicked(UserHandle.myUserId(), false); } else { showDialog(DIALOG_USER_PROFILE_EDITOR); @@ -1147,11 +1165,9 @@ public class UserSettings extends SettingsPreferenceFragment int userId = ((UserPreference) pref).getUserId(); // Get the latest status of the user UserInfo user = mUserManager.getUserInfo(userId); - if (!user.isInitialized()) { + if (!isInitialized(user)) { mHandler.sendMessage(mHandler.obtainMessage( MESSAGE_SETUP_USER, user.id, user.serialNumber)); - } else { - onManageUserClicked(userId, false); } } else if (pref == mAddUser) { // If we allow both types, show a picker, otherwise directly go to @@ -1161,19 +1177,39 @@ public class UserSettings extends SettingsPreferenceFragment } else { onAddUserClicked(USER_TYPE_USER); } - } else if (pref == mAddGuest) { - UserInfo guest = mUserManager.createGuest( - getContext(), getString(com.android.settingslib.R.string.user_guest)); - switchUserNow(guest.id); } return false; } + private boolean isInitialized(UserInfo user) { + return (user.flags & UserInfo.FLAG_INITIALIZED) != 0; + } + private Drawable encircle(Bitmap icon) { Drawable circled = CircleFramedDrawable.getInstance(getActivity(), icon); return circled; } + @Override + public void onClick(View v) { + if (v.getTag() instanceof UserPreference) { + int userId = ((UserPreference) v.getTag()).getUserId(); + if (v.getId() == UserPreference.DELETE_ID) { + final EnforcedAdmin removeDisallowedAdmin = + RestrictedLockUtilsInternal.checkIfRestrictionEnforced(getContext(), + UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId()); + if (removeDisallowedAdmin != null) { + RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(), + removeDisallowedAdmin); + } else { + onRemoveUserClicked(userId); + } + } else if (v.getId() == UserPreference.SETTINGS_ID) { + onManageUserClicked(userId, false); + } + } + } + @Override public void onDismiss(DialogInterface dialog) { synchronized (mUserLock) { diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java index 673c0fe2689..659c5de770c 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java @@ -18,7 +18,6 @@ package com.android.settings.testutils.shadow; import android.annotation.UserIdInt; import android.content.pm.UserInfo; -import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; import android.os.UserManager.EnforcingUser; @@ -44,18 +43,13 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager private static boolean sIsSupportsMultipleUsers; - private final List mBaseRestrictions = new ArrayList<>(); - private final List mUserRestrictions = new ArrayList<>(); - private final List mGuestRestrictions = new ArrayList<>(); + private final List mRestrictions = new ArrayList<>(); private final Map> mRestrictionSources = new HashMap<>(); private final List mUserProfileInfos = new ArrayList<>(); private final Set mManagedProfiles = new HashSet<>(); private boolean mIsQuietModeEnabled = false; private int[] profileIdsForUser = new int[0]; private boolean mUserSwitchEnabled; - - private @UserManager.UserSwitchabilityResult int mSwitchabilityStatus = - UserManager.SWITCHABILITY_STATUS_OK; private final Map mSameProfileGroupIds = Maps.newHashMap(); public void addProfile(UserInfo userInfo) { @@ -88,32 +82,11 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager @Implementation protected boolean hasBaseUserRestriction(String restrictionKey, UserHandle userHandle) { - return mBaseRestrictions.contains(restrictionKey); + return mRestrictions.contains(restrictionKey); } public void addBaseUserRestriction(String restriction) { - mBaseRestrictions.add(restriction); - } - - @Implementation - protected boolean hasUserRestriction(@UserManager.UserRestrictionKey String restrictionKey, - UserHandle userHandle) { - return mUserRestrictions.contains(restrictionKey); - } - - public void addUserRestriction(String restriction) { - mUserRestrictions.add(restriction); - } - - @Implementation - protected Bundle getDefaultGuestRestrictions() { - Bundle bundle = new Bundle(); - mGuestRestrictions.forEach(restriction -> bundle.putBoolean(restriction, true)); - return bundle; - } - - public void addGuestUserRestriction(String restriction) { - mGuestRestrictions.add(restriction); + mRestrictions.add(restriction); } public static ShadowUserManager getShadow() { @@ -193,21 +166,4 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager public void setSupportsMultipleUsers(boolean supports) { sIsSupportsMultipleUsers = supports; } - - @Implementation - protected UserInfo getUserInfo(@UserIdInt int userId) { - return mUserProfileInfos.stream() - .filter(userInfo -> userInfo.id == userId) - .findFirst() - .orElse(super.getUserInfo(userId)); - } - - @Implementation - protected @UserManager.UserSwitchabilityResult int getUserSwitchability() { - return mSwitchabilityStatus; - } - - public void setSwitchabilityStatus(@UserManager.UserSwitchabilityResult int newStatus) { - mSwitchabilityStatus = newStatus; - } } diff --git a/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java b/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java deleted file mode 100644 index c51b2fc67d8..00000000000 --- a/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java +++ /dev/null @@ -1,466 +0,0 @@ -/* - * 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 static android.os.UserManager.SWITCHABILITY_STATUS_OK; -import static android.os.UserManager.SWITCHABILITY_STATUS_USER_IN_CALL; -import static android.os.UserManager.SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -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 android.content.ComponentName; -import android.content.Context; -import android.content.pm.UserInfo; -import android.os.Bundle; -import android.os.UserHandle; -import android.os.UserManager; -import android.telephony.TelephonyManager; - -import androidx.fragment.app.FragmentActivity; -import androidx.preference.Preference; -import androidx.preference.PreferenceScreen; -import androidx.preference.SwitchPreference; - -import com.android.settings.R; -import com.android.settings.testutils.shadow.ShadowDevicePolicyManager; -import com.android.settings.testutils.shadow.ShadowUserManager; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.android.controller.ActivityController; -import org.robolectric.annotation.Config; -import org.robolectric.shadow.api.Shadow; -import org.robolectric.util.ReflectionHelpers; - -import java.util.ArrayList; -import java.util.List; - -@RunWith(RobolectricTestRunner.class) -@Config(shadows = { - ShadowUserManager.class, - ShadowDevicePolicyManager.class -}) -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 int DIALOG_CONFIRM_REMOVE = 1; - - @Mock - private TelephonyManager mTelephonyManager; - - private ShadowUserManager mUserManager; - - @Mock - private Preference mSwitchUserPref; - @Mock - private SwitchPreference mPhonePref; - @Mock - private Preference mRemoveUserPref; - - private FragmentActivity mActivity; - private Context mContext; - private UserDetailsSettings mFragment; - private Bundle mArguments; - private UserInfo mUserInfo; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - mActivity = spy(ActivityController.of(new FragmentActivity()).get()); - mContext = spy(RuntimeEnvironment.application); - mFragment = spy(new UserDetailsSettings()); - mArguments = new Bundle(); - - UserManager userManager = (UserManager) mContext.getSystemService( - Context.USER_SERVICE); - mUserManager = Shadow.extract(userManager); - - doReturn(mTelephonyManager).when(mActivity).getSystemService(Context.TELEPHONY_SERVICE); - - ReflectionHelpers.setField(mFragment, "mUserManager", userManager); - doReturn(mActivity).when(mFragment).getActivity(); - doReturn(mContext).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); - } - - @After - public void tearDown() { - ShadowUserManager.reset(); - } - - @Test(expected = IllegalStateException.class) - public void initialize_nullArguments_shouldThrowException() { - mFragment.initialize(mActivity, null); - } - - @Test(expected = IllegalStateException.class) - public void initialize_emptyArguments_shouldThrowException() { - mFragment.initialize(mActivity, new Bundle()); - } - - @Test - public void initialize_userSelected_shouldSetupSwitchPref() { - setupSelectedUser(); - doReturn("Switch to " + mUserInfo.name) - .when(mActivity).getString(anyInt(), anyString()); - - mFragment.initialize(mActivity, mArguments); - - verify(mActivity).getString(com.android.settingslib.R.string.user_switch_to_user, - mUserInfo.name); - verify(mSwitchUserPref).setTitle("Switch to " + mUserInfo.name); - verify(mSwitchUserPref).setOnPreferenceClickListener(mFragment); - verify(mFragment, never()).removePreference(KEY_SWITCH_USER); - } - - @Test - public void initialize_guestSelected_shouldSetupSwitchPref() { - setupSelectedGuest(); - doReturn("Switch to " + mUserInfo.name) - .when(mActivity).getString(anyInt(), anyString()); - - mFragment.initialize(mActivity, mArguments); - - verify(mActivity).getString(com.android.settingslib.R.string.user_switch_to_user, - mUserInfo.name); - verify(mSwitchUserPref).setTitle("Switch to " + mUserInfo.name); - verify(mSwitchUserPref).setOnPreferenceClickListener(mFragment); - verify(mFragment, never()).removePreference(KEY_SWITCH_USER); - } - - @Test - public void onResume_canSwitch_shouldEnableSwitchPref() { - mUserManager.setSwitchabilityStatus(SWITCHABILITY_STATUS_OK); - mFragment.mSwitchUserPref = mSwitchUserPref; - mFragment.onAttach(mContext); - - mFragment.onResume(); - - verify(mSwitchUserPref).setEnabled(true); - } - - @Test - public void onResume_userInCall_shouldDisableSwitchPref() { - mUserManager.setSwitchabilityStatus(SWITCHABILITY_STATUS_USER_IN_CALL); - mFragment.mSwitchUserPref = mSwitchUserPref; - mFragment.onAttach(mContext); - - mFragment.onResume(); - - verify(mSwitchUserPref).setEnabled(false); - } - - @Test - public void onResume_switchDisallowed_shouldDisableSwitchPref() { - mUserManager.setSwitchabilityStatus(SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED); - mFragment.mSwitchUserPref = mSwitchUserPref; - mFragment.onAttach(mContext); - - mFragment.onResume(); - - verify(mSwitchUserPref).setEnabled(false); - } - - @Test - public void onResume_systemUserLocked_shouldDisableSwitchPref() { - mUserManager.setSwitchabilityStatus(UserManager.SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED); - mFragment.mSwitchUserPref = mSwitchUserPref; - mFragment.onAttach(mContext); - - mFragment.onResume(); - - verify(mSwitchUserPref).setEnabled(false); - } - - @Test - public void initialize_adminWithTelephony_shouldShowPhonePreference() { - setupSelectedUser(); - doReturn(true).when(mTelephonyManager).isVoiceCapable(); - mUserManager.setIsAdminUser(true); - - mFragment.initialize(mActivity, mArguments); - - verify(mFragment, never()).removePreference(KEY_ENABLE_TELEPHONY); - verify(mPhonePref).setOnPreferenceChangeListener(mFragment); - } - - @Test - public void initialize_adminNoTelephony_shouldNotShowPhonePreference() { - setupSelectedUser(); - doReturn(false).when(mTelephonyManager).isVoiceCapable(); - mUserManager.setIsAdminUser(true); - doReturn(null).when(mActivity).getSystemService(Context.TELEPHONY_SERVICE); - - mFragment.initialize(mActivity, mArguments); - - verify(mFragment).removePreference(KEY_ENABLE_TELEPHONY); - } - - @Test - public void initialize_nonAdminWithTelephony_shouldNotShowPhonePreference() { - setupSelectedUser(); - doReturn(true).when(mTelephonyManager).isVoiceCapable(); - mUserManager.setIsAdminUser(false); - - mFragment.initialize(mActivity, mArguments); - - verify(mFragment).removePreference(KEY_ENABLE_TELEPHONY); - } - - @Test - public void initialize_adminSelectsSecondaryUser_shouldShowRemovePreference() { - setupSelectedUser(); - mUserManager.setIsAdminUser(true); - - mFragment.initialize(mActivity, mArguments); - - verify(mRemoveUserPref).setOnPreferenceClickListener(mFragment); - verify(mRemoveUserPref).setTitle(R.string.user_remove_user); - verify(mFragment, never()).removePreference(KEY_REMOVE_USER); - } - - @Test - public void initialize_adminSelectsGuest_shouldShowRemovePreference() { - setupSelectedGuest(); - mUserManager.setIsAdminUser(true); - - mFragment.initialize(mActivity, mArguments); - - verify(mRemoveUserPref).setOnPreferenceClickListener(mFragment); - verify(mRemoveUserPref).setTitle(R.string.user_exit_guest_title); - verify(mFragment, never()).removePreference(KEY_REMOVE_USER); - } - - @Test - public void initialize_nonAdmin_shouldNotShowRemovePreference() { - setupSelectedUser(); - mUserManager.setIsAdminUser(false); - - mFragment.initialize(mActivity, mArguments); - - verify(mFragment).removePreference(KEY_REMOVE_USER); - } - - @Test - public void initialize_disallowRemoveUserRestriction_shouldNotShowRemovePreference() { - setupSelectedUser(); - mUserManager.setIsAdminUser(true); - mUserManager.addBaseUserRestriction(UserManager.DISALLOW_REMOVE_USER); - - mFragment.initialize(mActivity, mArguments); - - verify(mFragment).removePreference(KEY_REMOVE_USER); - } - - @Test - public void initialize_userHasCallRestriction_shouldSetPhoneSwitchUnChecked() { - setupSelectedUser(); - mUserManager.setIsAdminUser(true); - mUserManager.addUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS); - - mFragment.initialize(mActivity, mArguments); - - verify(mPhonePref).setChecked(false); - } - - @Test - public void initialize_noCallRestriction_shouldSetPhoneSwitchChecked() { - setupSelectedUser(); - mUserManager.setIsAdminUser(true); - - mFragment.initialize(mActivity, mArguments); - - verify(mPhonePref).setChecked(true); - } - - @Test - public void initialize_guestSelected_noCallRestriction_shouldSetPhonePreference() { - setupSelectedGuest(); - mUserManager.setIsAdminUser(true); - - mFragment.initialize(mActivity, mArguments); - - verify(mPhonePref).setTitle(R.string.user_enable_calling); - verify(mPhonePref).setChecked(true); - } - - @Test - public void initialize_guestSelected_callRestriction_shouldSetPhonePreference() { - setupSelectedGuest(); - mUserManager.setIsAdminUser(true); - mUserManager.addGuestUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS); - - mFragment.initialize(mActivity, mArguments); - - verify(mPhonePref).setTitle(R.string.user_enable_calling); - verify(mPhonePref).setChecked(false); - } - - @Test - public void onPreferenceClick_switchClicked_canSwitch_shouldSwitch() { - setupSelectedUser(); - mUserManager.setSwitchabilityStatus(SWITCHABILITY_STATUS_OK); - mFragment.mSwitchUserPref = mSwitchUserPref; - mFragment.mRemoveUserPref = mRemoveUserPref; - mFragment.mUserInfo = mUserInfo; - - mFragment.onPreferenceClick(mSwitchUserPref); - - verify(mFragment).switchUser(); - } - - @Test - public void onPreferenceClick_switchClicked_canNotSwitch_doNothing() { - setupSelectedUser(); - mUserManager.setSwitchabilityStatus(SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED); - mFragment.mSwitchUserPref = mSwitchUserPref; - mFragment.mRemoveUserPref = mRemoveUserPref; - mFragment.mUserInfo = mUserInfo; - - mFragment.onPreferenceClick(mSwitchUserPref); - - verify(mFragment, never()).switchUser(); - } - - @Test - public void onPreferenceClick_removeClicked_canDelete_shouldShowDialog() { - setupSelectedUser(); - mFragment.mUserInfo = mUserInfo; - mUserManager.setIsAdminUser(true); - mFragment.mSwitchUserPref = mSwitchUserPref; - mFragment.mRemoveUserPref = mRemoveUserPref; - doNothing().when(mFragment).showDialog(anyInt()); - - mFragment.onPreferenceClick(mRemoveUserPref); - - verify(mFragment).canDeleteUser(); - verify(mFragment).showDialog(DIALOG_CONFIRM_REMOVE); - } - - @Test - public void onPreferenceClick_removeClicked_canNotDelete_doNothing() { - setupSelectedUser(); - mFragment.mUserInfo = mUserInfo; - mUserManager.setIsAdminUser(false); - mFragment.mSwitchUserPref = mSwitchUserPref; - mFragment.mRemoveUserPref = mRemoveUserPref; - doNothing().when(mFragment).showDialog(anyInt()); - - mFragment.onPreferenceClick(mRemoveUserPref); - - verify(mFragment).canDeleteUser(); - verify(mFragment, never()).showDialog(DIALOG_CONFIRM_REMOVE); - } - - @Test - public void onPreferenceClick_unknownPreferenceClicked_doNothing() { - setupSelectedUser(); - mFragment.mUserInfo = mUserInfo; - mFragment.mSwitchUserPref = mSwitchUserPref; - mFragment.mRemoveUserPref = mRemoveUserPref; - - mFragment.onPreferenceClick(mock(UserPreference.class)); - - verify(mFragment).onPreferenceClick(any()); - verifyNoMoreInteractions(mFragment); - } - - @Test - public void canDeleteUser_nonAdminUser_shouldReturnFalse() { - mUserManager.setIsAdminUser(false); - - boolean result = mFragment.canDeleteUser(); - - assertThat(result).isFalse(); - } - - @Test - public void canDeleteUser_adminSelectsUser_noRestrictions_shouldReturnTrue() { - setupSelectedUser(); - mUserManager.setIsAdminUser(true); - - boolean result = mFragment.canDeleteUser(); - - assertThat(result).isTrue(); - } - - @Test - public void canDeleteUser_adminSelectsUser_hasRemoveRestriction_shouldReturnFalse() { - setupSelectedUser(); - mUserManager.setIsAdminUser(true); - ComponentName componentName = new ComponentName("test", "test"); - ShadowDevicePolicyManager.getShadow().setDeviceOwnerComponentOnAnyUser(componentName); - ShadowDevicePolicyManager.getShadow().setDeviceOwnerUserId(UserHandle.myUserId()); - List enforcingUsers = new ArrayList<>(); - enforcingUsers.add(new UserManager.EnforcingUser(UserHandle.myUserId(), - UserManager.RESTRICTION_SOURCE_DEVICE_OWNER)); - mUserManager.setUserRestrictionSources( - UserManager.DISALLOW_REMOVE_USER, - UserHandle.of(UserHandle.myUserId()), - enforcingUsers - ); - - boolean result = mFragment.canDeleteUser(); - - assertThat(result).isFalse(); - } - - private void setupSelectedUser() { - mArguments.putInt("user_id", 1); - mUserInfo = new UserInfo(1, "Tom", null, - UserInfo.FLAG_FULL | UserInfo.FLAG_INITIALIZED, - UserManager.USER_TYPE_FULL_SECONDARY); - - mUserManager.addProfile(mUserInfo); - } - - private void setupSelectedGuest() { - mArguments.putInt("user_id", 23); - mUserInfo = new UserInfo(23, "Guest", null, - UserInfo.FLAG_FULL | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_GUEST, - UserManager.USER_TYPE_FULL_GUEST); - - mUserManager.addProfile(mUserInfo); - } -} diff --git a/tests/robotests/src/com/android/settings/users/UserPreferenceTest.java b/tests/robotests/src/com/android/settings/users/UserPreferenceTest.java index 28f415e362b..345784aa51e 100644 --- a/tests/robotests/src/com/android/settings/users/UserPreferenceTest.java +++ b/tests/robotests/src/com/android/settings/users/UserPreferenceTest.java @@ -18,8 +18,12 @@ package com.android.settings.users; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import android.content.Context; import android.os.UserHandle; +import android.view.View; import com.android.settingslib.RestrictedPreferenceHelper; @@ -44,12 +48,28 @@ public class UserPreferenceTest { public void setUp() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; - mUserPreference = new UserPreference(mContext, null /* attrs */, UserHandle.USER_CURRENT); + mUserPreference = new UserPreference(mContext, null /* attrs */, UserHandle.USER_CURRENT, + null /* settingsListener */, null /* deleteListener */); ReflectionHelpers.setField(mUserPreference, "mHelper", mRestrictedPreferenceHelper); } @Test - public void testShouldHideSecondTarget_shouldHide() { + public void testShouldHideSecondTarget_noListener_shouldHide() { assertThat(mUserPreference.shouldHideSecondTarget()).isTrue(); } + + @Test + public void testShouldHideSecondTarget_disabledByAdmin_shouldHide() { + when(mRestrictedPreferenceHelper.isDisabledByAdmin()).thenReturn(true); + + assertThat(mUserPreference.shouldHideSecondTarget()).isTrue(); + } + + @Test + public void testShouldHideSecondTarget_hasSettingListener_shouldNotHide() { + ReflectionHelpers.setField(mUserPreference, "mSettingsClickListener", + mock(View.OnClickListener.class)); + + assertThat(mUserPreference.shouldHideSecondTarget()).isFalse(); + } } diff --git a/tests/robotests/src/com/android/settings/users/UserSettingsTest.java b/tests/robotests/src/com/android/settings/users/UserSettingsTest.java index d2a18403773..5853308c601 100644 --- a/tests/robotests/src/com/android/settings/users/UserSettingsTest.java +++ b/tests/robotests/src/com/android/settings/users/UserSettingsTest.java @@ -16,14 +16,9 @@ package com.android.settings.users; -import static android.os.UserManager.SWITCHABILITY_STATUS_OK; -import static android.os.UserManager.SWITCHABILITY_STATUS_USER_IN_CALL; -import static android.os.UserManager.SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED; - import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.notNull; @@ -31,14 +26,12 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.content.ComponentName; import android.content.Context; import android.content.SharedPreferences; -import android.content.pm.UserInfo; -import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.os.UserHandle; import android.os.UserManager; @@ -49,13 +42,14 @@ import android.view.MenuInflater; import android.view.MenuItem; import androidx.fragment.app.FragmentActivity; +import androidx.preference.Preference; import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceGroup; import androidx.preference.PreferenceManager; import androidx.preference.PreferenceScreen; import com.android.settings.testutils.shadow.ShadowDevicePolicyManager; import com.android.settings.testutils.shadow.ShadowUserManager; -import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedPreference; import org.junit.After; @@ -63,7 +57,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.AdditionalMatchers; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; @@ -72,7 +65,6 @@ import org.robolectric.android.controller.ActivityController; import org.robolectric.annotation.Config; import org.robolectric.util.ReflectionHelpers; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -81,18 +73,7 @@ import java.util.List; public class UserSettingsTest { private static final String KEY_USER_GUEST = "user_guest"; - private static final int ACTIVE_USER_ID = 0; - private static final int INACTIVE_ADMIN_USER_ID = 1; - private static final int INACTIVE_SECONDARY_USER_ID = 14; - private static final int INACTIVE_RESTRICTED_USER_ID = 21; - private static final int INACTIVE_GUEST_USER_ID = 23; - private static final int MANAGED_USER_ID = 11; - private static final String ADMIN_USER_NAME = "Owner"; - private static final String SECONDARY_USER_NAME = "Tom"; - private static final String RESTRICTED_USER_NAME = "Bob"; - private static final String GUEST_USER_NAME = "Guest"; - private static final String MANAGED_USER_NAME = "Work profile"; - private int mProvisionedBackupValue; + private int mProvisioned; @Mock private Drawable mDefaultIconDrawable; @@ -101,10 +82,6 @@ public class UserSettingsTest { @Mock private UserPreference mMePreference; @Mock - private RestrictedPreference mAddUserPreference; - @Mock - private RestrictedPreference mAddGuestPreference; - @Mock private UserManager mUserManager; private FragmentActivity mActivity; @@ -118,7 +95,6 @@ public class UserSettingsTest { mActivity = spy(ActivityController.of(new FragmentActivity()).get()); mContext = spy(RuntimeEnvironment.application); mUserCapabilities = UserCapabilities.create(mContext); - mUserCapabilities.mUserSwitcherEnabled = true; mFragment = spy(new UserSettings()); ReflectionHelpers.setField(mFragment, "mAddUserWhenLockedPreferenceController", @@ -129,41 +105,100 @@ public class UserSettingsTest { ReflectionHelpers.setField(mFragment, "mUserCaps", mUserCapabilities); ReflectionHelpers.setField(mFragment, "mDefaultIconDrawable", mDefaultIconDrawable); ReflectionHelpers.setField(mFragment, "mAddingUser", false); + mFragment.mMePreference = mMePreference; - doReturn(mUserManager).when(mActivity).getSystemService(UserManager.class); - + when((Object) mActivity.getSystemService(UserManager.class)).thenReturn(mUserManager); doReturn(mActivity).when(mFragment).getActivity(); doReturn(mContext).when(mFragment).getContext(); doReturn(mMockPreferenceManager).when(mFragment).getPreferenceManager(); doReturn(mUserManager).when(mContext).getSystemService(UserManager.class); - - mProvisionedBackupValue = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.DEVICE_PROVISIONED, 0); - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.DEVICE_PROVISIONED, 1); //default state - - final SharedPreferences prefs = mock(SharedPreferences.class); - - doReturn(prefs).when(mMockPreferenceManager).getSharedPreferences(); - doReturn(mContext).when(mMockPreferenceManager).getContext(); - doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); - - mFragment.mMePreference = mMePreference; - mFragment.mAddUser = mAddUserPreference; - mFragment.mAddGuest = mAddGuestPreference; - mFragment.mUserListCategory = mock(PreferenceCategory.class); + mProvisioned = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 0); + final SharedPreferences prefs = mock(SharedPreferences .class); + when(mMockPreferenceManager.getSharedPreferences()).thenReturn(prefs); + when(mMockPreferenceManager.getContext()).thenReturn(mContext); } @After public void tearDown() { Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.DEVICE_PROVISIONED, mProvisionedBackupValue); + Settings.Global.DEVICE_PROVISIONED, mProvisioned); } @Test public void testAssignDefaultPhoto_ContextNull_ReturnFalseAndNotCrash() { // Should not crash here - assertThat(UserSettings.assignDefaultPhoto(null, ACTIVE_USER_ID)).isFalse(); + assertThat(UserSettings.assignDefaultPhoto(null, 0)).isFalse(); + } + + @Test + public void updateUserList_cannotSwitchUser_shouldNotBeSelectableForGuest() { + final RestrictedPreference addUser = spy(new RestrictedPreference(mContext)); + final PreferenceGroup userListCategory = spy(new PreferenceCategory(mContext)); + + mUserCapabilities.mIsGuest = false; + mUserCapabilities.mCanAddGuest = true; + mUserCapabilities.mDisallowAddUser = false; + mUserCapabilities.mDisallowSwitchUser = false; + mUserCapabilities.mUserSwitcherEnabled = true; + + mFragment.mUserListCategory = userListCategory; + mFragment.mAddUser = addUser; + + when(mUserManager.canSwitchUsers()).thenReturn(false); + doReturn(mMockPreferenceManager).when(mFragment).getPreferenceManager(); + doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); + doReturn(mMockPreferenceManager).when(userListCategory).getPreferenceManager(); + + mFragment.updateUserList(); + + final Preference guest = userListCategory.findPreference(KEY_USER_GUEST); + assertThat(guest.isSelectable()).isFalse(); + } + + @Test + public void updateUserList_cannotSwitchUser_shouldDisableAddUser() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 1); + final RestrictedPreference addUser = spy(new RestrictedPreference(mContext)); + final PreferenceGroup userListCategory = spy(new PreferenceCategory(mContext)); + + mUserCapabilities.mCanAddUser = true; + mUserCapabilities.mDisallowAddUser = false; + mUserCapabilities.mUserSwitcherEnabled = true; + + mFragment.mUserListCategory = userListCategory; + mFragment.mAddUser = addUser; + + when(mUserManager.canSwitchUsers()).thenReturn(false); + when(mUserManager.canAddMoreUsers()).thenReturn(true); + doReturn(mMockPreferenceManager).when(mFragment).getPreferenceManager(); + doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); + doReturn(mMockPreferenceManager).when(userListCategory).getPreferenceManager(); + + mFragment.updateUserList(); + + assertThat(addUser.isEnabled()).isFalse(); + } + + @Test + public void updateUserList_cannotAddUserButCanSwitchUser_shouldNotShowAddUser() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 1); + final RestrictedPreference addUser = mock(RestrictedPreference.class); + + mUserCapabilities.mCanAddUser = false; + mUserCapabilities.mDisallowAddUser = true; + mUserCapabilities.mUserSwitcherEnabled = true; + + mFragment.mUserListCategory = mock(PreferenceCategory.class); + mFragment.mAddUser = addUser; + + doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); + + mFragment.updateUserList(); + + verify(addUser, never()).setVisible(true); } @Test @@ -183,7 +218,7 @@ public class UserSettingsTest { ShadowDevicePolicyManager.getShadow().setDeviceOwnerComponentOnAnyUser( new ComponentName("test", "test")); - doReturn(SWITCHABILITY_STATUS_OK).when(mUserManager).getUserSwitchability(); + doReturn(true).when(mUserManager).canSwitchUsers(); mUserCapabilities.mIsAdmin = false; Menu menu = mock(Menu.class); @@ -208,7 +243,7 @@ public class UserSettingsTest { @Test public void withoutDisallowRemoveUser_ShouldNotDisableRemoveUser() { // Arrange - doReturn(SWITCHABILITY_STATUS_OK).when(mUserManager).getUserSwitchability(); + doReturn(true).when(mUserManager).canSwitchUsers(); mUserCapabilities.mIsAdmin = false; Menu menu = mock(Menu.class); @@ -231,457 +266,44 @@ public class UserSettingsTest { @Test public void updateUserList_canAddUserAndSwitchUser_shouldShowAddUser() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 1); + final RestrictedPreference addUser = mock(RestrictedPreference.class); + mUserCapabilities.mCanAddUser = true; - doReturn(true).when(mUserManager).canAddMoreUsers(); - doReturn(true).when(mAddUserPreference).isEnabled(); - doReturn(SWITCHABILITY_STATUS_OK).when(mUserManager).getUserSwitchability(); + mUserCapabilities.mDisallowAddUser = false; + mUserCapabilities.mUserSwitcherEnabled = true; + + mFragment.mAddUser = addUser; + mFragment.mUserListCategory = mock(PreferenceCategory.class); + + doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); + doReturn("Test summary").when(mFragment).getString(anyInt(), anyInt()); mFragment.updateUserList(); - verify(mAddUserPreference).setVisible(true); - verify(mAddUserPreference).setSummary(null); - verify(mAddUserPreference).setEnabled(true); - verify(mAddUserPreference).setDisabledByAdmin(null); - verify(mAddUserPreference).setSelectable(true); + verify(addUser).setVisible(true); } @Test - public void updateUserList_canAddGuestAndSwitchUser_shouldShowAddGuest() { - mUserCapabilities.mCanAddGuest = true; - doReturn(true).when(mUserManager).canAddMoreUsers(); - doReturn(SWITCHABILITY_STATUS_OK).when(mUserManager).getUserSwitchability(); + public void updateUserList_addUserDisallowedByAdmin_shouldShowAddUserDisabled() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 1); + final RestrictedPreference addUser = mock(RestrictedPreference.class); - mFragment.updateUserList(); - - verify(mAddGuestPreference).setVisible(true); - verify(mAddGuestPreference).setEnabled(true); - verify(mAddGuestPreference).setIcon(any(Drawable.class)); - verify(mAddGuestPreference).setSelectable(true); - } - - @Test - public void updateUserList_cannotSwitchUser_shouldDisableAddUser() { - mUserCapabilities.mCanAddUser = true; - doReturn(true).when(mUserManager).canAddMoreUsers(); - doReturn(true).when(mAddUserPreference).isEnabled(); - doReturn(SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED) - .when(mUserManager).getUserSwitchability(); - - mFragment.updateUserList(); - - verify(mAddUserPreference).setVisible(true); - verify(mAddUserPreference).setSummary(null); - verify(mAddUserPreference).setEnabled(false); - verify(mAddUserPreference).setSelectable(true); - } - - @Test - public void updateUserList_canNotAddMoreUsers_shouldDisableAddUserWithSummary() { - doReturn(false).when(mUserManager).canAddMoreUsers(); - doReturn(false).when(mAddUserPreference).isEnabled(); - doReturn(SWITCHABILITY_STATUS_OK).when(mUserManager).getUserSwitchability(); - doReturn(4).when(mFragment).getRealUsersCount(); - - mFragment.updateUserList(); - - verify(mAddUserPreference).setVisible(true); - verify(mAddUserPreference).setSummary("You can add up to 4 users"); - verify(mAddUserPreference).setEnabled(false); - verify(mAddUserPreference).setSelectable(true); - } - - @Test - public void updateUserList_cannotSwitchUser_shouldDisableAddGuest() { - mUserCapabilities.mCanAddGuest = true; - doReturn(true).when(mUserManager).canAddMoreUsers(); - doReturn(SWITCHABILITY_STATUS_USER_IN_CALL).when(mUserManager).getUserSwitchability(); - - mFragment.updateUserList(); - - verify(mAddGuestPreference).setVisible(true); - verify(mAddGuestPreference).setEnabled(false); - verify(mAddGuestPreference).setIcon(any(Drawable.class)); - verify(mAddGuestPreference).setSelectable(true); - } - - @Test - public void updateUserList_addUserDisallowedByAdmin_shouldShowDisabledAddUser() { - RestrictedLockUtils.EnforcedAdmin enforcedAdmin = mock( - RestrictedLockUtils.EnforcedAdmin.class); - mUserCapabilities.mEnforcedAdmin = enforcedAdmin; mUserCapabilities.mCanAddUser = false; mUserCapabilities.mDisallowAddUser = true; mUserCapabilities.mDisallowAddUserSetByAdmin = true; - doReturn(true).when(mAddUserPreference).isEnabled(); + mUserCapabilities.mUserSwitcherEnabled = true; + + mFragment.mUserListCategory = mock(PreferenceCategory.class); + mFragment.mAddUser = addUser; + + doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); mFragment.updateUserList(); - verify(mAddUserPreference).setVisible(true); - ArgumentCaptor captor = ArgumentCaptor.forClass( - RestrictedLockUtils.EnforcedAdmin.class); - verify(mAddUserPreference).setDisabledByAdmin(captor.capture()); - assertThat(captor.getValue()).isEqualTo(enforcedAdmin); + verify(addUser).setVisible(true); + assertThat(addUser.isEnabled()).isFalse(); } - - @Test - public void updateUserList_cannotAddUserButCanSwitchUser_shouldNotShowAddUser() { - mUserCapabilities.mCanAddUser = false; - - mFragment.updateUserList(); - - verify(mAddUserPreference).setVisible(false); - } - - @Test - public void updateUserList_canNotAddGuest_shouldNotShowAddGuest() { - mUserCapabilities.mCanAddGuest = false; - - mFragment.updateUserList(); - - verify(mAddGuestPreference).setVisible(false); - } - - @Test - public void updateUserList_notProvisionedDevice_shouldNotShowAddUser() { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.DEVICE_PROVISIONED, 0); - mUserCapabilities.mCanAddUser = true; - - mFragment.updateUserList(); - - verify(mAddUserPreference).setVisible(false); - } - - @Test - public void updateUserList_notProvisionedDevice_shouldNotShowAddGuest() { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.DEVICE_PROVISIONED, 0); - mUserCapabilities.mCanAddGuest = true; - - mFragment.updateUserList(); - - verify(mAddGuestPreference).setVisible(false); - } - - @Test - public void updateUserList_userSwitcherDisabled_shouldNotShowAddUser() { - mUserCapabilities.mCanAddUser = true; - mUserCapabilities.mUserSwitcherEnabled = false; - - mFragment.updateUserList(); - - verify(mAddUserPreference).setVisible(false); - } - - @Test - public void updateUserList_userSwitcherDisabled_shouldNotShowAddGuest() { - mUserCapabilities.mCanAddGuest = true; - mUserCapabilities.mUserSwitcherEnabled = false; - - mFragment.updateUserList(); - - verify(mAddGuestPreference).setVisible(false); - } - - @Test - public void updateUserList_shouldAddAdminUserPreference() { - givenUsers(getAdminUser(true)); - - mFragment.updateUserList(); - - ArgumentCaptor captor = ArgumentCaptor.forClass(UserPreference.class); - verify(mFragment.mUserListCategory).addPreference(captor.capture()); - UserPreference adminPref = captor.getValue(); - assertThat(adminPref).isSameAs(mMePreference); - } - - @Test - public void updateUserList_existingGuest_shouldAddGuestUserPreference() { - givenUsers(getAdminUser(true), getGuest(false)); - - mFragment.updateUserList(); - - ArgumentCaptor captor = ArgumentCaptor.forClass(UserPreference.class); - verify(mFragment.mUserListCategory, times(2)) - .addPreference(captor.capture()); - UserPreference guestPref = captor.getAllValues().get(1); - assertThat(guestPref.getUserId()).isEqualTo(INACTIVE_GUEST_USER_ID); - assertThat(guestPref.getTitle()).isEqualTo("Guest"); - assertThat(guestPref.getIcon()).isNotNull(); - assertThat(guestPref.getKey()).isEqualTo(KEY_USER_GUEST); - assertThat(guestPref.isEnabled()).isEqualTo(true); - assertThat(guestPref.isSelectable()).isEqualTo(true); - assertThat(guestPref.getOnPreferenceClickListener()).isSameAs(mFragment); - } - - @Test - public void updateUserList_existingSecondaryUser_shouldAddSecondaryUserPreference() { - givenUsers(getAdminUser(true), getSecondaryUser(false)); - - mFragment.updateUserList(); - - ArgumentCaptor captor = ArgumentCaptor.forClass(UserPreference.class); - verify(mFragment.mUserListCategory, times(2)) - .addPreference(captor.capture()); - UserPreference userPref = captor.getAllValues().get(1); - assertThat(userPref.getUserId()).isEqualTo(INACTIVE_SECONDARY_USER_ID); - assertThat(userPref.getTitle()).isEqualTo(SECONDARY_USER_NAME); - assertThat(userPref.getIcon()).isNotNull(); - assertThat(userPref.getKey()).isEqualTo("id=" + INACTIVE_SECONDARY_USER_ID); - assertThat(userPref.isEnabled()).isEqualTo(true); - assertThat(userPref.isSelectable()).isEqualTo(true); - assertThat(userPref.getOnPreferenceClickListener()).isSameAs(mFragment); - } - - @Test - public void updateUserList_existingRestrictedUser_shouldAddRestrictedUserPreference() { - givenUsers(getAdminUser(true), getRestrictedUser(false)); - - mFragment.updateUserList(); - - ArgumentCaptor captor = ArgumentCaptor.forClass(UserPreference.class); - verify(mFragment.mUserListCategory, times(2)) - .addPreference(captor.capture()); - UserPreference userPref = captor.getAllValues().get(1); - assertThat(userPref.getUserId()).isEqualTo(INACTIVE_RESTRICTED_USER_ID); - assertThat(userPref.getTitle()).isEqualTo(RESTRICTED_USER_NAME); - assertThat(userPref.getIcon()).isNotNull(); - assertThat(userPref.getKey()).isEqualTo("id=" + INACTIVE_RESTRICTED_USER_ID); - assertThat(userPref.getSummary()).isEqualTo("Restricted profile"); - assertThat(userPref.isEnabled()).isEqualTo(true); - assertThat(userPref.isSelectable()).isEqualTo(true); - assertThat(userPref.getOnPreferenceClickListener()).isSameAs(mFragment); - } - - @Test - public void updateUserList_existingManagedUser_shouldNotAddUserPreference() { - givenUsers(getAdminUser(true), getManagedUser()); - - mFragment.updateUserList(); - - ArgumentCaptor captor = ArgumentCaptor.forClass(UserPreference.class); - verify(mFragment.mUserListCategory).addPreference(captor.capture()); - List userPreferences = captor.getAllValues(); - assertThat(userPreferences.size()).isEqualTo(1); - assertThat(userPreferences.get(0).getUserId()).isEqualTo(ACTIVE_USER_ID); - } - - @Test - public void updateUserList_uninitializedRestrictedUser_shouldAddUserPreference() { - UserInfo restrictedUser = getRestrictedUser(false); - removeFlag(restrictedUser, UserInfo.FLAG_INITIALIZED); - givenUsers(getAdminUser(true), restrictedUser); - doReturn(SWITCHABILITY_STATUS_OK).when(mUserManager).getUserSwitchability(); - mUserCapabilities.mDisallowSwitchUser = false; - - mFragment.updateUserList(); - - ArgumentCaptor captor = ArgumentCaptor.forClass(UserPreference.class); - verify(mFragment.mUserListCategory, times(2)) - .addPreference(captor.capture()); - UserPreference userPref = captor.getAllValues().get(1); - assertThat(userPref.getUserId()).isEqualTo(INACTIVE_RESTRICTED_USER_ID); - assertThat(userPref.getTitle()).isEqualTo(RESTRICTED_USER_NAME); - assertThat(userPref.getIcon()).isNotNull(); - assertThat(userPref.getKey()).isEqualTo("id=" + INACTIVE_RESTRICTED_USER_ID); - assertThat(userPref.getSummary()).isEqualTo("Not set up - Restricted profile"); - assertThat(userPref.isEnabled()).isEqualTo(true); - assertThat(userPref.isSelectable()).isEqualTo(true); - assertThat(userPref.getOnPreferenceClickListener()).isSameAs(mFragment); - } - - @Test - public void updateUserList_uninitializedUserAndCanNotSwitchUser_shouldDisablePref() { - UserInfo uninitializedUser = getSecondaryUser(false); - removeFlag(uninitializedUser, UserInfo.FLAG_INITIALIZED); - givenUsers(getAdminUser(true), uninitializedUser); - doReturn(SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED) - .when(mUserManager).getUserSwitchability(); - mUserCapabilities.mDisallowSwitchUser = false; - - mFragment.updateUserList(); - - ArgumentCaptor captor = ArgumentCaptor.forClass(UserPreference.class); - verify(mFragment.mUserListCategory, times(2)) - .addPreference(captor.capture()); - UserPreference userPref = captor.getAllValues().get(1); - assertThat(userPref.getUserId()).isEqualTo(INACTIVE_SECONDARY_USER_ID); - assertThat(userPref.getTitle()).isEqualTo(SECONDARY_USER_NAME); - assertThat(userPref.getIcon()).isNotNull(); - assertThat(userPref.getKey()).isEqualTo("id=" + INACTIVE_SECONDARY_USER_ID); - assertThat(userPref.getSummary()).isEqualTo("Not set up"); - assertThat(userPref.isEnabled()).isEqualTo(false); - assertThat(userPref.isSelectable()).isEqualTo(true); - assertThat(userPref.getOnPreferenceClickListener()).isSameAs(mFragment); - } - - @Test - public void updateUserList_guestWithoutInitializedFlag_shouldNotSetSummary() { - UserInfo guest = getGuest(false); - removeFlag(guest, UserInfo.FLAG_INITIALIZED); - givenUsers(getAdminUser(true), guest); - - mFragment.updateUserList(); - - ArgumentCaptor captor = ArgumentCaptor.forClass(UserPreference.class); - verify(mFragment.mUserListCategory, times(2)) - .addPreference(captor.capture()); - UserPreference userPref = captor.getAllValues().get(1); - assertThat(userPref.getUserId()).isEqualTo(INACTIVE_GUEST_USER_ID); - assertThat(userPref.getSummary()).isNull(); - } - - @Test - public void updateUserList_activeUserWithoutInitializedFlag_shouldNotSetSummary() { - UserInfo activeUser = getSecondaryUser(true); - removeFlag(activeUser, UserInfo.FLAG_INITIALIZED); - givenUsers(activeUser); - - mFragment.updateUserList(); - - ArgumentCaptor captor = ArgumentCaptor.forClass(UserPreference.class); - verify(mFragment.mUserListCategory).addPreference(captor.capture()); - UserPreference userPref = captor.getValue(); - assertThat(userPref.getUserId()).isEqualTo(ACTIVE_USER_ID); - assertThat(userPref.getSummary()).isNull(); - } - - @Test - public void updateUserList_guestIsAlreadyCreated_shouldNotShowAddGuest() { - givenUsers(getAdminUser(true), getGuest(true)); - mUserCapabilities.mCanAddGuest = true; - - mFragment.updateUserList(); - - verify(mAddGuestPreference).setVisible(false); - } - - @Test - public void updateUserList_userIconLoaded_shouldNotLoadIcon() { - UserInfo currentUser = getAdminUser(true); - currentUser.iconPath = "/data/system/users/0/photo.png"; - givenUsers(currentUser); - mFragment.mUserIcons.put(ACTIVE_USER_ID, - Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888)); - - mFragment.updateUserList(); - - verify(mUserManager, never()).getUserIcon(anyInt()); - // updateUserList should be called only once - verify(mUserManager).getUsers(true); - } - - @Test - public void updateUserList_userIconMissing_shouldLoadIcon() { - UserInfo currentUser = getAdminUser(true); - currentUser.iconPath = "/data/system/users/0/photo.png"; - givenUsers(currentUser); - // create a non-empty sparsearray - mFragment.mUserIcons.put(5, Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888)); - Bitmap userIcon = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888); - doReturn(userIcon).when(mUserManager).getUserIcon(ACTIVE_USER_ID); - - mFragment.updateUserList(); - - verify(mUserManager).getUserIcon(ACTIVE_USER_ID); - // updateUserList should be called another time after loading the icons - verify(mUserManager, times(2)).getUsers(true); - } - - @Test - public void getRealUsersCount_onlyAdmin_shouldCount() { - givenUsers(getAdminUser(true)); - - int result = mFragment.getRealUsersCount(); - - assertThat(result).isEqualTo(1); - verify(mUserManager).getUsers(); - } - - @Test - public void getRealUsersCount_secondaryUser_shouldCount() { - givenUsers(getAdminUser(true), getSecondaryUser(false)); - - int result = mFragment.getRealUsersCount(); - - assertThat(result).isEqualTo(2); - verify(mUserManager).getUsers(); - } - - @Test - public void getRealUsersCount_restrictedUser_shouldCount() { - givenUsers(getAdminUser(true), getSecondaryUser(false)); - - int result = mFragment.getRealUsersCount(); - - assertThat(result).isEqualTo(2); - verify(mUserManager).getUsers(); - } - - @Test - public void getRealUsersCount_guest_shouldNotCount() { - givenUsers(getAdminUser(true), getGuest(false)); - - int result = mFragment.getRealUsersCount(); - - assertThat(result).isEqualTo(1); - verify(mUserManager).getUsers(); - } - - @Test - public void getRealUsersCount_managedUser_shouldNotCount() { - givenUsers(getAdminUser(true), getManagedUser()); - - int result = mFragment.getRealUsersCount(); - - assertThat(result).isEqualTo(1); - verify(mUserManager).getUsers(); - } - - private void givenUsers(UserInfo... userInfo) { - List users = Arrays.asList(userInfo); - doReturn(users).when(mUserManager).getUsers(); - doReturn(users).when(mUserManager).getUsers(anyBoolean()); - } - - private static void removeFlag(UserInfo userInfo, int flag) { - userInfo.flags &= ~flag; - } - - private static UserInfo getAdminUser(boolean active) { - return new UserInfo(active ? ACTIVE_USER_ID : INACTIVE_ADMIN_USER_ID, ADMIN_USER_NAME, - null, - UserInfo.FLAG_FULL | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN, - UserManager.USER_TYPE_FULL_SYSTEM); - } - - private static UserInfo getSecondaryUser(boolean active) { - return new UserInfo(active ? ACTIVE_USER_ID : INACTIVE_SECONDARY_USER_ID, - SECONDARY_USER_NAME, null, - UserInfo.FLAG_FULL | UserInfo.FLAG_INITIALIZED, - UserManager.USER_TYPE_FULL_SECONDARY); - } - - private static UserInfo getRestrictedUser(boolean active) { - return new UserInfo(active ? ACTIVE_USER_ID : INACTIVE_RESTRICTED_USER_ID, - RESTRICTED_USER_NAME, null, - UserInfo.FLAG_FULL | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_RESTRICTED, - UserManager.USER_TYPE_FULL_RESTRICTED); - } - - private static UserInfo getManagedUser() { - return new UserInfo(MANAGED_USER_ID, - MANAGED_USER_NAME, null, - UserInfo.FLAG_PROFILE | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_MANAGED_PROFILE, - UserManager.USER_TYPE_PROFILE_MANAGED); - } - - private static UserInfo getGuest(boolean active) { - return new UserInfo(active ? ACTIVE_USER_ID : INACTIVE_GUEST_USER_ID, GUEST_USER_NAME, - null, - UserInfo.FLAG_FULL | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_GUEST, - UserManager.USER_TYPE_FULL_GUEST); - } - - } From bfdf45e5652efcbcca61f555e9e65f1f2a02e71e Mon Sep 17 00:00:00 2001 From: Bonian Chen Date: Thu, 7 May 2020 18:49:08 +0800 Subject: [PATCH 13/14] [Settings] Code reformat Code reformat. Bug: 155962136 Test: build pass Change-Id: Icb56f72af925f2b47cd8867156ca12fc9a6e6347 --- .../EnabledNetworkModePreferenceController.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/com/android/settings/network/telephony/EnabledNetworkModePreferenceController.java b/src/com/android/settings/network/telephony/EnabledNetworkModePreferenceController.java index d5a192a5456..6917549cb25 100644 --- a/src/com/android/settings/network/telephony/EnabledNetworkModePreferenceController.java +++ b/src/com/android/settings/network/telephony/EnabledNetworkModePreferenceController.java @@ -413,10 +413,10 @@ public class EnabledNetworkModePreferenceController extends TelephonyManagerConstants.NETWORK_MODE_LTE_GSM_WCDMA); if (is5gEntryDisplayed()) { setSummary(mShow4gForLTE - ? R.string.network_4G_pure : R.string.network_lte_pure); + ? R.string.network_4G_pure : R.string.network_lte_pure); } else { setSummary(mShow4gForLTE - ? R.string.network_4G : R.string.network_lte); + ? R.string.network_4G : R.string.network_lte); } } else { setSelectedEntry( @@ -461,8 +461,8 @@ public class EnabledNetworkModePreferenceController extends case TelephonyManagerConstants.NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA: case TelephonyManagerConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA: if (MobileNetworkUtils.isTdscdmaSupported(mContext, mSubId)) { - setSelectedEntry( - TelephonyManagerConstants.NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA); + setSelectedEntry(TelephonyManagerConstants + .NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA); setSummary(is5gEntryDisplayed() ? R.string.network_lte_pure : R.string.network_lte); } else { @@ -498,8 +498,8 @@ public class EnabledNetworkModePreferenceController extends case TelephonyManagerConstants.NETWORK_MODE_NR_LTE_TDSCDMA_WCDMA: case TelephonyManagerConstants.NETWORK_MODE_NR_LTE_TDSCDMA_GSM_WCDMA: case TelephonyManagerConstants.NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA: - setSelectedEntry( - TelephonyManagerConstants.NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA); + setSelectedEntry(TelephonyManagerConstants + .NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA); setSummary(mContext.getString(R.string.network_5G) + mContext.getString(R.string.network_recommended)); break; @@ -553,7 +553,8 @@ public class EnabledNetworkModePreferenceController extends case TelephonyManagerConstants.NETWORK_MODE_LTE_TDSCDMA_GSM_WCDMA: return TelephonyManagerConstants.NETWORK_MODE_NR_LTE_TDSCDMA_GSM_WCDMA; case TelephonyManagerConstants.NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA: - return TelephonyManagerConstants.NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA; + return TelephonyManagerConstants + .NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA; default: return networkType; // not LTE } @@ -654,7 +655,7 @@ public class EnabledNetworkModePreferenceController extends } private String[] getEntryValues() { - Integer intArr[] = mEntriesValue.toArray(new Integer[0]); + final Integer [] intArr = mEntriesValue.toArray(new Integer[0]); return Arrays.stream(intArr) .map(String::valueOf) .toArray(String[]::new); From 4d7d4effa5d98a0a84bf0c7539cd60bd05571c7b Mon Sep 17 00:00:00 2001 From: Andras Kloczl Date: Tue, 21 Apr 2020 11:32:12 +0100 Subject: [PATCH 14/14] Improve multi user settings screen - Added switch and user delete functionality to details screen. - Added robo tests. Screenshots: http://shortn/_S6fbIMhAYO Bug: 142798722 Test: Run robo tests with this command: make -j64 RunSettingsRoboTests ROBOTEST_FILTER="com.android.settings.users.*SettingsTest" Change-Id: Ied67290e8fed87feb0a60a3f2c40eb91cc57988e --- res/drawable/ic_add_40dp.xml | 36 ++ res/drawable/ic_phone.xml | 29 + res/drawable/ic_swap.xml | 25 + ...stricted_preference_user_delete_widget.xml | 60 -- res/layout/user_info_header.xml | 39 +- res/xml/user_details_settings.xml | 7 +- res/xml/user_settings.xml | 7 +- .../users/AppRestrictionsFragment.java | 8 + .../users/RestrictedProfileSettings.java | 33 +- .../settings/users/UserDetailsSettings.java | 276 +++++--- .../settings/users/UserPreference.java | 79 +-- .../android/settings/users/UserSettings.java | 256 ++++---- .../testutils/shadow/ShadowUserManager.java | 39 +- .../users/UserDetailsSettingsTest.java | 467 ++++++++++++++ .../settings/users/UserPreferenceTest.java | 24 +- .../settings/users/UserSettingsTest.java | 607 ++++++++++++++---- 16 files changed, 1469 insertions(+), 523 deletions(-) create mode 100644 res/drawable/ic_add_40dp.xml create mode 100644 res/drawable/ic_phone.xml create mode 100644 res/drawable/ic_swap.xml delete mode 100644 res/layout/restricted_preference_user_delete_widget.xml create mode 100644 tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java diff --git a/res/drawable/ic_add_40dp.xml b/res/drawable/ic_add_40dp.xml new file mode 100644 index 00000000000..7245823749b --- /dev/null +++ b/res/drawable/ic_add_40dp.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + diff --git a/res/drawable/ic_phone.xml b/res/drawable/ic_phone.xml new file mode 100644 index 00000000000..28f47fd3486 --- /dev/null +++ b/res/drawable/ic_phone.xml @@ -0,0 +1,29 @@ + + + + \ No newline at end of file diff --git a/res/drawable/ic_swap.xml b/res/drawable/ic_swap.xml new file mode 100644 index 00000000000..1c43d974428 --- /dev/null +++ b/res/drawable/ic_swap.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/layout/restricted_preference_user_delete_widget.xml b/res/layout/restricted_preference_user_delete_widget.xml deleted file mode 100644 index 71f1dd7c8bf..00000000000 --- a/res/layout/restricted_preference_user_delete_widget.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/layout/user_info_header.xml b/res/layout/user_info_header.xml index 5135e0e2d27..bfdf3fc7324 100644 --- a/res/layout/user_info_header.xml +++ b/res/layout/user_info_header.xml @@ -15,6 +15,7 @@ --> + android:layout_marginEnd="@*android:dimen/preference_item_padding_inner"/> @@ -88,6 +89,40 @@ android:layout_gravity="center" android:background="?android:attr/selectableItemBackground" /> + + + + + + + + + diff --git a/res/xml/user_details_settings.xml b/res/xml/user_details_settings.xml index 09154c483d7..d336395d02a 100644 --- a/res/xml/user_details_settings.xml +++ b/res/xml/user_details_settings.xml @@ -17,12 +17,17 @@ + + diff --git a/res/xml/user_settings.xml b/res/xml/user_settings.xml index eb8803b73ee..7726a18ea94 100644 --- a/res/xml/user_settings.xml +++ b/res/xml/user_settings.xml @@ -27,10 +27,15 @@ settings:searchable="false"> + + switchUser()); + // This is going to bind the preferences. super.onActivityCreated(savedInstanceState); } @@ -80,7 +92,6 @@ public class RestrictedProfileSettings extends AppRestrictionsFragment @Override public void onResume() { super.onResume(); - // Check if user still exists UserInfo info = Utils.getExistingUser(mUserManager, mUser); if (info == null) { @@ -89,6 +100,16 @@ public class RestrictedProfileSettings extends AppRestrictionsFragment ((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); + } } } @@ -158,6 +179,16 @@ public class RestrictedProfileSettings extends AppRestrictionsFragment }); } + 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); diff --git a/src/com/android/settings/users/UserDetailsSettings.java b/src/com/android/settings/users/UserDetailsSettings.java index 371c152e157..2696ddc1319 100644 --- a/src/com/android/settings/users/UserDetailsSettings.java +++ b/src/com/android/settings/users/UserDetailsSettings.java @@ -16,55 +16,63 @@ package com.android.settings.users; +import static android.os.UserHandle.USER_NULL; + +import android.app.ActivityManager; import android.app.Dialog; import android.app.settings.SettingsEnums; import android.content.Context; -import android.content.DialogInterface; import android.content.pm.UserInfo; import android.os.Bundle; +import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.util.Log; +import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.SwitchPreference; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.Utils; +import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtilsInternal; import java.util.List; /** - * Settings screen for configuring a specific user. It can contain user restrictions - * and deletion controls. It is shown when you tap on the settings icon in the - * user management (UserSettings) screen. + * Settings screen for configuring, deleting or switching to a specific user. + * It is shown when you tap on a user in the user management (UserSettings) screen. * * Arguments to this fragment must include the userId of the user (in EXTRA_USER_ID) for whom - * to display controls, or should contain the EXTRA_USER_GUEST = true. + * to display controls. */ public class UserDetailsSettings extends SettingsPreferenceFragment implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener { private static final String TAG = UserDetailsSettings.class.getSimpleName(); + 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"; /** Integer extra containing the userId to manage */ static final String EXTRA_USER_ID = "user_id"; - /** Boolean extra to indicate guest preferences */ - static final String EXTRA_USER_GUEST = "guest_user"; private static final int DIALOG_CONFIRM_REMOVE = 1; private static final int DIALOG_CONFIRM_ENABLE_CALLING = 2; private static final int DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS = 3; private UserManager mUserManager; + @VisibleForTesting + Preference mSwitchUserPref; private SwitchPreference mPhonePref; - private Preference mRemoveUserPref; + @VisibleForTesting + Preference mRemoveUserPref; - private UserInfo mUserInfo; - private boolean mGuestUser; + @VisibleForTesting + UserInfo mUserInfo; private Bundle mDefaultGuestRestrictions; @Override @@ -78,46 +86,28 @@ public class UserDetailsSettings extends SettingsPreferenceFragment final Context context = getActivity(); mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); - addPreferencesFromResource(R.xml.user_details_settings); - mPhonePref = (SwitchPreference) findPreference(KEY_ENABLE_TELEPHONY); - mRemoveUserPref = findPreference(KEY_REMOVE_USER); - mGuestUser = getArguments().getBoolean(EXTRA_USER_GUEST, false); + initialize(context, getArguments()); + } - if (!mGuestUser) { - // Regular user. Get the user id from the caller. - final int userId = getArguments().getInt(EXTRA_USER_ID, -1); - if (userId == -1) { - throw new RuntimeException("Arguments to this fragment must contain the user id"); - } - mUserInfo = mUserManager.getUserInfo(userId); - mPhonePref.setChecked(!mUserManager.hasUserRestriction( - UserManager.DISALLOW_OUTGOING_CALLS, new UserHandle(userId))); - mRemoveUserPref.setOnPreferenceClickListener(this); - } else { - // These are not for an existing user, just general Guest settings. - removePreference(KEY_REMOVE_USER); - // Default title is for calling and SMS. Change to calling-only here - mPhonePref.setTitle(R.string.user_enable_calling); - mDefaultGuestRestrictions = mUserManager.getDefaultGuestRestrictions(); - mPhonePref.setChecked( - !mDefaultGuestRestrictions.getBoolean(UserManager.DISALLOW_OUTGOING_CALLS)); - } - if (RestrictedLockUtilsInternal.hasBaseUserRestriction(context, - UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId())) { - removePreference(KEY_REMOVE_USER); - } - mPhonePref.setOnPreferenceChangeListener(this); + @Override + public void onResume() { + super.onResume(); + mSwitchUserPref.setEnabled(canSwitchUserNow()); } @Override public boolean onPreferenceClick(Preference preference) { if (preference == mRemoveUserPref) { - if (!mUserManager.isAdminUser()) { - throw new RuntimeException("Only admins can remove a user"); + if (canDeleteUser()) { + showDialog(DIALOG_CONFIRM_REMOVE); + } + return true; + } else if (preference == mSwitchUserPref) { + if (canSwitchUserNow()) { + switchUser(); } - showDialog(DIALOG_CONFIRM_REMOVE); return true; } return false; @@ -126,7 +116,7 @@ public class UserDetailsSettings extends SettingsPreferenceFragment @Override public boolean onPreferenceChange(Preference preference, Object newValue) { if (Boolean.TRUE.equals(newValue)) { - showDialog(mGuestUser ? DIALOG_CONFIRM_ENABLE_CALLING + showDialog(mUserInfo.isGuest() ? DIALOG_CONFIRM_ENABLE_CALLING : DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS); return false; } @@ -134,65 +124,6 @@ public class UserDetailsSettings extends SettingsPreferenceFragment return true; } - void enableCallsAndSms(boolean enabled) { - mPhonePref.setChecked(enabled); - if (mGuestUser) { - mDefaultGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, !enabled); - // SMS is always disabled for guest - mDefaultGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true); - mUserManager.setDefaultGuestRestrictions(mDefaultGuestRestrictions); - - // Update the guest's restrictions, if there is a guest - // TODO: Maybe setDefaultGuestRestrictions() can internally just set the restrictions - // on any existing guest rather than do it here with multiple Binder calls. - List users = mUserManager.getUsers(true); - for (UserInfo user: users) { - if (user.isGuest()) { - UserHandle userHandle = UserHandle.of(user.id); - for (String key : mDefaultGuestRestrictions.keySet()) { - mUserManager.setUserRestriction( - key, mDefaultGuestRestrictions.getBoolean(key), userHandle); - } - } - } - } else { - UserHandle userHandle = UserHandle.of(mUserInfo.id); - mUserManager.setUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, !enabled, - userHandle); - mUserManager.setUserRestriction(UserManager.DISALLOW_SMS, !enabled, userHandle); - } - } - - @Override - public Dialog onCreateDialog(int dialogId) { - Context context = getActivity(); - if (context == null) return null; - switch (dialogId) { - case DIALOG_CONFIRM_REMOVE: - return UserDialogs.createRemoveDialog(getActivity(), mUserInfo.id, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - removeUser(); - } - }); - case DIALOG_CONFIRM_ENABLE_CALLING: - return UserDialogs.createEnablePhoneCallsDialog(getActivity(), - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - enableCallsAndSms(true); - } - }); - case DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS: - return UserDialogs.createEnablePhoneCallsAndSmsDialog(getActivity(), - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - enableCallsAndSms(true); - } - }); - } - throw new IllegalArgumentException("Unsupported dialogId " + dialogId); - } - @Override public int getDialogMetricsCategory(int dialogId) { switch (dialogId) { @@ -207,7 +138,148 @@ public class UserDetailsSettings extends SettingsPreferenceFragment } } - void removeUser() { + @Override + public Dialog onCreateDialog(int dialogId) { + Context context = getActivity(); + if (context == null) { + return null; + } + switch (dialogId) { + case DIALOG_CONFIRM_REMOVE: + return UserDialogs.createRemoveDialog(getActivity(), mUserInfo.id, + (dialog, which) -> removeUser()); + case DIALOG_CONFIRM_ENABLE_CALLING: + return UserDialogs.createEnablePhoneCallsDialog(getActivity(), + (dialog, which) -> enableCallsAndSms(true)); + case DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS: + return UserDialogs.createEnablePhoneCallsAndSmsDialog(getActivity(), + (dialog, which) -> enableCallsAndSms(true)); + } + throw new IllegalArgumentException("Unsupported dialogId " + dialogId); + } + + @VisibleForTesting + @Override + protected void showDialog(int dialogId) { + super.showDialog(dialogId); + } + + @VisibleForTesting + void initialize(Context context, Bundle arguments) { + int userId = arguments != null ? arguments.getInt(EXTRA_USER_ID, USER_NULL) : USER_NULL; + if (userId == USER_NULL) { + throw new IllegalStateException("Arguments to this fragment must contain the user id"); + } + mUserInfo = mUserManager.getUserInfo(userId); + + mSwitchUserPref = findPreference(KEY_SWITCH_USER); + mPhonePref = findPreference(KEY_ENABLE_TELEPHONY); + mRemoveUserPref = findPreference(KEY_REMOVE_USER); + + mSwitchUserPref.setTitle( + context.getString(com.android.settingslib.R.string.user_switch_to_user, + mUserInfo.name)); + mSwitchUserPref.setOnPreferenceClickListener(this); + + if (!mUserManager.isAdminUser()) { // non admin users can't remove users and allow calls + removePreference(KEY_ENABLE_TELEPHONY); + removePreference(KEY_REMOVE_USER); + } 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); + } else { + // 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); + mDefaultGuestRestrictions = mUserManager.getDefaultGuestRestrictions(); + mPhonePref.setChecked( + !mDefaultGuestRestrictions.getBoolean(UserManager.DISALLOW_OUTGOING_CALLS)); + mRemoveUserPref.setTitle(R.string.user_exit_guest_title); + } + if (RestrictedLockUtilsInternal.hasBaseUserRestriction(context, + UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId())) { + removePreference(KEY_REMOVE_USER); + } + + mRemoveUserPref.setOnPreferenceClickListener(this); + mPhonePref.setOnPreferenceChangeListener(this); + } + } + + @VisibleForTesting + boolean canDeleteUser() { + if (!mUserManager.isAdminUser()) { + return false; + } + + Context context = getActivity(); + if (context == null) { + return false; + } + + final RestrictedLockUtils.EnforcedAdmin removeDisallowedAdmin = + RestrictedLockUtilsInternal.checkIfRestrictionEnforced(context, + UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId()); + if (removeDisallowedAdmin != null) { + RestrictedLockUtils.sendShowAdminSupportDetailsIntent(context, + removeDisallowedAdmin); + return false; + } + return true; + } + + @VisibleForTesting + boolean canSwitchUserNow() { + return mUserManager.getUserSwitchability() == UserManager.SWITCHABILITY_STATUS_OK; + } + + @VisibleForTesting + void switchUser() { + try { + ActivityManager.getService().switchUser(mUserInfo.id); + } catch (RemoteException re) { + Log.e(TAG, "Error while switching to other user."); + } finally { + finishFragment(); + } + } + + private void enableCallsAndSms(boolean enabled) { + mPhonePref.setChecked(enabled); + if (mUserInfo.isGuest()) { + mDefaultGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, !enabled); + // SMS is always disabled for guest + mDefaultGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true); + mUserManager.setDefaultGuestRestrictions(mDefaultGuestRestrictions); + + // Update the guest's restrictions, if there is a guest + // TODO: Maybe setDefaultGuestRestrictions() can internally just set the restrictions + // on any existing guest rather than do it here with multiple Binder calls. + List users = mUserManager.getUsers(true); + for (UserInfo user : users) { + if (user.isGuest()) { + UserHandle userHandle = UserHandle.of(user.id); + for (String key : mDefaultGuestRestrictions.keySet()) { + mUserManager.setUserRestriction( + key, mDefaultGuestRestrictions.getBoolean(key), userHandle); + } + } + } + } else { + UserHandle userHandle = UserHandle.of(mUserInfo.id); + mUserManager.setUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, !enabled, + userHandle); + mUserManager.setUserRestriction(UserManager.DISALLOW_SMS, !enabled, userHandle); + } + } + + private void removeUser() { mUserManager.removeUser(mUserInfo.id); finishFragment(); } diff --git a/src/com/android/settings/users/UserPreference.java b/src/com/android/settings/users/UserPreference.java index 3603d44ea09..0b78d787608 100644 --- a/src/com/android/settings/users/UserPreference.java +++ b/src/com/android/settings/users/UserPreference.java @@ -21,18 +21,16 @@ import android.graphics.drawable.Drawable; import android.os.UserHandle; import android.os.UserManager; import android.util.AttributeSet; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.ImageView; import androidx.preference.PreferenceViewHolder; -import com.android.settings.R; -import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.RestrictedPreference; import java.util.Comparator; +/** + * Preference for a user that appear on {@link UserSettings} screen. + */ public class UserPreference extends RestrictedPreference { private static final int ALPHA_ENABLED = 255; private static final int ALPHA_DISABLED = 102; @@ -44,8 +42,7 @@ public class UserPreference extends RestrictedPreference { if (p1 == null) { return -1; - } - else if (p2 == null) { + } else if (p2 == null) { return 1; } int sn1 = p1.getSerialNumber(); @@ -58,26 +55,15 @@ public class UserPreference extends RestrictedPreference { return 0; }; - private OnClickListener mDeleteClickListener; - private OnClickListener mSettingsClickListener; private int mSerialNumber = -1; private int mUserId = USERID_UNKNOWN; - static final int SETTINGS_ID = R.id.manage_user; - static final int DELETE_ID = R.id.trash_user; public UserPreference(Context context, AttributeSet attrs) { - this(context, attrs, USERID_UNKNOWN, null, null); + this(context, attrs, USERID_UNKNOWN); } - UserPreference(Context context, AttributeSet attrs, int userId, - OnClickListener settingsListener, - OnClickListener deleteListener) { + UserPreference(Context context, AttributeSet attrs, int userId) { super(context, attrs); - if (deleteListener != null || settingsListener != null) { - setWidgetLayoutResource(R.layout.restricted_preference_user_delete_widget); - } - mDeleteClickListener = deleteListener; - mSettingsClickListener = settingsListener; mUserId = userId; useAdminDisabledSummary(true); } @@ -92,62 +78,13 @@ public class UserPreference extends RestrictedPreference { @Override protected boolean shouldHideSecondTarget() { - if (isDisabledByAdmin()) { - // Disabled by admin, show no secondary target. - return true; - } - if (canDeleteUser()) { - // Need to show delete user target so don't hide. - return false; - } - // Hide if don't have advanced setting listener. - return mSettingsClickListener == null; + return true; } @Override public void onBindViewHolder(PreferenceViewHolder view) { super.onBindViewHolder(view); - final boolean disabledByAdmin = isDisabledByAdmin(); - dimIcon(disabledByAdmin); - View userDeleteWidget = view.findViewById(R.id.user_delete_widget); - if (userDeleteWidget != null) { - userDeleteWidget.setVisibility(disabledByAdmin ? View.GONE : View.VISIBLE); - } - if (!disabledByAdmin) { - View deleteDividerView = view.findViewById(R.id.divider_delete); - View manageDividerView = view.findViewById(R.id.divider_manage); - View deleteView = view.findViewById(R.id.trash_user); - if (deleteView != null) { - if (canDeleteUser()) { - deleteView.setVisibility(View.VISIBLE); - deleteDividerView.setVisibility(View.VISIBLE); - deleteView.setOnClickListener(mDeleteClickListener); - deleteView.setTag(this); - } else { - deleteView.setVisibility(View.GONE); - deleteDividerView.setVisibility(View.GONE); - } - } - ImageView manageView = (ImageView) view.findViewById(R.id.manage_user); - if (manageView != null) { - if (mSettingsClickListener != null) { - manageView.setVisibility(View.VISIBLE); - manageDividerView.setVisibility(mDeleteClickListener == null - ? View.VISIBLE : View.GONE); - manageView.setOnClickListener(mSettingsClickListener); - manageView.setTag(this); - } else { - manageView.setVisibility(View.GONE); - manageDividerView.setVisibility(View.GONE); - } - } - } - } - - private boolean canDeleteUser() { - return mDeleteClickListener != null - && !RestrictedLockUtilsInternal.hasBaseUserRestriction(getContext(), - UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId()); + dimIcon(isDisabledByAdmin()); } private int getSerialNumber() { diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java index 38ef199c5cb..7d4ab5d5312 100644 --- a/src/com/android/settings/users/UserSettings.java +++ b/src/com/android/settings/users/UserSettings.java @@ -48,7 +48,6 @@ import android.util.SparseArray; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; -import android.view.View; import android.widget.SimpleAdapter; import androidx.annotation.VisibleForTesting; @@ -69,7 +68,6 @@ import com.android.settings.password.ChooseLockGeneric; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.widget.SwitchBar; import com.android.settings.widget.SwitchBarController; -import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.RestrictedPreference; @@ -89,15 +87,14 @@ import java.util.Random; /** * Screen that manages the list of users on the device. - * Guest user is an always visible entry, even if the guest is not currently - * active/created. It is meant for controlling properties of a guest user. + * Secondary users and a guest user can be created if there is no restriction. * - * The first one is always the current user. + * The first user in the list is always the current user. * Owner is the primary user. */ @SearchIndexable public class UserSettings extends SettingsPreferenceFragment - implements Preference.OnPreferenceClickListener, View.OnClickListener, + implements Preference.OnPreferenceClickListener, MultiUserSwitchBarController.OnMultiUserSwitchChangedListener, DialogInterface.OnDismissListener { @@ -111,6 +108,7 @@ public class UserSettings extends SettingsPreferenceFragment private static final String KEY_USER_LIST = "user_list"; private static final String KEY_USER_ME = "user_me"; private static final String KEY_USER_GUEST = "user_guest"; + private static final String KEY_ADD_GUEST = "guest_add"; private static final String KEY_ADD_USER = "user_add"; private static final String KEY_ADD_USER_WHEN_LOCKED = "user_settings_add_users_when_locked"; private static final String KEY_MULTIUSER_FOOTER = "multiuser_footer"; @@ -156,7 +154,11 @@ public class UserSettings extends SettingsPreferenceFragment @VisibleForTesting UserPreference mMePreference; @VisibleForTesting + RestrictedPreference mAddGuest; + @VisibleForTesting RestrictedPreference mAddUser; + @VisibleForTesting + SparseArray mUserIcons = new SparseArray<>(); private int mRemovingUserId = -1; private int mAddedUserId = 0; private boolean mAddingUser; @@ -165,7 +167,6 @@ public class UserSettings extends SettingsPreferenceFragment private boolean mShouldUpdateUserList = true; private final Object mUserLock = new Object(); private UserManager mUserManager; - private SparseArray mUserIcons = new SparseArray<>(); private static SparseArray sDarkDefaultUserBitmapCache = new SparseArray<>(); private MultiUserSwitchBarController mSwitchBarController; @@ -271,15 +272,17 @@ public class UserSettings extends SettingsPreferenceFragment final int myUserId = UserHandle.myUserId(); mUserListCategory = (PreferenceGroup) findPreference(KEY_USER_LIST); - mMePreference = new UserPreference(getPrefContext(), null /* attrs */, myUserId, - null /* settings icon handler */, - null /* delete icon handler */); + mMePreference = new UserPreference(getPrefContext(), null /* attrs */, myUserId); mMePreference.setKey(KEY_USER_ME); mMePreference.setOnPreferenceClickListener(this); if (mUserCaps.mIsAdmin) { mMePreference.setSummary(R.string.user_admin); } - mAddUser = (RestrictedPreference) findPreference(KEY_ADD_USER); + + mAddGuest = findPreference(KEY_ADD_GUEST); + mAddGuest.setOnPreferenceClickListener(this); + + mAddUser = findPreference(KEY_ADD_USER); if (!mUserCaps.mCanAddRestrictedProfile) { // Label should only mention adding a "user", not a "profile" mAddUser.setTitle(R.string.user_add_user_menu); @@ -344,8 +347,7 @@ public class UserSettings extends SettingsPreferenceFragment @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { int pos = 0; - final boolean canSwitchUsers = mUserManager.canSwitchUsers(); - if (!mUserCaps.mIsAdmin && canSwitchUsers) { + if (!mUserCaps.mIsAdmin && canSwitchUserNow()) { String nickname = mUserManager.getUserName(); MenuItem removeThisUser = menu.add(0, MENU_REMOVE_USER, pos++, getResources().getString(R.string.user_remove_user_menu, nickname)); @@ -386,10 +388,13 @@ public class UserSettings extends SettingsPreferenceFragment * Loads profile information for the current user. */ private void loadProfile() { - if (mUserCaps.mIsGuest) { + if (isCurrentUserGuest()) { // No need to load profile information mMePreference.setIcon(getEncircledDefaultIcon()); mMePreference.setTitle(R.string.user_exit_guest_title); + mMePreference.setSelectable(true); + // removing a guest will result in switching back to the admin user + mMePreference.setEnabled(canSwitchUserNow()); return; } @@ -412,7 +417,9 @@ public class UserSettings extends SettingsPreferenceFragment } private void finishLoadProfile(String profileName) { - if (getActivity() == null) return; + if (getActivity() == null) { + return; + } mMePreference.setTitle(getString(R.string.user_you, profileName)); int myUserId = UserHandle.myUserId(); Bitmap b = mUserManager.getUserIcon(myUserId); @@ -477,38 +484,28 @@ public class UserSettings extends SettingsPreferenceFragment private void onManageUserClicked(int userId, boolean newUser) { mAddingUser = false; - if (userId == UserPreference.USERID_GUEST_DEFAULTS) { - Bundle extras = new Bundle(); - extras.putBoolean(UserDetailsSettings.EXTRA_USER_GUEST, true); - new SubSettingLauncher(getContext()) - .setDestination(UserDetailsSettings.class.getName()) - .setArguments(extras) - .setTitleRes(R.string.user_guest) - .setSourceMetricsCategory(getMetricsCategory()) - .launch(); - return; - } - UserInfo info = mUserManager.getUserInfo(userId); - if (info.isRestricted() && mUserCaps.mIsAdmin) { + 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 (info.id == UserHandle.myUserId()) { + } else if (userId == UserHandle.myUserId()) { // Jump to owner info panel OwnerInfoSettings.show(this); - } else if (mUserCaps.mIsAdmin) { - final Bundle extras = new Bundle(); + } else { + Bundle extras = new Bundle(); extras.putInt(UserDetailsSettings.EXTRA_USER_ID, userId); new SubSettingLauncher(getContext()) .setDestination(UserDetailsSettings.class.getName()) .setArguments(extras) - .setTitleText(info.name) + .setTitleText(userInfo.name) .setSourceMetricsCategory(getMetricsCategory()) .launch(); } @@ -538,7 +535,9 @@ public class UserSettings extends SettingsPreferenceFragment @Override public Dialog onCreateDialog(int dialogId) { Context context = getActivity(); - if (context == null) return null; + if (context == null) { + return null; + } switch (dialogId) { case DIALOG_CONFIRM_REMOVE: { Dialog dlg = @@ -811,7 +810,7 @@ public class UserSettings extends SettingsPreferenceFragment } private void removeThisUser() { - if (!mUserManager.canSwitchUsers()) { + if (!canSwitchUserNow()) { Log.w(TAG, "Cannot remove current user when switching is disabled"); return; } @@ -882,10 +881,14 @@ public class UserSettings extends SettingsPreferenceFragment } private void switchUserNow(int userId) { + if (!canSwitchUserNow()) { + return; + } + try { ActivityManager.getService().switchUser(userId); } catch (RemoteException re) { - // Nothing to do + Log.e(TAG, "Error while switching to other user."); } } @@ -894,7 +897,7 @@ public class UserSettings extends SettingsPreferenceFragment */ private void exitGuest() { // Just to be safe - if (!mUserCaps.mIsGuest) { + if (!isCurrentUserGuest()) { return; } removeThisUser(); @@ -908,12 +911,12 @@ public class UserSettings extends SettingsPreferenceFragment } final List users = mUserManager.getUsers(true); - final boolean voiceCapable = Utils.isVoiceCapable(context); final ArrayList missingIcons = new ArrayList<>(); final ArrayList userPreferences = new ArrayList<>(); - int guestId = UserPreference.USERID_GUEST_DEFAULTS; userPreferences.add(mMePreference); + boolean canOpenUserDetails = + mUserCaps.mIsAdmin || (canSwitchUserNow() && !mUserCaps.mDisallowSwitchUser); for (UserInfo user : users) { if (!user.supportsSwitchToByUser()) { // Only users that can be switched to should show up here. @@ -924,37 +927,38 @@ public class UserSettings extends SettingsPreferenceFragment if (user.id == UserHandle.myUserId()) { pref = mMePreference; } else if (user.isGuest()) { - // Skip over Guest. We add generic Guest settings after this loop - guestId = user.id; - continue; + pref = new UserPreference(getPrefContext(), null, user.id); + pref.setTitle(R.string.user_guest); + pref.setIcon(getEncircledDefaultIcon()); + pref.setKey(KEY_USER_GUEST); + userPreferences.add(pref); + pref.setEnabled(canOpenUserDetails); + pref.setSelectable(true); + + if (mUserCaps.mDisallowSwitchUser) { + pref.setDisabledByAdmin(RestrictedLockUtilsInternal.getDeviceOwner(context)); + } else { + pref.setDisabledByAdmin(null); + } + pref.setOnPreferenceClickListener(this); } else { - // With Telephony: - // Secondary user: Settings - // Guest: Settings - // Restricted Profile: There is no Restricted Profile - // Without Telephony: - // Secondary user: Delete - // Guest: Nothing - // Restricted Profile: Settings - final boolean showSettings = mUserCaps.mIsAdmin - && (voiceCapable || user.isRestricted()); - final boolean showDelete = mUserCaps.mIsAdmin - && (!voiceCapable && !user.isRestricted() && !user.isGuest()); - pref = new UserPreference(getPrefContext(), null, user.id, - showSettings ? this : null, - showDelete ? this : null); + pref = new UserPreference(getPrefContext(), null, user.id); pref.setKey("id=" + user.id); userPreferences.add(pref); if (user.isAdmin()) { pref.setSummary(R.string.user_admin); } pref.setTitle(user.name); - pref.setSelectable(false); + pref.setOnPreferenceClickListener(this); + pref.setEnabled(canOpenUserDetails); + pref.setSelectable(true); } if (pref == null) { continue; } - if (!isInitialized(user)) { + if (user.id != UserHandle.myUserId() && !user.isGuest() && !user.isInitialized()) { + // sometimes after creating a guest the initialized flag isn't immediately set + // and we don't want to show "Not set up" summary for them if (user.isRestricted()) { pref.setSummary(R.string.user_summary_restricted_not_set_up); } else { @@ -962,10 +966,7 @@ public class UserSettings extends SettingsPreferenceFragment } // Disallow setting up user which results in user switching when the restriction is // set. - if (!mUserCaps.mDisallowSwitchUser) { - pref.setOnPreferenceClickListener(this); - pref.setSelectable(mUserManager.canSwitchUsers()); - } + pref.setEnabled(!mUserCaps.mDisallowSwitchUser && canSwitchUserNow()); } else if (user.isRestricted()) { pref.setSummary(R.string.user_summary_restricted_profile); } @@ -986,53 +987,13 @@ public class UserSettings extends SettingsPreferenceFragment // Add a temporary entry for the user being created if (mAddingUser) { UserPreference pref = new UserPreference(getPrefContext(), null, - UserPreference.USERID_UNKNOWN, null, null); + UserPreference.USERID_UNKNOWN); pref.setEnabled(false); pref.setTitle(mAddingUserName); pref.setIcon(getEncircledDefaultIcon()); userPreferences.add(pref); } - // Check if Guest tile should be added. - if (!mUserCaps.mIsGuest && (mUserCaps.mCanAddGuest || - mUserCaps.mDisallowAddUserSetByAdmin)) { - // Add a virtual Guest user for guest defaults - UserPreference pref = new UserPreference(getPrefContext(), null, - UserPreference.USERID_GUEST_DEFAULTS, - mUserCaps.mIsAdmin && voiceCapable ? this : null /* settings icon handler */, - null /* delete icon handler */); - pref.setTitle(R.string.user_guest); - pref.setIcon(getEncircledDefaultIcon()); - pref.setKey(KEY_USER_GUEST); - userPreferences.add(pref); - if (mUserCaps.mDisallowAddUser) { - pref.setDisabledByAdmin(mUserCaps.mEnforcedAdmin); - } else if (mUserCaps.mDisallowSwitchUser) { - pref.setDisabledByAdmin(RestrictedLockUtilsInternal.getDeviceOwner(context)); - } else { - pref.setDisabledByAdmin(null); - } - if (!mUserManager.canSwitchUsers()) { - pref.setSelectable(false); - } - int finalGuestId = guestId; - pref.setOnPreferenceClickListener(preference -> { - int id = finalGuestId; - if (id == UserPreference.USERID_GUEST_DEFAULTS) { - UserInfo guest = mUserManager.createGuest( - getContext(), preference.getTitle().toString()); - if (guest != null) { - id = guest.id; - } - } - try { - ActivityManager.getService().switchUser(id); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - return true; - }); - } // Sort list of users by serialNum Collections.sort(userPreferences, UserPreference.SERIAL_NUMBER_COMPARATOR); @@ -1064,6 +1025,7 @@ public class UserSettings extends SettingsPreferenceFragment mMultiUserFooterPreferenceController.updateState(multiUserFooterPrefence); mUserListCategory.setVisible(mUserCaps.mUserSwitcherEnabled); + updateAddGuest(context, users.stream().anyMatch(UserInfo::isGuest)); updateAddUser(context); if (!mUserCaps.mUserSwitcherEnabled) { @@ -1077,15 +1039,38 @@ public class UserSettings extends SettingsPreferenceFragment } + private boolean isCurrentUserGuest() { + return mUserCaps.mIsGuest; + } + + private boolean canSwitchUserNow() { + return mUserManager.getUserSwitchability() == UserManager.SWITCHABILITY_STATUS_OK; + } + + private void updateAddGuest(Context context, boolean isGuestAlreadyCreated) { + if (!isGuestAlreadyCreated && mUserCaps.mCanAddGuest + && WizardManagerHelper.isDeviceProvisioned(context) + && mUserCaps.mUserSwitcherEnabled) { + mAddGuest.setVisible(true); + mAddGuest.setIcon(getEncircledDefaultIcon()); + mAddGuest.setEnabled(canSwitchUserNow()); + mAddGuest.setSelectable(true); + } else { + mAddGuest.setVisible(false); + } + } + private void updateAddUser(Context context) { if ((mUserCaps.mCanAddUser || mUserCaps.mDisallowAddUserSetByAdmin) && WizardManagerHelper.isDeviceProvisioned(context) && mUserCaps.mUserSwitcherEnabled) { mAddUser.setVisible(true); - final boolean moreUsers = mUserManager.canAddMoreUsers(); - mAddUser.setEnabled(moreUsers && !mAddingUser && mUserManager.canSwitchUsers()); - if (!moreUsers) { - mAddUser.setSummary(getString(R.string.user_add_max_count, getMaxRealUsers())); + mAddUser.setSelectable(true); + final boolean canAddMoreUsers = mUserManager.canAddMoreUsers(); + mAddUser.setEnabled(canAddMoreUsers && !mAddingUser && canSwitchUserNow()); + if (!canAddMoreUsers) { + mAddUser.setSummary( + getString(R.string.user_add_max_count, getRealUsersCount())); } else { mAddUser.setSummary(null); } @@ -1098,18 +1083,15 @@ public class UserSettings extends SettingsPreferenceFragment } } - private int getMaxRealUsers() { - // guest is not counted against getMaxSupportedUsers() number - final int maxUsersAndGuest = UserManager.getMaxSupportedUsers() + 1; - final List users = mUserManager.getUsers(); - // managed profiles are counted against getMaxSupportedUsers() - int managedProfiles = 0; - for (UserInfo user : users) { - if (user.isManagedProfile()) { - managedProfiles++; - } - } - return maxUsersAndGuest - managedProfiles; + /** + * @return number of non-guest non-managed users + */ + @VisibleForTesting + int getRealUsersCount() { + return (int) mUserManager.getUsers() + .stream() + .filter(user -> !user.isGuest() && !user.isProfile()) + .count(); } private void loadIconsAsync(List missingIcons) { @@ -1151,12 +1133,12 @@ public class UserSettings extends SettingsPreferenceFragment @Override public boolean onPreferenceClick(Preference pref) { if (pref == mMePreference) { - if (mUserCaps.mIsGuest) { + if (isCurrentUserGuest()) { showDialog(DIALOG_CONFIRM_EXIT_GUEST); return true; } // If this is a limited user, launch the user info settings instead of profile editor - if (mUserManager.isLinkedUser()) { + if (mUserManager.isRestrictedProfile()) { onManageUserClicked(UserHandle.myUserId(), false); } else { showDialog(DIALOG_USER_PROFILE_EDITOR); @@ -1165,9 +1147,11 @@ public class UserSettings extends SettingsPreferenceFragment int userId = ((UserPreference) pref).getUserId(); // Get the latest status of the user UserInfo user = mUserManager.getUserInfo(userId); - if (!isInitialized(user)) { + if (!user.isInitialized()) { mHandler.sendMessage(mHandler.obtainMessage( MESSAGE_SETUP_USER, user.id, user.serialNumber)); + } else { + onManageUserClicked(userId, false); } } else if (pref == mAddUser) { // If we allow both types, show a picker, otherwise directly go to @@ -1177,39 +1161,19 @@ public class UserSettings extends SettingsPreferenceFragment } else { onAddUserClicked(USER_TYPE_USER); } + } else if (pref == mAddGuest) { + UserInfo guest = mUserManager.createGuest( + getContext(), getString(com.android.settingslib.R.string.user_guest)); + switchUserNow(guest.id); } return false; } - private boolean isInitialized(UserInfo user) { - return (user.flags & UserInfo.FLAG_INITIALIZED) != 0; - } - private Drawable encircle(Bitmap icon) { Drawable circled = CircleFramedDrawable.getInstance(getActivity(), icon); return circled; } - @Override - public void onClick(View v) { - if (v.getTag() instanceof UserPreference) { - int userId = ((UserPreference) v.getTag()).getUserId(); - if (v.getId() == UserPreference.DELETE_ID) { - final EnforcedAdmin removeDisallowedAdmin = - RestrictedLockUtilsInternal.checkIfRestrictionEnforced(getContext(), - UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId()); - if (removeDisallowedAdmin != null) { - RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(), - removeDisallowedAdmin); - } else { - onRemoveUserClicked(userId); - } - } else if (v.getId() == UserPreference.SETTINGS_ID) { - onManageUserClicked(userId, false); - } - } - } - @Override public void onDismiss(DialogInterface dialog) { synchronized (mUserLock) { diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java index 659c5de770c..1d4f2019fb8 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java @@ -18,6 +18,7 @@ package com.android.settings.testutils.shadow; import android.annotation.UserIdInt; import android.content.pm.UserInfo; +import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; import android.os.UserManager.EnforcingUser; @@ -43,13 +44,17 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager private static boolean sIsSupportsMultipleUsers; - private final List mRestrictions = new ArrayList<>(); + private final List mBaseRestrictions = new ArrayList<>(); + private final List mGuestRestrictions = new ArrayList<>(); private final Map> mRestrictionSources = new HashMap<>(); private final List mUserProfileInfos = new ArrayList<>(); private final Set mManagedProfiles = new HashSet<>(); private boolean mIsQuietModeEnabled = false; private int[] profileIdsForUser = new int[0]; private boolean mUserSwitchEnabled; + + private @UserManager.UserSwitchabilityResult int mSwitchabilityStatus = + UserManager.SWITCHABILITY_STATUS_OK; private final Map mSameProfileGroupIds = Maps.newHashMap(); public void addProfile(UserInfo userInfo) { @@ -82,11 +87,22 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager @Implementation protected boolean hasBaseUserRestriction(String restrictionKey, UserHandle userHandle) { - return mRestrictions.contains(restrictionKey); + return mBaseRestrictions.contains(restrictionKey); } public void addBaseUserRestriction(String restriction) { - mRestrictions.add(restriction); + mBaseRestrictions.add(restriction); + } + + @Implementation + protected Bundle getDefaultGuestRestrictions() { + Bundle bundle = new Bundle(); + mGuestRestrictions.forEach(restriction -> bundle.putBoolean(restriction, true)); + return bundle; + } + + public void addGuestUserRestriction(String restriction) { + mGuestRestrictions.add(restriction); } public static ShadowUserManager getShadow() { @@ -166,4 +182,21 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager public void setSupportsMultipleUsers(boolean supports) { sIsSupportsMultipleUsers = supports; } + + @Implementation + protected UserInfo getUserInfo(@UserIdInt int userId) { + return mUserProfileInfos.stream() + .filter(userInfo -> userInfo.id == userId) + .findFirst() + .orElse(super.getUserInfo(userId)); + } + + @Implementation + protected @UserManager.UserSwitchabilityResult int getUserSwitchability() { + return mSwitchabilityStatus; + } + + public void setSwitchabilityStatus(@UserManager.UserSwitchabilityResult int newStatus) { + mSwitchabilityStatus = newStatus; + } } diff --git a/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java b/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java new file mode 100644 index 00000000000..6c5478262aa --- /dev/null +++ b/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java @@ -0,0 +1,467 @@ +/* + * 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 static android.os.UserManager.SWITCHABILITY_STATUS_OK; +import static android.os.UserManager.SWITCHABILITY_STATUS_USER_IN_CALL; +import static android.os.UserManager.SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +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 android.content.ComponentName; +import android.content.Context; +import android.content.pm.UserInfo; +import android.os.Bundle; +import android.os.UserHandle; +import android.os.UserManager; +import android.telephony.TelephonyManager; + +import androidx.fragment.app.FragmentActivity; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreference; + +import com.android.settings.R; +import com.android.settings.testutils.shadow.ShadowDevicePolicyManager; +import com.android.settings.testutils.shadow.ShadowUserManager; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.android.controller.ActivityController; +import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; +import org.robolectric.util.ReflectionHelpers; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = { + ShadowUserManager.class, + ShadowDevicePolicyManager.class +}) +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 int DIALOG_CONFIRM_REMOVE = 1; + + @Mock + private TelephonyManager mTelephonyManager; + + private ShadowUserManager mUserManager; + + @Mock + private Preference mSwitchUserPref; + @Mock + private SwitchPreference mPhonePref; + @Mock + private Preference mRemoveUserPref; + + private FragmentActivity mActivity; + private Context mContext; + private UserDetailsSettings mFragment; + private Bundle mArguments; + private UserInfo mUserInfo; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mActivity = spy(ActivityController.of(new FragmentActivity()).get()); + mContext = spy(RuntimeEnvironment.application); + mFragment = spy(new UserDetailsSettings()); + mArguments = new Bundle(); + + UserManager userManager = (UserManager) mContext.getSystemService( + Context.USER_SERVICE); + mUserManager = Shadow.extract(userManager); + + doReturn(mTelephonyManager).when(mActivity).getSystemService(Context.TELEPHONY_SERVICE); + + ReflectionHelpers.setField(mFragment, "mUserManager", userManager); + doReturn(mActivity).when(mFragment).getActivity(); + doReturn(mContext).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); + } + + @After + public void tearDown() { + ShadowUserManager.reset(); + } + + @Test(expected = IllegalStateException.class) + public void initialize_nullArguments_shouldThrowException() { + mFragment.initialize(mActivity, null); + } + + @Test(expected = IllegalStateException.class) + public void initialize_emptyArguments_shouldThrowException() { + mFragment.initialize(mActivity, new Bundle()); + } + + @Test + public void initialize_userSelected_shouldSetupSwitchPref() { + setupSelectedUser(); + doReturn("Switch to " + mUserInfo.name) + .when(mActivity).getString(anyInt(), anyString()); + + mFragment.initialize(mActivity, mArguments); + + verify(mActivity).getString(com.android.settingslib.R.string.user_switch_to_user, + mUserInfo.name); + verify(mSwitchUserPref).setTitle("Switch to " + mUserInfo.name); + verify(mSwitchUserPref).setOnPreferenceClickListener(mFragment); + verify(mFragment, never()).removePreference(KEY_SWITCH_USER); + } + + @Test + public void initialize_guestSelected_shouldSetupSwitchPref() { + setupSelectedGuest(); + doReturn("Switch to " + mUserInfo.name) + .when(mActivity).getString(anyInt(), anyString()); + + mFragment.initialize(mActivity, mArguments); + + verify(mActivity).getString(com.android.settingslib.R.string.user_switch_to_user, + mUserInfo.name); + verify(mSwitchUserPref).setTitle("Switch to " + mUserInfo.name); + verify(mSwitchUserPref).setOnPreferenceClickListener(mFragment); + verify(mFragment, never()).removePreference(KEY_SWITCH_USER); + } + + @Test + public void onResume_canSwitch_shouldEnableSwitchPref() { + mUserManager.setSwitchabilityStatus(SWITCHABILITY_STATUS_OK); + mFragment.mSwitchUserPref = mSwitchUserPref; + mFragment.onAttach(mContext); + + mFragment.onResume(); + + verify(mSwitchUserPref).setEnabled(true); + } + + @Test + public void onResume_userInCall_shouldDisableSwitchPref() { + mUserManager.setSwitchabilityStatus(SWITCHABILITY_STATUS_USER_IN_CALL); + mFragment.mSwitchUserPref = mSwitchUserPref; + mFragment.onAttach(mContext); + + mFragment.onResume(); + + verify(mSwitchUserPref).setEnabled(false); + } + + @Test + public void onResume_switchDisallowed_shouldDisableSwitchPref() { + mUserManager.setSwitchabilityStatus(SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED); + mFragment.mSwitchUserPref = mSwitchUserPref; + mFragment.onAttach(mContext); + + mFragment.onResume(); + + verify(mSwitchUserPref).setEnabled(false); + } + + @Test + public void onResume_systemUserLocked_shouldDisableSwitchPref() { + mUserManager.setSwitchabilityStatus(UserManager.SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED); + mFragment.mSwitchUserPref = mSwitchUserPref; + mFragment.onAttach(mContext); + + mFragment.onResume(); + + verify(mSwitchUserPref).setEnabled(false); + } + + @Test + public void initialize_adminWithTelephony_shouldShowPhonePreference() { + setupSelectedUser(); + doReturn(true).when(mTelephonyManager).isVoiceCapable(); + mUserManager.setIsAdminUser(true); + + mFragment.initialize(mActivity, mArguments); + + verify(mFragment, never()).removePreference(KEY_ENABLE_TELEPHONY); + verify(mPhonePref).setOnPreferenceChangeListener(mFragment); + } + + @Test + public void initialize_adminNoTelephony_shouldNotShowPhonePreference() { + setupSelectedUser(); + doReturn(false).when(mTelephonyManager).isVoiceCapable(); + mUserManager.setIsAdminUser(true); + doReturn(null).when(mActivity).getSystemService(Context.TELEPHONY_SERVICE); + + mFragment.initialize(mActivity, mArguments); + + verify(mFragment).removePreference(KEY_ENABLE_TELEPHONY); + } + + @Test + public void initialize_nonAdminWithTelephony_shouldNotShowPhonePreference() { + setupSelectedUser(); + doReturn(true).when(mTelephonyManager).isVoiceCapable(); + mUserManager.setIsAdminUser(false); + + mFragment.initialize(mActivity, mArguments); + + verify(mFragment).removePreference(KEY_ENABLE_TELEPHONY); + } + + @Test + public void initialize_adminSelectsSecondaryUser_shouldShowRemovePreference() { + setupSelectedUser(); + mUserManager.setIsAdminUser(true); + + mFragment.initialize(mActivity, mArguments); + + verify(mRemoveUserPref).setOnPreferenceClickListener(mFragment); + verify(mRemoveUserPref).setTitle(R.string.user_remove_user); + verify(mFragment, never()).removePreference(KEY_REMOVE_USER); + } + + @Test + public void initialize_adminSelectsGuest_shouldShowRemovePreference() { + setupSelectedGuest(); + mUserManager.setIsAdminUser(true); + + mFragment.initialize(mActivity, mArguments); + + verify(mRemoveUserPref).setOnPreferenceClickListener(mFragment); + verify(mRemoveUserPref).setTitle(R.string.user_exit_guest_title); + verify(mFragment, never()).removePreference(KEY_REMOVE_USER); + } + + @Test + public void initialize_nonAdmin_shouldNotShowRemovePreference() { + setupSelectedUser(); + mUserManager.setIsAdminUser(false); + + mFragment.initialize(mActivity, mArguments); + + verify(mFragment).removePreference(KEY_REMOVE_USER); + } + + @Test + public void initialize_disallowRemoveUserRestriction_shouldNotShowRemovePreference() { + setupSelectedUser(); + mUserManager.setIsAdminUser(true); + mUserManager.addBaseUserRestriction(UserManager.DISALLOW_REMOVE_USER); + + mFragment.initialize(mActivity, mArguments); + + verify(mFragment).removePreference(KEY_REMOVE_USER); + } + + @Test + public void initialize_userHasCallRestriction_shouldSetPhoneSwitchUnChecked() { + setupSelectedUser(); + mUserManager.setIsAdminUser(true); + mUserManager.setUserRestriction(mUserInfo.getUserHandle(), + UserManager.DISALLOW_OUTGOING_CALLS, true); + + mFragment.initialize(mActivity, mArguments); + + verify(mPhonePref).setChecked(false); + } + + @Test + public void initialize_noCallRestriction_shouldSetPhoneSwitchChecked() { + setupSelectedUser(); + mUserManager.setIsAdminUser(true); + + mFragment.initialize(mActivity, mArguments); + + verify(mPhonePref).setChecked(true); + } + + @Test + public void initialize_guestSelected_noCallRestriction_shouldSetPhonePreference() { + setupSelectedGuest(); + mUserManager.setIsAdminUser(true); + + mFragment.initialize(mActivity, mArguments); + + verify(mPhonePref).setTitle(R.string.user_enable_calling); + verify(mPhonePref).setChecked(true); + } + + @Test + public void initialize_guestSelected_callRestriction_shouldSetPhonePreference() { + setupSelectedGuest(); + mUserManager.setIsAdminUser(true); + mUserManager.addGuestUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS); + + mFragment.initialize(mActivity, mArguments); + + verify(mPhonePref).setTitle(R.string.user_enable_calling); + verify(mPhonePref).setChecked(false); + } + + @Test + public void onPreferenceClick_switchClicked_canSwitch_shouldSwitch() { + setupSelectedUser(); + mUserManager.setSwitchabilityStatus(SWITCHABILITY_STATUS_OK); + mFragment.mSwitchUserPref = mSwitchUserPref; + mFragment.mRemoveUserPref = mRemoveUserPref; + mFragment.mUserInfo = mUserInfo; + + mFragment.onPreferenceClick(mSwitchUserPref); + + verify(mFragment).switchUser(); + } + + @Test + public void onPreferenceClick_switchClicked_canNotSwitch_doNothing() { + setupSelectedUser(); + mUserManager.setSwitchabilityStatus(SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED); + mFragment.mSwitchUserPref = mSwitchUserPref; + mFragment.mRemoveUserPref = mRemoveUserPref; + mFragment.mUserInfo = mUserInfo; + + mFragment.onPreferenceClick(mSwitchUserPref); + + verify(mFragment, never()).switchUser(); + } + + @Test + public void onPreferenceClick_removeClicked_canDelete_shouldShowDialog() { + setupSelectedUser(); + mFragment.mUserInfo = mUserInfo; + mUserManager.setIsAdminUser(true); + mFragment.mSwitchUserPref = mSwitchUserPref; + mFragment.mRemoveUserPref = mRemoveUserPref; + doNothing().when(mFragment).showDialog(anyInt()); + + mFragment.onPreferenceClick(mRemoveUserPref); + + verify(mFragment).canDeleteUser(); + verify(mFragment).showDialog(DIALOG_CONFIRM_REMOVE); + } + + @Test + public void onPreferenceClick_removeClicked_canNotDelete_doNothing() { + setupSelectedUser(); + mFragment.mUserInfo = mUserInfo; + mUserManager.setIsAdminUser(false); + mFragment.mSwitchUserPref = mSwitchUserPref; + mFragment.mRemoveUserPref = mRemoveUserPref; + doNothing().when(mFragment).showDialog(anyInt()); + + mFragment.onPreferenceClick(mRemoveUserPref); + + verify(mFragment).canDeleteUser(); + verify(mFragment, never()).showDialog(DIALOG_CONFIRM_REMOVE); + } + + @Test + public void onPreferenceClick_unknownPreferenceClicked_doNothing() { + setupSelectedUser(); + mFragment.mUserInfo = mUserInfo; + mFragment.mSwitchUserPref = mSwitchUserPref; + mFragment.mRemoveUserPref = mRemoveUserPref; + + mFragment.onPreferenceClick(mock(UserPreference.class)); + + verify(mFragment).onPreferenceClick(any()); + verifyNoMoreInteractions(mFragment); + } + + @Test + public void canDeleteUser_nonAdminUser_shouldReturnFalse() { + mUserManager.setIsAdminUser(false); + + boolean result = mFragment.canDeleteUser(); + + assertThat(result).isFalse(); + } + + @Test + public void canDeleteUser_adminSelectsUser_noRestrictions_shouldReturnTrue() { + setupSelectedUser(); + mUserManager.setIsAdminUser(true); + + boolean result = mFragment.canDeleteUser(); + + assertThat(result).isTrue(); + } + + @Test + public void canDeleteUser_adminSelectsUser_hasRemoveRestriction_shouldReturnFalse() { + setupSelectedUser(); + mUserManager.setIsAdminUser(true); + ComponentName componentName = new ComponentName("test", "test"); + ShadowDevicePolicyManager.getShadow().setDeviceOwnerComponentOnAnyUser(componentName); + ShadowDevicePolicyManager.getShadow().setDeviceOwnerUserId(UserHandle.myUserId()); + List enforcingUsers = new ArrayList<>(); + enforcingUsers.add(new UserManager.EnforcingUser(UserHandle.myUserId(), + UserManager.RESTRICTION_SOURCE_DEVICE_OWNER)); + mUserManager.setUserRestrictionSources( + UserManager.DISALLOW_REMOVE_USER, + UserHandle.of(UserHandle.myUserId()), + enforcingUsers + ); + + boolean result = mFragment.canDeleteUser(); + + assertThat(result).isFalse(); + } + + private void setupSelectedUser() { + mArguments.putInt("user_id", 1); + mUserInfo = new UserInfo(1, "Tom", null, + UserInfo.FLAG_FULL | UserInfo.FLAG_INITIALIZED, + UserManager.USER_TYPE_FULL_SECONDARY); + + mUserManager.addProfile(mUserInfo); + } + + private void setupSelectedGuest() { + mArguments.putInt("user_id", 23); + mUserInfo = new UserInfo(23, "Guest", null, + UserInfo.FLAG_FULL | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_GUEST, + UserManager.USER_TYPE_FULL_GUEST); + + mUserManager.addProfile(mUserInfo); + } +} diff --git a/tests/robotests/src/com/android/settings/users/UserPreferenceTest.java b/tests/robotests/src/com/android/settings/users/UserPreferenceTest.java index 345784aa51e..28f415e362b 100644 --- a/tests/robotests/src/com/android/settings/users/UserPreferenceTest.java +++ b/tests/robotests/src/com/android/settings/users/UserPreferenceTest.java @@ -18,12 +18,8 @@ package com.android.settings.users; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - import android.content.Context; import android.os.UserHandle; -import android.view.View; import com.android.settingslib.RestrictedPreferenceHelper; @@ -48,28 +44,12 @@ public class UserPreferenceTest { public void setUp() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; - mUserPreference = new UserPreference(mContext, null /* attrs */, UserHandle.USER_CURRENT, - null /* settingsListener */, null /* deleteListener */); + mUserPreference = new UserPreference(mContext, null /* attrs */, UserHandle.USER_CURRENT); ReflectionHelpers.setField(mUserPreference, "mHelper", mRestrictedPreferenceHelper); } @Test - public void testShouldHideSecondTarget_noListener_shouldHide() { + public void testShouldHideSecondTarget_shouldHide() { assertThat(mUserPreference.shouldHideSecondTarget()).isTrue(); } - - @Test - public void testShouldHideSecondTarget_disabledByAdmin_shouldHide() { - when(mRestrictedPreferenceHelper.isDisabledByAdmin()).thenReturn(true); - - assertThat(mUserPreference.shouldHideSecondTarget()).isTrue(); - } - - @Test - public void testShouldHideSecondTarget_hasSettingListener_shouldNotHide() { - ReflectionHelpers.setField(mUserPreference, "mSettingsClickListener", - mock(View.OnClickListener.class)); - - assertThat(mUserPreference.shouldHideSecondTarget()).isFalse(); - } } diff --git a/tests/robotests/src/com/android/settings/users/UserSettingsTest.java b/tests/robotests/src/com/android/settings/users/UserSettingsTest.java index 5853308c601..3104c3c2749 100644 --- a/tests/robotests/src/com/android/settings/users/UserSettingsTest.java +++ b/tests/robotests/src/com/android/settings/users/UserSettingsTest.java @@ -16,9 +16,14 @@ package com.android.settings.users; +import static android.os.UserManager.SWITCHABILITY_STATUS_OK; +import static android.os.UserManager.SWITCHABILITY_STATUS_USER_IN_CALL; +import static android.os.UserManager.SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.notNull; @@ -26,12 +31,14 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import android.content.ComponentName; import android.content.Context; import android.content.SharedPreferences; +import android.content.pm.UserInfo; +import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.os.UserHandle; import android.os.UserManager; @@ -42,14 +49,13 @@ import android.view.MenuInflater; import android.view.MenuItem; import androidx.fragment.app.FragmentActivity; -import androidx.preference.Preference; import androidx.preference.PreferenceCategory; -import androidx.preference.PreferenceGroup; import androidx.preference.PreferenceManager; import androidx.preference.PreferenceScreen; import com.android.settings.testutils.shadow.ShadowDevicePolicyManager; import com.android.settings.testutils.shadow.ShadowUserManager; +import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedPreference; import org.junit.After; @@ -57,6 +63,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.AdditionalMatchers; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; @@ -65,6 +72,7 @@ import org.robolectric.android.controller.ActivityController; import org.robolectric.annotation.Config; import org.robolectric.util.ReflectionHelpers; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -73,7 +81,18 @@ import java.util.List; public class UserSettingsTest { private static final String KEY_USER_GUEST = "user_guest"; - private int mProvisioned; + private static final int ACTIVE_USER_ID = 0; + private static final int INACTIVE_ADMIN_USER_ID = 1; + private static final int INACTIVE_SECONDARY_USER_ID = 14; + private static final int INACTIVE_RESTRICTED_USER_ID = 21; + private static final int INACTIVE_GUEST_USER_ID = 23; + private static final int MANAGED_USER_ID = 11; + private static final String ADMIN_USER_NAME = "Owner"; + private static final String SECONDARY_USER_NAME = "Tom"; + private static final String RESTRICTED_USER_NAME = "Bob"; + private static final String GUEST_USER_NAME = "Guest"; + private static final String MANAGED_USER_NAME = "Work profile"; + private int mProvisionedBackupValue; @Mock private Drawable mDefaultIconDrawable; @@ -82,6 +101,10 @@ public class UserSettingsTest { @Mock private UserPreference mMePreference; @Mock + private RestrictedPreference mAddUserPreference; + @Mock + private RestrictedPreference mAddGuestPreference; + @Mock private UserManager mUserManager; private FragmentActivity mActivity; @@ -95,6 +118,7 @@ public class UserSettingsTest { mActivity = spy(ActivityController.of(new FragmentActivity()).get()); mContext = spy(RuntimeEnvironment.application); mUserCapabilities = UserCapabilities.create(mContext); + mUserCapabilities.mUserSwitcherEnabled = true; mFragment = spy(new UserSettings()); ReflectionHelpers.setField(mFragment, "mAddUserWhenLockedPreferenceController", @@ -105,100 +129,41 @@ public class UserSettingsTest { ReflectionHelpers.setField(mFragment, "mUserCaps", mUserCapabilities); ReflectionHelpers.setField(mFragment, "mDefaultIconDrawable", mDefaultIconDrawable); ReflectionHelpers.setField(mFragment, "mAddingUser", false); - mFragment.mMePreference = mMePreference; - when((Object) mActivity.getSystemService(UserManager.class)).thenReturn(mUserManager); + doReturn(mUserManager).when(mActivity).getSystemService(UserManager.class); + doReturn(mActivity).when(mFragment).getActivity(); doReturn(mContext).when(mFragment).getContext(); doReturn(mMockPreferenceManager).when(mFragment).getPreferenceManager(); doReturn(mUserManager).when(mContext).getSystemService(UserManager.class); - mProvisioned = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.DEVICE_PROVISIONED, 0); - final SharedPreferences prefs = mock(SharedPreferences .class); - when(mMockPreferenceManager.getSharedPreferences()).thenReturn(prefs); - when(mMockPreferenceManager.getContext()).thenReturn(mContext); + + mProvisionedBackupValue = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 0); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 1); //default state + + final SharedPreferences prefs = mock(SharedPreferences.class); + + doReturn(prefs).when(mMockPreferenceManager).getSharedPreferences(); + doReturn(mContext).when(mMockPreferenceManager).getContext(); + doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); + + mFragment.mMePreference = mMePreference; + mFragment.mAddUser = mAddUserPreference; + mFragment.mAddGuest = mAddGuestPreference; + mFragment.mUserListCategory = mock(PreferenceCategory.class); } @After public void tearDown() { Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.DEVICE_PROVISIONED, mProvisioned); + Settings.Global.DEVICE_PROVISIONED, mProvisionedBackupValue); } @Test public void testAssignDefaultPhoto_ContextNull_ReturnFalseAndNotCrash() { // Should not crash here - assertThat(UserSettings.assignDefaultPhoto(null, 0)).isFalse(); - } - - @Test - public void updateUserList_cannotSwitchUser_shouldNotBeSelectableForGuest() { - final RestrictedPreference addUser = spy(new RestrictedPreference(mContext)); - final PreferenceGroup userListCategory = spy(new PreferenceCategory(mContext)); - - mUserCapabilities.mIsGuest = false; - mUserCapabilities.mCanAddGuest = true; - mUserCapabilities.mDisallowAddUser = false; - mUserCapabilities.mDisallowSwitchUser = false; - mUserCapabilities.mUserSwitcherEnabled = true; - - mFragment.mUserListCategory = userListCategory; - mFragment.mAddUser = addUser; - - when(mUserManager.canSwitchUsers()).thenReturn(false); - doReturn(mMockPreferenceManager).when(mFragment).getPreferenceManager(); - doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); - doReturn(mMockPreferenceManager).when(userListCategory).getPreferenceManager(); - - mFragment.updateUserList(); - - final Preference guest = userListCategory.findPreference(KEY_USER_GUEST); - assertThat(guest.isSelectable()).isFalse(); - } - - @Test - public void updateUserList_cannotSwitchUser_shouldDisableAddUser() { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.DEVICE_PROVISIONED, 1); - final RestrictedPreference addUser = spy(new RestrictedPreference(mContext)); - final PreferenceGroup userListCategory = spy(new PreferenceCategory(mContext)); - - mUserCapabilities.mCanAddUser = true; - mUserCapabilities.mDisallowAddUser = false; - mUserCapabilities.mUserSwitcherEnabled = true; - - mFragment.mUserListCategory = userListCategory; - mFragment.mAddUser = addUser; - - when(mUserManager.canSwitchUsers()).thenReturn(false); - when(mUserManager.canAddMoreUsers()).thenReturn(true); - doReturn(mMockPreferenceManager).when(mFragment).getPreferenceManager(); - doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); - doReturn(mMockPreferenceManager).when(userListCategory).getPreferenceManager(); - - mFragment.updateUserList(); - - assertThat(addUser.isEnabled()).isFalse(); - } - - @Test - public void updateUserList_cannotAddUserButCanSwitchUser_shouldNotShowAddUser() { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.DEVICE_PROVISIONED, 1); - final RestrictedPreference addUser = mock(RestrictedPreference.class); - - mUserCapabilities.mCanAddUser = false; - mUserCapabilities.mDisallowAddUser = true; - mUserCapabilities.mUserSwitcherEnabled = true; - - mFragment.mUserListCategory = mock(PreferenceCategory.class); - mFragment.mAddUser = addUser; - - doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); - - mFragment.updateUserList(); - - verify(addUser, never()).setVisible(true); + assertThat(UserSettings.assignDefaultPhoto(null, ACTIVE_USER_ID)).isFalse(); } @Test @@ -218,7 +183,7 @@ public class UserSettingsTest { ShadowDevicePolicyManager.getShadow().setDeviceOwnerComponentOnAnyUser( new ComponentName("test", "test")); - doReturn(true).when(mUserManager).canSwitchUsers(); + doReturn(SWITCHABILITY_STATUS_OK).when(mUserManager).getUserSwitchability(); mUserCapabilities.mIsAdmin = false; Menu menu = mock(Menu.class); @@ -243,7 +208,7 @@ public class UserSettingsTest { @Test public void withoutDisallowRemoveUser_ShouldNotDisableRemoveUser() { // Arrange - doReturn(true).when(mUserManager).canSwitchUsers(); + doReturn(SWITCHABILITY_STATUS_OK).when(mUserManager).getUserSwitchability(); mUserCapabilities.mIsAdmin = false; Menu menu = mock(Menu.class); @@ -266,44 +231,458 @@ public class UserSettingsTest { @Test public void updateUserList_canAddUserAndSwitchUser_shouldShowAddUser() { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.DEVICE_PROVISIONED, 1); - final RestrictedPreference addUser = mock(RestrictedPreference.class); - mUserCapabilities.mCanAddUser = true; - mUserCapabilities.mDisallowAddUser = false; - mUserCapabilities.mUserSwitcherEnabled = true; - - mFragment.mAddUser = addUser; - mFragment.mUserListCategory = mock(PreferenceCategory.class); - - doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); - doReturn("Test summary").when(mFragment).getString(anyInt(), anyInt()); + doReturn(true).when(mUserManager).canAddMoreUsers(); + doReturn(true).when(mAddUserPreference).isEnabled(); + doReturn(SWITCHABILITY_STATUS_OK).when(mUserManager).getUserSwitchability(); mFragment.updateUserList(); - verify(addUser).setVisible(true); + verify(mAddUserPreference).setVisible(true); + verify(mAddUserPreference).setSummary(null); + verify(mAddUserPreference).setEnabled(true); + verify(mAddUserPreference).setDisabledByAdmin(null); + verify(mAddUserPreference).setSelectable(true); } @Test - public void updateUserList_addUserDisallowedByAdmin_shouldShowAddUserDisabled() { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.DEVICE_PROVISIONED, 1); - final RestrictedPreference addUser = mock(RestrictedPreference.class); - - mUserCapabilities.mCanAddUser = false; - mUserCapabilities.mDisallowAddUser = true; - mUserCapabilities.mDisallowAddUserSetByAdmin = true; - mUserCapabilities.mUserSwitcherEnabled = true; - - mFragment.mUserListCategory = mock(PreferenceCategory.class); - mFragment.mAddUser = addUser; - - doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); + public void updateUserList_canAddGuestAndSwitchUser_shouldShowAddGuest() { + mUserCapabilities.mCanAddGuest = true; + doReturn(true).when(mUserManager).canAddMoreUsers(); + doReturn(SWITCHABILITY_STATUS_OK).when(mUserManager).getUserSwitchability(); mFragment.updateUserList(); - verify(addUser).setVisible(true); - assertThat(addUser.isEnabled()).isFalse(); + verify(mAddGuestPreference).setVisible(true); + verify(mAddGuestPreference).setEnabled(true); + verify(mAddGuestPreference).setIcon(any(Drawable.class)); + verify(mAddGuestPreference).setSelectable(true); } + + @Test + public void updateUserList_cannotSwitchUser_shouldDisableAddUser() { + mUserCapabilities.mCanAddUser = true; + doReturn(true).when(mUserManager).canAddMoreUsers(); + doReturn(true).when(mAddUserPreference).isEnabled(); + doReturn(SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED) + .when(mUserManager).getUserSwitchability(); + + mFragment.updateUserList(); + + verify(mAddUserPreference).setVisible(true); + verify(mAddUserPreference).setSummary(null); + verify(mAddUserPreference).setEnabled(false); + verify(mAddUserPreference).setSelectable(true); + } + + @Test + public void updateUserList_canNotAddMoreUsers_shouldDisableAddUserWithSummary() { + mUserCapabilities.mCanAddUser = true; + doReturn(false).when(mUserManager).canAddMoreUsers(); + doReturn(false).when(mAddUserPreference).isEnabled(); + doReturn(SWITCHABILITY_STATUS_OK).when(mUserManager).getUserSwitchability(); + doReturn(4).when(mFragment).getRealUsersCount(); + + mFragment.updateUserList(); + + verify(mAddUserPreference).setVisible(true); + verify(mAddUserPreference).setSummary("You can add up to 4 users"); + verify(mAddUserPreference).setEnabled(false); + verify(mAddUserPreference).setSelectable(true); + } + + @Test + public void updateUserList_cannotSwitchUser_shouldDisableAddGuest() { + mUserCapabilities.mCanAddGuest = true; + doReturn(true).when(mUserManager).canAddMoreUsers(); + doReturn(SWITCHABILITY_STATUS_USER_IN_CALL).when(mUserManager).getUserSwitchability(); + + mFragment.updateUserList(); + + verify(mAddGuestPreference).setVisible(true); + verify(mAddGuestPreference).setEnabled(false); + verify(mAddGuestPreference).setIcon(any(Drawable.class)); + verify(mAddGuestPreference).setSelectable(true); + } + + @Test + public void updateUserList_addUserDisallowedByAdmin_shouldShowDisabledAddUser() { + RestrictedLockUtils.EnforcedAdmin enforcedAdmin = mock( + RestrictedLockUtils.EnforcedAdmin.class); + mUserCapabilities.mEnforcedAdmin = enforcedAdmin; + mUserCapabilities.mCanAddUser = false; + mUserCapabilities.mDisallowAddUser = true; + mUserCapabilities.mDisallowAddUserSetByAdmin = true; + doReturn(true).when(mAddUserPreference).isEnabled(); + + mFragment.updateUserList(); + + verify(mAddUserPreference).setVisible(true); + ArgumentCaptor captor = ArgumentCaptor.forClass( + RestrictedLockUtils.EnforcedAdmin.class); + verify(mAddUserPreference).setDisabledByAdmin(captor.capture()); + assertThat(captor.getValue()).isEqualTo(enforcedAdmin); + } + + @Test + public void updateUserList_cannotAddUserButCanSwitchUser_shouldNotShowAddUser() { + mUserCapabilities.mCanAddUser = false; + + mFragment.updateUserList(); + + verify(mAddUserPreference).setVisible(false); + } + + @Test + public void updateUserList_canNotAddGuest_shouldNotShowAddGuest() { + mUserCapabilities.mCanAddGuest = false; + + mFragment.updateUserList(); + + verify(mAddGuestPreference).setVisible(false); + } + + @Test + public void updateUserList_notProvisionedDevice_shouldNotShowAddUser() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 0); + mUserCapabilities.mCanAddUser = true; + + mFragment.updateUserList(); + + verify(mAddUserPreference).setVisible(false); + } + + @Test + public void updateUserList_notProvisionedDevice_shouldNotShowAddGuest() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 0); + mUserCapabilities.mCanAddGuest = true; + + mFragment.updateUserList(); + + verify(mAddGuestPreference).setVisible(false); + } + + @Test + public void updateUserList_userSwitcherDisabled_shouldNotShowAddUser() { + mUserCapabilities.mCanAddUser = true; + mUserCapabilities.mUserSwitcherEnabled = false; + + mFragment.updateUserList(); + + verify(mAddUserPreference).setVisible(false); + } + + @Test + public void updateUserList_userSwitcherDisabled_shouldNotShowAddGuest() { + mUserCapabilities.mCanAddGuest = true; + mUserCapabilities.mUserSwitcherEnabled = false; + + mFragment.updateUserList(); + + verify(mAddGuestPreference).setVisible(false); + } + + @Test + public void updateUserList_shouldAddAdminUserPreference() { + givenUsers(getAdminUser(true)); + + mFragment.updateUserList(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(UserPreference.class); + verify(mFragment.mUserListCategory).addPreference(captor.capture()); + UserPreference adminPref = captor.getValue(); + assertThat(adminPref).isSameAs(mMePreference); + } + + @Test + public void updateUserList_existingGuest_shouldAddGuestUserPreference() { + givenUsers(getAdminUser(true), getGuest(false)); + + mFragment.updateUserList(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(UserPreference.class); + verify(mFragment.mUserListCategory, times(2)) + .addPreference(captor.capture()); + UserPreference guestPref = captor.getAllValues().get(1); + assertThat(guestPref.getUserId()).isEqualTo(INACTIVE_GUEST_USER_ID); + assertThat(guestPref.getTitle()).isEqualTo("Guest"); + assertThat(guestPref.getIcon()).isNotNull(); + assertThat(guestPref.getKey()).isEqualTo(KEY_USER_GUEST); + assertThat(guestPref.isEnabled()).isEqualTo(true); + assertThat(guestPref.isSelectable()).isEqualTo(true); + assertThat(guestPref.getOnPreferenceClickListener()).isSameAs(mFragment); + } + + @Test + public void updateUserList_existingSecondaryUser_shouldAddSecondaryUserPreference() { + givenUsers(getAdminUser(true), getSecondaryUser(false)); + + mFragment.updateUserList(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(UserPreference.class); + verify(mFragment.mUserListCategory, times(2)) + .addPreference(captor.capture()); + UserPreference userPref = captor.getAllValues().get(1); + assertThat(userPref.getUserId()).isEqualTo(INACTIVE_SECONDARY_USER_ID); + assertThat(userPref.getTitle()).isEqualTo(SECONDARY_USER_NAME); + assertThat(userPref.getIcon()).isNotNull(); + assertThat(userPref.getKey()).isEqualTo("id=" + INACTIVE_SECONDARY_USER_ID); + assertThat(userPref.isEnabled()).isEqualTo(true); + assertThat(userPref.isSelectable()).isEqualTo(true); + assertThat(userPref.getOnPreferenceClickListener()).isSameAs(mFragment); + } + + @Test + public void updateUserList_existingRestrictedUser_shouldAddRestrictedUserPreference() { + givenUsers(getAdminUser(true), getRestrictedUser(false)); + + mFragment.updateUserList(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(UserPreference.class); + verify(mFragment.mUserListCategory, times(2)) + .addPreference(captor.capture()); + UserPreference userPref = captor.getAllValues().get(1); + assertThat(userPref.getUserId()).isEqualTo(INACTIVE_RESTRICTED_USER_ID); + assertThat(userPref.getTitle()).isEqualTo(RESTRICTED_USER_NAME); + assertThat(userPref.getIcon()).isNotNull(); + assertThat(userPref.getKey()).isEqualTo("id=" + INACTIVE_RESTRICTED_USER_ID); + assertThat(userPref.getSummary()).isEqualTo("Restricted profile"); + assertThat(userPref.isEnabled()).isEqualTo(true); + assertThat(userPref.isSelectable()).isEqualTo(true); + assertThat(userPref.getOnPreferenceClickListener()).isSameAs(mFragment); + } + + @Test + public void updateUserList_existingManagedUser_shouldNotAddUserPreference() { + givenUsers(getAdminUser(true), getManagedUser()); + + mFragment.updateUserList(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(UserPreference.class); + verify(mFragment.mUserListCategory).addPreference(captor.capture()); + List userPreferences = captor.getAllValues(); + assertThat(userPreferences.size()).isEqualTo(1); + assertThat(userPreferences.get(0).getUserId()).isEqualTo(ACTIVE_USER_ID); + } + + @Test + public void updateUserList_uninitializedRestrictedUser_shouldAddUserPreference() { + UserInfo restrictedUser = getRestrictedUser(false); + removeFlag(restrictedUser, UserInfo.FLAG_INITIALIZED); + givenUsers(getAdminUser(true), restrictedUser); + doReturn(SWITCHABILITY_STATUS_OK).when(mUserManager).getUserSwitchability(); + mUserCapabilities.mDisallowSwitchUser = false; + + mFragment.updateUserList(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(UserPreference.class); + verify(mFragment.mUserListCategory, times(2)) + .addPreference(captor.capture()); + UserPreference userPref = captor.getAllValues().get(1); + assertThat(userPref.getUserId()).isEqualTo(INACTIVE_RESTRICTED_USER_ID); + assertThat(userPref.getTitle()).isEqualTo(RESTRICTED_USER_NAME); + assertThat(userPref.getIcon()).isNotNull(); + assertThat(userPref.getKey()).isEqualTo("id=" + INACTIVE_RESTRICTED_USER_ID); + assertThat(userPref.getSummary()).isEqualTo("Not set up - Restricted profile"); + assertThat(userPref.isEnabled()).isEqualTo(true); + assertThat(userPref.isSelectable()).isEqualTo(true); + assertThat(userPref.getOnPreferenceClickListener()).isSameAs(mFragment); + } + + @Test + public void updateUserList_uninitializedUserAndCanNotSwitchUser_shouldDisablePref() { + UserInfo uninitializedUser = getSecondaryUser(false); + removeFlag(uninitializedUser, UserInfo.FLAG_INITIALIZED); + givenUsers(getAdminUser(true), uninitializedUser); + doReturn(SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED) + .when(mUserManager).getUserSwitchability(); + mUserCapabilities.mDisallowSwitchUser = false; + + mFragment.updateUserList(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(UserPreference.class); + verify(mFragment.mUserListCategory, times(2)) + .addPreference(captor.capture()); + UserPreference userPref = captor.getAllValues().get(1); + assertThat(userPref.getUserId()).isEqualTo(INACTIVE_SECONDARY_USER_ID); + assertThat(userPref.getTitle()).isEqualTo(SECONDARY_USER_NAME); + assertThat(userPref.getIcon()).isNotNull(); + assertThat(userPref.getKey()).isEqualTo("id=" + INACTIVE_SECONDARY_USER_ID); + assertThat(userPref.getSummary()).isEqualTo("Not set up"); + assertThat(userPref.isEnabled()).isEqualTo(false); + assertThat(userPref.isSelectable()).isEqualTo(true); + assertThat(userPref.getOnPreferenceClickListener()).isSameAs(mFragment); + } + + @Test + public void updateUserList_guestWithoutInitializedFlag_shouldNotSetSummary() { + UserInfo guest = getGuest(false); + removeFlag(guest, UserInfo.FLAG_INITIALIZED); + givenUsers(getAdminUser(true), guest); + + mFragment.updateUserList(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(UserPreference.class); + verify(mFragment.mUserListCategory, times(2)) + .addPreference(captor.capture()); + UserPreference userPref = captor.getAllValues().get(1); + assertThat(userPref.getUserId()).isEqualTo(INACTIVE_GUEST_USER_ID); + assertThat(userPref.getSummary()).isNull(); + } + + @Test + public void updateUserList_activeUserWithoutInitializedFlag_shouldNotSetSummary() { + UserInfo activeUser = getSecondaryUser(true); + removeFlag(activeUser, UserInfo.FLAG_INITIALIZED); + givenUsers(activeUser); + + mFragment.updateUserList(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(UserPreference.class); + verify(mFragment.mUserListCategory).addPreference(captor.capture()); + UserPreference userPref = captor.getValue(); + assertThat(userPref.getUserId()).isEqualTo(ACTIVE_USER_ID); + assertThat(userPref.getSummary()).isNull(); + } + + @Test + public void updateUserList_guestIsAlreadyCreated_shouldNotShowAddGuest() { + givenUsers(getAdminUser(true), getGuest(true)); + mUserCapabilities.mCanAddGuest = true; + + mFragment.updateUserList(); + + verify(mAddGuestPreference).setVisible(false); + } + + @Test + public void updateUserList_userIconLoaded_shouldNotLoadIcon() { + UserInfo currentUser = getAdminUser(true); + currentUser.iconPath = "/data/system/users/0/photo.png"; + givenUsers(currentUser); + mFragment.mUserIcons.put(ACTIVE_USER_ID, + Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888)); + + mFragment.updateUserList(); + + verify(mUserManager, never()).getUserIcon(anyInt()); + // updateUserList should be called only once + verify(mUserManager).getUsers(true); + } + + @Test + public void updateUserList_userIconMissing_shouldLoadIcon() { + UserInfo currentUser = getAdminUser(true); + currentUser.iconPath = "/data/system/users/0/photo.png"; + givenUsers(currentUser); + // create a non-empty sparsearray + mFragment.mUserIcons.put(5, Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888)); + Bitmap userIcon = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888); + doReturn(userIcon).when(mUserManager).getUserIcon(ACTIVE_USER_ID); + + mFragment.updateUserList(); + + verify(mUserManager).getUserIcon(ACTIVE_USER_ID); + // updateUserList should be called another time after loading the icons + verify(mUserManager, times(2)).getUsers(true); + } + + @Test + public void getRealUsersCount_onlyAdmin_shouldCount() { + givenUsers(getAdminUser(true)); + + int result = mFragment.getRealUsersCount(); + + assertThat(result).isEqualTo(1); + verify(mUserManager).getUsers(); + } + + @Test + public void getRealUsersCount_secondaryUser_shouldCount() { + givenUsers(getAdminUser(true), getSecondaryUser(false)); + + int result = mFragment.getRealUsersCount(); + + assertThat(result).isEqualTo(2); + verify(mUserManager).getUsers(); + } + + @Test + public void getRealUsersCount_restrictedUser_shouldCount() { + givenUsers(getAdminUser(true), getSecondaryUser(false)); + + int result = mFragment.getRealUsersCount(); + + assertThat(result).isEqualTo(2); + verify(mUserManager).getUsers(); + } + + @Test + public void getRealUsersCount_guest_shouldNotCount() { + givenUsers(getAdminUser(true), getGuest(false)); + + int result = mFragment.getRealUsersCount(); + + assertThat(result).isEqualTo(1); + verify(mUserManager).getUsers(); + } + + @Test + public void getRealUsersCount_managedUser_shouldNotCount() { + givenUsers(getAdminUser(true), getManagedUser()); + + int result = mFragment.getRealUsersCount(); + + assertThat(result).isEqualTo(1); + verify(mUserManager).getUsers(); + } + + private void givenUsers(UserInfo... userInfo) { + List users = Arrays.asList(userInfo); + doReturn(users).when(mUserManager).getUsers(); + doReturn(users).when(mUserManager).getUsers(anyBoolean()); + } + + private static void removeFlag(UserInfo userInfo, int flag) { + userInfo.flags &= ~flag; + } + + private static UserInfo getAdminUser(boolean active) { + return new UserInfo(active ? ACTIVE_USER_ID : INACTIVE_ADMIN_USER_ID, ADMIN_USER_NAME, + null, + UserInfo.FLAG_FULL | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN, + UserManager.USER_TYPE_FULL_SYSTEM); + } + + private static UserInfo getSecondaryUser(boolean active) { + return new UserInfo(active ? ACTIVE_USER_ID : INACTIVE_SECONDARY_USER_ID, + SECONDARY_USER_NAME, null, + UserInfo.FLAG_FULL | UserInfo.FLAG_INITIALIZED, + UserManager.USER_TYPE_FULL_SECONDARY); + } + + private static UserInfo getRestrictedUser(boolean active) { + return new UserInfo(active ? ACTIVE_USER_ID : INACTIVE_RESTRICTED_USER_ID, + RESTRICTED_USER_NAME, null, + UserInfo.FLAG_FULL | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_RESTRICTED, + UserManager.USER_TYPE_FULL_RESTRICTED); + } + + private static UserInfo getManagedUser() { + return new UserInfo(MANAGED_USER_ID, + MANAGED_USER_NAME, null, + UserInfo.FLAG_PROFILE | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_MANAGED_PROFILE, + UserManager.USER_TYPE_PROFILE_MANAGED); + } + + private static UserInfo getGuest(boolean active) { + return new UserInfo(active ? ACTIVE_USER_ID : INACTIVE_GUEST_USER_ID, GUEST_USER_NAME, + null, + UserInfo.FLAG_FULL | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_GUEST, + UserManager.USER_TYPE_FULL_GUEST); + } + + }