From b4a92ff791f4ec7ff6640cc3cf01c26010f77325 Mon Sep 17 00:00:00 2001 From: Junyu Lai Date: Wed, 2 Oct 2024 13:49:12 +0800 Subject: [PATCH] Improved Entitlement UI Launch for multiple admin users This change addresses the launch of entitlement UI activities in Headless System User Mode (HSUM) on multi-user devices. HSUM is a configuration where the system user (user 0) does not represent a human user and primarily runs system services. In this mode, the first user set up on the device is not user 0. Modified the EntitlementManager logic to handle the launch of the entitlement UI in multi-user environments, specifically addressing scenarios where HSUM is enabled, which includes: 1. Always launch the UI to the foreground current user. 2. Check whether the current user is allowed to show the UI. This ensures the UI is presented to the appropriate user. Test: Manual 1. Mock requires entitlement 2. adb shell cmd user set-system-user-mode-emulation headless 3. enable hotspot Test: atest TetheringTests:EntitlementManagerTest Fix: 363972541 Change-Id: I950d678741a3f1714f347206fa161bcd7538b402 --- Tethering/AndroidManifest.xml | 2 + Tethering/apex/permissions/permissions.xml | 2 + .../tethering/EntitlementManager.java | 40 +++++++++++-- .../tethering/EntitlementManagerTest.java | 56 ++++++++++++++++++- 4 files changed, 94 insertions(+), 6 deletions(-) diff --git a/Tethering/AndroidManifest.xml b/Tethering/AndroidManifest.xml index 6a363b09ab..2a6f6d5d16 100644 --- a/Tethering/AndroidManifest.xml +++ b/Tethering/AndroidManifest.xml @@ -32,8 +32,10 @@ + + diff --git a/Tethering/apex/permissions/permissions.xml b/Tethering/apex/permissions/permissions.xml index f26a9616e0..fcb287e342 100644 --- a/Tethering/apex/permissions/permissions.xml +++ b/Tethering/apex/permissions/permissions.xml @@ -18,8 +18,10 @@ + + diff --git a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java index b88b13bb2f..e42a516f9b 100644 --- a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java +++ b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java @@ -36,6 +36,7 @@ import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED; import static com.android.networkstack.apishim.ConstantsShim.ACTION_TETHER_UNSUPPORTED_CARRIER_UI; import static com.android.networkstack.apishim.ConstantsShim.RECEIVER_NOT_EXPORTED; +import android.app.ActivityManager; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -50,9 +51,13 @@ import android.os.Parcel; import android.os.ResultReceiver; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.UserManager; import android.provider.Settings; import android.util.SparseIntArray; +import androidx.annotation.Nullable; + import com.android.internal.annotations.VisibleForTesting; import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.SharedLog; @@ -406,12 +411,20 @@ public class EntitlementManager { return intent; } + @VisibleForTesting + int getCurrentUser() { + return ActivityManager.getCurrentUser(); + } + /** * Run the UI-enabled tethering provisioning check. * @param type tethering type from TetheringManager.TETHERING_{@code *} - * @param subId default data subscription ID. * @param receiver to receive entitlement check result. + * + * @return the broadcast intent, or null if the current user is not allowed to + * perform entitlement check. */ + @Nullable @VisibleForTesting protected Intent runUiTetherProvisioning(int type, final TetheringConfiguration config, ResultReceiver receiver) { @@ -423,9 +436,28 @@ public class EntitlementManager { intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver); intent.putExtra(EXTRA_TETHER_SUBID, config.activeDataSubId); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - // Only launch entitlement UI for system user. Entitlement UI should not appear for other - // user because only admin user is allowed to change tethering. - mContext.startActivity(intent); + + // Only launch entitlement UI for the current user if it is allowed to + // change tethering. This usually means the system user or the admin users in HSUM. + if (SdkLevel.isAtLeastT()) { + // Create a user context for the current foreground user as UserManager#isAdmin() + // operates on the context user. + final int currentUserId = getCurrentUser(); + final UserHandle currentUser = UserHandle.of(currentUserId); + final Context userContext = mContext.createContextAsUser(currentUser, 0); + final UserManager userManager = userContext.getSystemService(UserManager.class); + + if (userManager.isAdminUser()) { + mContext.startActivityAsUser(intent, currentUser); + } else { + mLog.e("Current user (" + currentUserId + + ") is not allowed to perform entitlement check."); + return null; + } + } else { + // For T- devices, there is no other admin user other than the system user. + mContext.startActivity(intent); + } return intent; } diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java index c2e1617cd8..9fa7004298 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java @@ -38,6 +38,7 @@ import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.networkstack.apishim.ConstantsShim.KEY_CARRIER_SUPPORTS_TETHERING_BOOL; +import static com.android.testutils.DevSdkIgnoreRule.IgnoreAfter; import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2; @@ -71,6 +72,7 @@ import android.os.PersistableBundle; import android.os.ResultReceiver; import android.os.SystemProperties; import android.os.UserHandle; +import android.os.UserManager; import android.os.test.TestLooper; import android.provider.DeviceConfig; import android.provider.Settings; @@ -114,6 +116,7 @@ public final class EntitlementManagerTest { @Mock private EntitlementManager .OnTetherProvisioningFailedListener mTetherProvisioningFailedListener; @Mock private AlarmManager mAlarmManager; + @Mock private UserManager mUserManager; @Mock private PendingIntent mAlarmIntent; @Rule @@ -143,9 +146,21 @@ public final class EntitlementManagerTest { @Override public Object getSystemService(String name) { if (Context.ALARM_SERVICE.equals(name)) return mAlarmManager; + if (Context.USER_SERVICE.equals(name)) return mUserManager; return super.getSystemService(name); } + + @Override + public String getSystemServiceName(Class serviceClass) { + if (UserManager.class.equals(serviceClass)) return Context.USER_SERVICE; + return super.getSystemServiceName(serviceClass); + } + + @Override + public Context createContextAsUser(UserHandle user, int flags) { + return mMockContext; // Return self for easier test injection. + } } public class WrappedEntitlementManager extends EntitlementManager { @@ -168,8 +183,10 @@ public final class EntitlementManagerTest { protected Intent runUiTetherProvisioning(int type, final TetheringConfiguration config, final ResultReceiver receiver) { Intent intent = super.runUiTetherProvisioning(type, config, receiver); - assertUiTetherProvisioningIntent(type, config, receiver, intent); - uiProvisionCount++; + if (intent != null) { + assertUiTetherProvisioningIntent(type, config, receiver, intent); + uiProvisionCount++; + } receiver.send(fakeEntitlementResult, null); return intent; } @@ -217,6 +234,13 @@ public final class EntitlementManagerTest { assertEquals(TEST_PACKAGE_NAME, pkgName); return mAlarmIntent; } + + @Override + int getCurrentUser() { + // The result is not used, just override to bypass the need of accessing + // the static method. + return 0; + } } @Before @@ -253,6 +277,7 @@ public final class EntitlementManagerTest { false); when(mResources.getString(R.string.config_wifi_tether_enable)).thenReturn(""); when(mLog.forSubComponent(anyString())).thenReturn(mLog); + doReturn(true).when(mUserManager).isAdminUser(); mMockContext = new MockContext(mContext); mPermissionChangeCallback = spy(() -> { }); @@ -619,6 +644,33 @@ public final class EntitlementManagerTest { .onTetherProvisioningFailed(TETHERING_WIFI, FAILED_TETHERING_REASON); } + @IgnoreUpTo(SC_V2) + @Test + public void testUiProvisioningMultiUser_aboveT() { + doTestUiProvisioningMultiUser(true, 1); + doTestUiProvisioningMultiUser(false, 0); + } + + @IgnoreAfter(SC_V2) + @Test + public void testUiProvisioningMultiUser_belowT() { + doTestUiProvisioningMultiUser(true, 1); + doTestUiProvisioningMultiUser(false, 1); + } + + private void doTestUiProvisioningMultiUser(boolean isAdminUser, int expectedUiProvisionCount) { + setupForRequiredProvisioning(); + doReturn(isAdminUser).when(mUserManager).isAdminUser(); + + mEnMgr.reset(); + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.notifyUpstream(true); + mLooper.dispatchAll(); + mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true); + mLooper.dispatchAll(); + assertEquals(expectedUiProvisionCount, mEnMgr.uiProvisionCount); + } + @Test public void testsetExemptedDownstreamType() throws Exception { setupForRequiredProvisioning();