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();