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