Merge "Support to query TYPE_WIFI usage with subscriberId"
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index 730555b..27bf114 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -294,7 +294,6 @@
method @NonNull public android.net.NetworkCapabilities.Builder addCapability(int);
method @NonNull public android.net.NetworkCapabilities.Builder addTransportType(int);
method @NonNull public android.net.NetworkCapabilities build();
- method @NonNull public android.net.NetworkCapabilities.Builder clearAll();
method @NonNull public android.net.NetworkCapabilities.Builder removeCapability(int);
method @NonNull public android.net.NetworkCapabilities.Builder removeTransportType(int);
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAdministratorUids(@NonNull int[]);
@@ -308,6 +307,7 @@
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setSsid(@Nullable String);
method @NonNull public android.net.NetworkCapabilities.Builder setSubscriptionIds(@NonNull java.util.Set<java.lang.Integer>);
method @NonNull public android.net.NetworkCapabilities.Builder setTransportInfo(@Nullable android.net.TransportInfo);
+ method @NonNull public static android.net.NetworkCapabilities.Builder withoutDefaultCapabilities();
}
public class NetworkProvider {
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index c19a906..90d821b 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -2399,6 +2399,11 @@
return mTransportInfo.getApplicableRedactions();
}
+ private NetworkCapabilities removeDefaultCapabilites() {
+ mNetworkCapabilities &= ~DEFAULT_CAPABILITIES;
+ return this;
+ }
+
/**
* Builder class for NetworkCapabilities.
*
@@ -2435,6 +2440,16 @@
}
/**
+ * Creates a new Builder without the default capabilities.
+ */
+ @NonNull
+ public static Builder withoutDefaultCapabilities() {
+ final NetworkCapabilities nc = new NetworkCapabilities();
+ nc.removeDefaultCapabilites();
+ return new Builder(nc);
+ }
+
+ /**
* Adds the given transport type.
*
* Multiple transports may be added. Note that when searching for a network to satisfy a
@@ -2495,17 +2510,6 @@
}
/**
- * Completely clears the contents of this object, removing even the capabilities that are
- * set by default when the object is constructed.
- * @return this builder
- */
- @NonNull
- public Builder clearAll() {
- mCaps.clearAll();
- return this;
- }
-
- /**
* Sets the owner UID.
*
* The default value is {@link Process#INVALID_UID}. Pass this value to reset.
diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java
index dd88c5a..e6a96ef 100644
--- a/framework/src/android/net/NetworkRequest.java
+++ b/framework/src/android/net/NetworkRequest.java
@@ -200,8 +200,9 @@
private final NetworkCapabilities mNetworkCapabilities;
- // A boolean that represents the user modified NOT_VCN_MANAGED capability.
- private boolean mModifiedNotVcnManaged = false;
+ // A boolean that represents whether the NOT_VCN_MANAGED capability should be deduced when
+ // the NetworkRequest object is built.
+ private boolean mShouldDeduceNotVcnManaged = true;
/**
* Default constructor for Builder.
@@ -223,7 +224,7 @@
// If the caller constructed the builder from a request, it means the user
// might explicitly want the capabilities from the request. Thus, the NOT_VCN_MANAGED
// capabilities should not be touched later.
- mModifiedNotVcnManaged = true;
+ mShouldDeduceNotVcnManaged = false;
}
/**
@@ -254,7 +255,7 @@
public Builder addCapability(@NetworkCapabilities.NetCapability int capability) {
mNetworkCapabilities.addCapability(capability);
if (capability == NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) {
- mModifiedNotVcnManaged = true;
+ mShouldDeduceNotVcnManaged = false;
}
return this;
}
@@ -268,7 +269,7 @@
public Builder removeCapability(@NetworkCapabilities.NetCapability int capability) {
mNetworkCapabilities.removeCapability(capability);
if (capability == NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) {
- mModifiedNotVcnManaged = true;
+ mShouldDeduceNotVcnManaged = false;
}
return this;
}
@@ -352,7 +353,7 @@
mNetworkCapabilities.clearAll();
// If the caller explicitly clear all capabilities, the NOT_VCN_MANAGED capabilities
// should not be add back later.
- mModifiedNotVcnManaged = true;
+ mShouldDeduceNotVcnManaged = false;
return this;
}
@@ -453,6 +454,9 @@
throw new IllegalArgumentException("A MatchAllNetworkSpecifier is not permitted");
}
mNetworkCapabilities.setNetworkSpecifier(networkSpecifier);
+ // Do not touch NOT_VCN_MANAGED if the caller needs to access to a very specific
+ // Network.
+ mShouldDeduceNotVcnManaged = false;
return this;
}
@@ -486,12 +490,13 @@
* {@link #VCN_SUPPORTED_CAPABILITIES}, add the NET_CAPABILITY_NOT_VCN_MANAGED to
* allow the callers automatically utilize VCN networks if available.
* 2. For the requests that explicitly add or remove NET_CAPABILITY_NOT_VCN_MANAGED,
+ * or has clear intention of tracking specific network,
* do not alter them to allow user fire request that suits their need.
*
* @hide
*/
private void deduceNotVcnManagedCapability(final NetworkCapabilities nc) {
- if (mModifiedNotVcnManaged) return;
+ if (!mShouldDeduceNotVcnManaged) return;
for (final int cap : nc.getCapabilities()) {
if (!VCN_SUPPORTED_CAPABILITIES.contains(cap)) return;
}
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 085943a..e192c8f 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -1045,14 +1045,10 @@
} else {
// ConnectivityService publishes binder service using publishBinderService() with
// no priority assigned will be treated as NORMAL priority. Dumpsys does not send
- // "--dump-priority" arguments to the service. Thus, dump both NORMAL and HIGH to
- // align the legacy design.
+ // "--dump-priority" arguments to the service. Thus, dump NORMAL only to align the
+ // legacy output for dumpsys connectivity.
// TODO: Integrate into signal dump.
dumpNormal(fd, pw, args);
- pw.println();
- pw.println("DUMP OF SERVICE HIGH connectivity");
- pw.println();
- dumpHigh(fd, pw);
}
}
}
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
index 506cadb..673c804 100644
--- a/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -24,6 +24,7 @@
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.net.ConnectivitySettingsManager.APPS_ALLOWED_ON_RESTRICTED_NETWORKS;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.os.Process.INVALID_UID;
import static android.os.Process.SYSTEM_UID;
@@ -39,6 +40,8 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.ContentObserver;
+import android.net.ConnectivitySettingsManager;
import android.net.INetd;
import android.net.UidRange;
import android.net.Uri;
@@ -48,7 +51,9 @@
import android.os.SystemConfigManager;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.system.OsConstants;
+import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -66,7 +71,6 @@
import java.util.Map.Entry;
import java.util.Set;
-
/**
* A utility class to inform Netd of UID permisisons.
* Does a mass update at boot and then monitors for app install/remove.
@@ -105,6 +109,14 @@
@GuardedBy("this")
private final Set<Integer> mAllApps = new HashSet<>();
+ // A set of apps which are allowed to use restricted networks. These apps can't hold the
+ // CONNECTIVITY_USE_RESTRICTED_NETWORKS permission because they can't be signature|privileged
+ // apps. However, these apps should still be able to use restricted networks under certain
+ // conditions (e.g. government app using emergency services). So grant netd system permission
+ // to uids whose package name is listed in APPS_ALLOWED_ON_RESTRICTED_NETWORKS setting.
+ @GuardedBy("this")
+ private final Set<String> mAppsAllowedOnRestrictedNetworks = new ArraySet<>();
+
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -135,6 +147,22 @@
public int getDeviceFirstSdkInt() {
return Build.VERSION.FIRST_SDK_INT;
}
+
+ /**
+ * Get apps allowed to use restricted networks via ConnectivitySettingsManager.
+ */
+ public Set<String> getAppsAllowedOnRestrictedNetworks(@NonNull Context context) {
+ return ConnectivitySettingsManager.getAppsAllowedOnRestrictedNetworks(context);
+ }
+
+ /**
+ * Register ContentObserver for given Uri.
+ */
+ public void registerContentObserver(@NonNull Context context, @NonNull Uri uri,
+ boolean notifyForDescendants, @NonNull ContentObserver observer) {
+ context.getContentResolver().registerContentObserver(
+ uri, notifyForDescendants, observer);
+ }
}
public PermissionMonitor(@NonNull final Context context, @NonNull final INetd netd) {
@@ -157,14 +185,31 @@
public synchronized void startMonitoring() {
log("Monitoring");
+ final Context userAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
intentFilter.addDataScheme("package");
- mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */).registerReceiver(
+ userAllContext.registerReceiver(
mIntentReceiver, intentFilter, null /* broadcastPermission */,
null /* scheduler */);
+ // Register APPS_ALLOWED_ON_RESTRICTED_NETWORKS setting observer
+ mDeps.registerContentObserver(
+ userAllContext,
+ Settings.Secure.getUriFor(APPS_ALLOWED_ON_RESTRICTED_NETWORKS),
+ false /* notifyForDescendants */,
+ new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ onSettingChanged();
+ }
+ });
+
+ // Read APPS_ALLOWED_ON_RESTRICTED_NETWORKS setting and update
+ // mAppsAllowedOnRestrictedNetworks.
+ updateAppsAllowedOnRestrictedNetworks(mDeps.getAppsAllowedOnRestrictedNetworks(mContext));
+
List<PackageInfo> apps = mPackageManager.getInstalledPackages(GET_PERMISSIONS
| MATCH_ANY_USER);
if (apps == null) {
@@ -220,11 +265,33 @@
}
@VisibleForTesting
+ void updateAppsAllowedOnRestrictedNetworks(final Set<String> apps) {
+ mAppsAllowedOnRestrictedNetworks.clear();
+ mAppsAllowedOnRestrictedNetworks.addAll(apps);
+ }
+
+ @VisibleForTesting
static boolean isVendorApp(@NonNull ApplicationInfo appInfo) {
return appInfo.isVendor() || appInfo.isOem() || appInfo.isProduct();
}
@VisibleForTesting
+ boolean isCarryoverPackage(final ApplicationInfo appInfo) {
+ if (appInfo == null) return false;
+ return (appInfo.targetSdkVersion < VERSION_Q && isVendorApp(appInfo))
+ // Backward compatibility for b/114245686, on devices that launched before Q daemons
+ // and apps running as the system UID are exempted from this check.
+ || (appInfo.uid == SYSTEM_UID && mDeps.getDeviceFirstSdkInt() < VERSION_Q);
+ }
+
+ @VisibleForTesting
+ boolean isAppAllowedOnRestrictedNetworks(@NonNull final PackageInfo app) {
+ // Check whether package name is in allowed on restricted networks app list. If so, this app
+ // can have netd system permission.
+ return mAppsAllowedOnRestrictedNetworks.contains(app.packageName);
+ }
+
+ @VisibleForTesting
boolean hasPermission(@NonNull final PackageInfo app, @NonNull final String permission) {
if (app.requestedPermissions == null || app.requestedPermissionsFlags == null) {
return false;
@@ -241,22 +308,10 @@
@VisibleForTesting
boolean hasRestrictedNetworkPermission(@NonNull final PackageInfo app) {
- // TODO : remove this check in the future(b/31479477). All apps should just
- // request the appropriate permission for their use case since android Q.
- if (app.applicationInfo != null) {
- // Backward compatibility for b/114245686, on devices that launched before Q daemons
- // and apps running as the system UID are exempted from this check.
- if (app.applicationInfo.uid == SYSTEM_UID && mDeps.getDeviceFirstSdkInt() < VERSION_Q) {
- return true;
- }
-
- if (app.applicationInfo.targetSdkVersion < VERSION_Q
- && isVendorApp(app.applicationInfo)) {
- return true;
- }
- }
-
- return hasPermission(app, PERMISSION_MAINLINE_NETWORK_STACK)
+ // TODO : remove carryover package check in the future(b/31479477). All apps should just
+ // request the appropriate permission for their use case since android Q.
+ return isCarryoverPackage(app.applicationInfo) || isAppAllowedOnRestrictedNetworks(app)
+ || hasPermission(app, PERMISSION_MAINLINE_NETWORK_STACK)
|| hasPermission(app, NETWORK_STACK)
|| hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS);
}
@@ -410,6 +465,20 @@
mAllApps.add(UserHandle.getAppId(uid));
}
+ private Boolean highestUidNetworkPermission(int uid) {
+ Boolean permission = null;
+ final String[] packages = mPackageManager.getPackagesForUid(uid);
+ if (!CollectionUtils.isEmpty(packages)) {
+ for (String name : packages) {
+ permission = highestPermissionForUid(permission, name);
+ if (permission == SYSTEM) {
+ break;
+ }
+ }
+ }
+ return permission;
+ }
+
/**
* Called when a package is removed.
*
@@ -440,19 +509,14 @@
}
Map<Integer, Boolean> apps = new HashMap<>();
- Boolean permission = null;
- String[] packages = mPackageManager.getPackagesForUid(uid);
- if (packages != null && packages.length > 0) {
- for (String name : packages) {
- permission = highestPermissionForUid(permission, name);
- if (permission == SYSTEM) {
- // An app with this UID still has the SYSTEM permission.
- // Therefore, this UID must already have the SYSTEM permission.
- // Nothing to do.
- return;
- }
- }
+ final Boolean permission = highestUidNetworkPermission(uid);
+ if (permission == SYSTEM) {
+ // An app with this UID still has the SYSTEM permission.
+ // Therefore, this UID must already have the SYSTEM permission.
+ // Nothing to do.
+ return;
}
+
if (permission == mApps.get(uid)) {
// The permissions of this UID have not changed. Nothing to do.
return;
@@ -705,6 +769,38 @@
return mVpnUidRanges.get(iface);
}
+ private synchronized void onSettingChanged() {
+ // Step1. Update apps allowed to use restricted networks and compute the set of packages to
+ // update.
+ final Set<String> packagesToUpdate = new ArraySet<>(mAppsAllowedOnRestrictedNetworks);
+ updateAppsAllowedOnRestrictedNetworks(mDeps.getAppsAllowedOnRestrictedNetworks(mContext));
+ packagesToUpdate.addAll(mAppsAllowedOnRestrictedNetworks);
+
+ final Map<Integer, Boolean> updatedApps = new HashMap<>();
+ final Map<Integer, Boolean> removedApps = new HashMap<>();
+
+ // Step2. For each package to update, find out its new permission.
+ for (String app : packagesToUpdate) {
+ final PackageInfo info = getPackageInfo(app);
+ if (info == null || info.applicationInfo == null) continue;
+
+ final int uid = info.applicationInfo.uid;
+ final Boolean permission = highestUidNetworkPermission(uid);
+
+ if (null == permission) {
+ removedApps.put(uid, NETWORK); // Doesn't matter which permission is set here.
+ mApps.remove(uid);
+ } else {
+ updatedApps.put(uid, permission);
+ mApps.put(uid, permission);
+ }
+ }
+
+ // Step3. Update or revoke permission for uids with netd.
+ update(mUsers, updatedApps, true /* add */);
+ update(mUsers, removedApps, false /* add */);
+ }
+
/** Dump info to dumpsys */
public void dump(IndentingPrintWriter pw) {
pw.println("Interface filtering rules:");
diff --git a/tests/unit/java/android/net/NetworkTemplateTest.kt b/tests/unit/java/android/net/NetworkTemplateTest.kt
index ab6b2f4..cb39a0c 100644
--- a/tests/unit/java/android/net/NetworkTemplateTest.kt
+++ b/tests/unit/java/android/net/NetworkTemplateTest.kt
@@ -40,7 +40,7 @@
import android.net.NetworkTemplate.SUBSCRIBER_ID_MATCH_RULE_EXACT
import android.net.NetworkTemplate.buildTemplateWifi
import android.net.NetworkTemplate.buildTemplateWifiWildcard
-import android.net.NetworkTemplate.buildTemplateCarrier
+import android.net.NetworkTemplate.buildTemplateCarrierMetered
import android.net.NetworkTemplate.buildTemplateMobileWithRatType
import android.telephony.TelephonyManager
import com.android.testutils.assertParcelSane
@@ -73,11 +73,12 @@
type: Int,
subscriberId: String? = null,
ssid: String? = null,
- oemManaged: Int = OEM_NONE
+ oemManaged: Int = OEM_NONE,
+ metered: Boolean = true
): NetworkStateSnapshot {
val lp = LinkProperties()
val caps = NetworkCapabilities().apply {
- setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, false)
+ setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, !metered)
setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true)
setSSID(ssid)
setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID,
@@ -167,25 +168,38 @@
}
@Test
- fun testCarrierMatches() {
- val templateCarrierImsi1 = buildTemplateCarrier(TEST_IMSI1)
+ fun testCarrierMeteredMatches() {
+ val templateCarrierImsi1Metered = buildTemplateCarrierMetered(TEST_IMSI1)
- val identMobile1 = buildNetworkIdentity(mockContext, buildMobileNetworkState(TEST_IMSI1),
- false, TelephonyManager.NETWORK_TYPE_UMTS)
- val identMobile2 = buildNetworkIdentity(mockContext, buildMobileNetworkState(TEST_IMSI2),
- false, TelephonyManager.NETWORK_TYPE_UMTS)
- val identWifiSsid1 = buildNetworkIdentity(
- mockContext, buildWifiNetworkState(null, TEST_SSID1), true, 0)
- val identCarrierWifiImsi1 = buildNetworkIdentity(
- mockContext, buildWifiNetworkState(TEST_IMSI1, TEST_SSID1), true, 0)
- val identCarrierWifiImsi2 = buildNetworkIdentity(
- mockContext, buildWifiNetworkState(TEST_IMSI2, TEST_SSID1), true, 0)
+ val mobileImsi1 = buildMobileNetworkState(TEST_IMSI1)
+ val mobileImsi1Unmetered = buildNetworkState(TYPE_MOBILE, TEST_IMSI1, null /* ssid */,
+ OEM_NONE, false /* metered */)
+ val mobileImsi2 = buildMobileNetworkState(TEST_IMSI2)
+ val wifiSsid1 = buildWifiNetworkState(null /* subscriberId */, TEST_SSID1)
+ val wifiImsi1Ssid1 = buildWifiNetworkState(TEST_IMSI1, TEST_SSID1)
+ val wifiImsi1Ssid1Unmetered = buildNetworkState(TYPE_WIFI, TEST_IMSI1, TEST_SSID1,
+ OEM_NONE, false /* metered */)
- templateCarrierImsi1.assertMatches(identCarrierWifiImsi1)
- templateCarrierImsi1.assertDoesNotMatch(identCarrierWifiImsi2)
- templateCarrierImsi1.assertDoesNotMatch(identWifiSsid1)
- templateCarrierImsi1.assertMatches(identMobile1)
- templateCarrierImsi1.assertDoesNotMatch(identMobile2)
+ val identMobileImsi1Metered = buildNetworkIdentity(mockContext,
+ mobileImsi1, false /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS)
+ val identMobileImsi1Unmetered = buildNetworkIdentity(mockContext,
+ mobileImsi1Unmetered, false /* defaultNetwork */,
+ TelephonyManager.NETWORK_TYPE_UMTS)
+ val identMobileImsi2Metered = buildNetworkIdentity(mockContext,
+ mobileImsi2, false /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS)
+ val identWifiSsid1Metered = buildNetworkIdentity(
+ mockContext, wifiSsid1, true /* defaultNetwork */, 0 /* subType */)
+ val identCarrierWifiImsi1Metered = buildNetworkIdentity(
+ mockContext, wifiImsi1Ssid1, true /* defaultNetwork */, 0 /* subType */)
+ val identCarrierWifiImsi1NonMetered = buildNetworkIdentity(mockContext,
+ wifiImsi1Ssid1Unmetered, true /* defaultNetwork */, 0 /* subType */)
+
+ templateCarrierImsi1Metered.assertMatches(identMobileImsi1Metered)
+ templateCarrierImsi1Metered.assertDoesNotMatch(identMobileImsi1Unmetered)
+ templateCarrierImsi1Metered.assertDoesNotMatch(identMobileImsi2Metered)
+ templateCarrierImsi1Metered.assertDoesNotMatch(identWifiSsid1Metered)
+ templateCarrierImsi1Metered.assertMatches(identCarrierWifiImsi1Metered)
+ templateCarrierImsi1Metered.assertDoesNotMatch(identCarrierWifiImsi1NonMetered)
}
@Test
diff --git a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
index 02a5808..c75618f 100644
--- a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -30,6 +30,8 @@
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_REQUIRED;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.net.ConnectivitySettingsManager.APPS_ALLOWED_ON_RESTRICTED_NETWORKS;
+import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.os.Process.SYSTEM_UID;
import static com.android.server.connectivity.PermissionMonitor.NETWORK;
@@ -43,8 +45,10 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.AdditionalMatchers.aryEq;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
@@ -61,6 +65,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.database.ContentObserver;
import android.net.INetd;
import android.net.UidRange;
import android.net.Uri;
@@ -68,6 +73,7 @@
import android.os.SystemConfigManager;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.ArraySet;
import android.util.SparseIntArray;
import androidx.test.InstrumentationRegistry;
@@ -136,6 +142,7 @@
final Context asUserCtx = mock(Context.class, AdditionalAnswers.delegatesTo(mContext));
doReturn(UserHandle.ALL).when(asUserCtx).getUser();
when(mContext.createContextAsUser(eq(UserHandle.ALL), anyInt())).thenReturn(asUserCtx);
+ when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(new ArraySet<>());
mPermissionMonitor = spy(new PermissionMonitor(mContext, mNetdService, mDeps));
@@ -145,8 +152,15 @@
private boolean hasRestrictedNetworkPermission(String partition, int targetSdkVersion, int uid,
String... permissions) {
+ return hasRestrictedNetworkPermission(
+ partition, targetSdkVersion, "" /* packageName */, uid, permissions);
+ }
+
+ private boolean hasRestrictedNetworkPermission(String partition, int targetSdkVersion,
+ String packageName, int uid, String... permissions) {
final PackageInfo packageInfo =
packageInfoWithPermissions(REQUESTED_PERMISSION_GRANTED, permissions, partition);
+ packageInfo.packageName = packageName;
packageInfo.applicationInfo.targetSdkVersion = targetSdkVersion;
packageInfo.applicationInfo.uid = uid;
return mPermissionMonitor.hasRestrictedNetworkPermission(packageInfo);
@@ -280,6 +294,8 @@
PARTITION_SYSTEM, VERSION_P, MOCK_UID1, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
assertFalse(hasRestrictedNetworkPermission(
PARTITION_SYSTEM, VERSION_P, MOCK_UID1, CHANGE_WIFI_STATE));
+ assertTrue(hasRestrictedNetworkPermission(
+ PARTITION_SYSTEM, VERSION_P, MOCK_UID1, PERMISSION_MAINLINE_NETWORK_STACK));
assertFalse(hasRestrictedNetworkPermission(PARTITION_SYSTEM, VERSION_Q, MOCK_UID1));
assertFalse(hasRestrictedNetworkPermission(
@@ -324,6 +340,90 @@
PARTITION_VENDOR, VERSION_Q, MOCK_UID1, CHANGE_NETWORK_STATE));
}
+ @Test
+ public void testHasRestrictedNetworkPermissionAppAllowedOnRestrictedNetworks() {
+ mPermissionMonitor.updateAppsAllowedOnRestrictedNetworks(
+ new ArraySet<>(new String[] { MOCK_PACKAGE1 }));
+ assertTrue(hasRestrictedNetworkPermission(
+ PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE1, MOCK_UID1));
+ assertTrue(hasRestrictedNetworkPermission(
+ PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE1, MOCK_UID1, CHANGE_NETWORK_STATE));
+ assertTrue(hasRestrictedNetworkPermission(
+ PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE1, MOCK_UID1, CONNECTIVITY_INTERNAL));
+
+ assertFalse(hasRestrictedNetworkPermission(
+ PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE2, MOCK_UID1));
+ assertFalse(hasRestrictedNetworkPermission(
+ PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE2, MOCK_UID1, CHANGE_NETWORK_STATE));
+ assertFalse(hasRestrictedNetworkPermission(
+ PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE2, MOCK_UID1, CONNECTIVITY_INTERNAL));
+
+ }
+
+ private boolean wouldBeCarryoverPackage(String partition, int targetSdkVersion, int uid) {
+ final PackageInfo packageInfo = packageInfoWithPermissions(
+ REQUESTED_PERMISSION_GRANTED, new String[] {}, partition);
+ packageInfo.applicationInfo.targetSdkVersion = targetSdkVersion;
+ packageInfo.applicationInfo.uid = uid;
+ return mPermissionMonitor.isCarryoverPackage(packageInfo.applicationInfo);
+ }
+
+ @Test
+ public void testIsCarryoverPackage() {
+ doReturn(VERSION_P).when(mDeps).getDeviceFirstSdkInt();
+ assertTrue(wouldBeCarryoverPackage(PARTITION_SYSTEM, VERSION_P, SYSTEM_UID));
+ assertTrue(wouldBeCarryoverPackage(PARTITION_VENDOR, VERSION_P, SYSTEM_UID));
+ assertFalse(wouldBeCarryoverPackage(PARTITION_SYSTEM, VERSION_P, MOCK_UID1));
+ assertTrue(wouldBeCarryoverPackage(PARTITION_VENDOR, VERSION_P, MOCK_UID1));
+ assertTrue(wouldBeCarryoverPackage(PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID));
+ assertTrue(wouldBeCarryoverPackage(PARTITION_VENDOR, VERSION_Q, SYSTEM_UID));
+ assertFalse(wouldBeCarryoverPackage(PARTITION_SYSTEM, VERSION_Q, MOCK_UID1));
+ assertFalse(wouldBeCarryoverPackage(PARTITION_VENDOR, VERSION_Q, MOCK_UID1));
+
+ doReturn(VERSION_Q).when(mDeps).getDeviceFirstSdkInt();
+ assertFalse(wouldBeCarryoverPackage(PARTITION_SYSTEM, VERSION_P, SYSTEM_UID));
+ assertTrue(wouldBeCarryoverPackage(PARTITION_VENDOR, VERSION_P, SYSTEM_UID));
+ assertFalse(wouldBeCarryoverPackage(PARTITION_SYSTEM, VERSION_P, MOCK_UID1));
+ assertTrue(wouldBeCarryoverPackage(PARTITION_VENDOR, VERSION_P, MOCK_UID1));
+ assertFalse(wouldBeCarryoverPackage(PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID));
+ assertFalse(wouldBeCarryoverPackage(PARTITION_VENDOR, VERSION_Q, SYSTEM_UID));
+ assertFalse(wouldBeCarryoverPackage(PARTITION_SYSTEM, VERSION_Q, MOCK_UID1));
+ assertFalse(wouldBeCarryoverPackage(PARTITION_VENDOR, VERSION_Q, MOCK_UID1));
+
+ assertFalse(wouldBeCarryoverPackage(PARTITION_OEM, VERSION_Q, SYSTEM_UID));
+ assertFalse(wouldBeCarryoverPackage(PARTITION_PRODUCT, VERSION_Q, SYSTEM_UID));
+ assertFalse(wouldBeCarryoverPackage(PARTITION_OEM, VERSION_Q, MOCK_UID1));
+ assertFalse(wouldBeCarryoverPackage(PARTITION_PRODUCT, VERSION_Q, MOCK_UID1));
+ }
+
+ private boolean wouldBeAppAllowedOnRestrictedNetworks(String packageName) {
+ final PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = packageName;
+ return mPermissionMonitor.isAppAllowedOnRestrictedNetworks(packageInfo);
+ }
+
+ @Test
+ public void testIsAppAllowedOnRestrictedNetworks() {
+ mPermissionMonitor.updateAppsAllowedOnRestrictedNetworks(new ArraySet<>());
+ assertFalse(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE1));
+ assertFalse(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE2));
+
+ mPermissionMonitor.updateAppsAllowedOnRestrictedNetworks(
+ new ArraySet<>(new String[] { MOCK_PACKAGE1 }));
+ assertTrue(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE1));
+ assertFalse(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE2));
+
+ mPermissionMonitor.updateAppsAllowedOnRestrictedNetworks(
+ new ArraySet<>(new String[] { MOCK_PACKAGE2 }));
+ assertFalse(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE1));
+ assertTrue(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE2));
+
+ mPermissionMonitor.updateAppsAllowedOnRestrictedNetworks(
+ new ArraySet<>(new String[] { "com.android.test" }));
+ assertFalse(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE1));
+ assertFalse(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE2));
+ }
+
private void assertBackgroundPermission(boolean hasPermission, String name, int uid,
String... permissions) throws Exception {
when(mPackageManager.getPackageInfo(eq(name), anyInt()))
@@ -800,4 +900,102 @@
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UNINSTALLED, new int[] { MOCK_UID1 });
}
-}
+ @Test
+ public void testAppsAllowedOnRestrictedNetworksChanged() throws Exception {
+ final NetdMonitor mNetdMonitor = new NetdMonitor(mNetdService);
+ final ArgumentCaptor<ContentObserver> captor =
+ ArgumentCaptor.forClass(ContentObserver.class);
+ verify(mDeps, times(1)).registerContentObserver(any(),
+ argThat(uri -> uri.getEncodedPath().contains(APPS_ALLOWED_ON_RESTRICTED_NETWORKS)),
+ anyBoolean(), captor.capture());
+ final ContentObserver contentObserver = captor.getValue();
+
+ mPermissionMonitor.onUserAdded(MOCK_USER1);
+ // Prepare PackageInfo for MOCK_PACKAGE1
+ final PackageInfo packageInfo = buildPackageInfo(
+ false /* hasSystemPermission */, MOCK_UID1, MOCK_USER1);
+ packageInfo.packageName = MOCK_PACKAGE1;
+ when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE1), anyInt())).thenReturn(packageInfo);
+ when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{MOCK_PACKAGE1});
+ // Prepare PackageInfo for MOCK_PACKAGE2
+ final PackageInfo packageInfo2 = buildPackageInfo(
+ false /* hasSystemPermission */, MOCK_UID2, MOCK_USER1);
+ packageInfo2.packageName = MOCK_PACKAGE2;
+ when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE2), anyInt())).thenReturn(packageInfo2);
+ when(mPackageManager.getPackagesForUid(MOCK_UID2)).thenReturn(new String[]{MOCK_PACKAGE2});
+
+ // MOCK_PACKAGE1 is listed in setting that allow to use restricted networks, MOCK_UID1
+ // should have SYSTEM permission.
+ when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(
+ new ArraySet<>(new String[] { MOCK_PACKAGE1 }));
+ contentObserver.onChange(true /* selfChange */);
+ mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
+ mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID2});
+
+ // MOCK_PACKAGE2 is listed in setting that allow to use restricted networks, MOCK_UID2
+ // should have SYSTEM permission but MOCK_UID1 should revoke permission.
+ when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(
+ new ArraySet<>(new String[] { MOCK_PACKAGE2 }));
+ contentObserver.onChange(true /* selfChange */);
+ mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID2});
+ mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
+
+ // No app lists in setting, should revoke permission from all uids.
+ when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(new ArraySet<>());
+ contentObserver.onChange(true /* selfChange */);
+ mNetdMonitor.expectNoPermission(
+ new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1, MOCK_UID2});
+ }
+
+ @Test
+ public void testAppsAllowedOnRestrictedNetworksChangedWithSharedUid() throws Exception {
+ final NetdMonitor mNetdMonitor = new NetdMonitor(mNetdService);
+ final ArgumentCaptor<ContentObserver> captor =
+ ArgumentCaptor.forClass(ContentObserver.class);
+ verify(mDeps, times(1)).registerContentObserver(any(),
+ argThat(uri -> uri.getEncodedPath().contains(APPS_ALLOWED_ON_RESTRICTED_NETWORKS)),
+ anyBoolean(), captor.capture());
+ final ContentObserver contentObserver = captor.getValue();
+
+ mPermissionMonitor.onUserAdded(MOCK_USER1);
+ // Prepare PackageInfo for MOCK_PACKAGE1 and MOCK_PACKAGE2 with shared uid MOCK_UID1.
+ final PackageInfo packageInfo = systemPackageInfoWithPermissions(CHANGE_NETWORK_STATE);
+ packageInfo.applicationInfo.uid = MOCK_USER1.getUid(MOCK_UID1);
+ packageInfo.packageName = MOCK_PACKAGE1;
+ final PackageInfo packageInfo2 = buildPackageInfo(
+ false /* hasSystemPermission */, MOCK_UID1, MOCK_USER1);
+ packageInfo2.packageName = MOCK_PACKAGE2;
+ when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE1), anyInt())).thenReturn(packageInfo);
+ when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE2), anyInt())).thenReturn(packageInfo2);
+ when(mPackageManager.getPackagesForUid(MOCK_UID1))
+ .thenReturn(new String[]{MOCK_PACKAGE1, MOCK_PACKAGE2});
+
+ // MOCK_PACKAGE1 have CHANGE_NETWORK_STATE, MOCK_UID1 should have NETWORK permission.
+ addPackageForUsers(new UserHandle[]{MOCK_USER1}, MOCK_PACKAGE1, MOCK_UID1);
+ mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
+
+ // MOCK_PACKAGE2 is listed in setting that allow to use restricted networks, MOCK_UID1
+ // should upgrade to SYSTEM permission.
+ when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(
+ new ArraySet<>(new String[] { MOCK_PACKAGE2 }));
+ contentObserver.onChange(true /* selfChange */);
+ mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
+
+ // MOCK_PACKAGE1 is listed in setting that allow to use restricted networks, MOCK_UID1
+ // should still have SYSTEM permission.
+ when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(
+ new ArraySet<>(new String[] { MOCK_PACKAGE1 }));
+ contentObserver.onChange(true /* selfChange */);
+ mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
+
+ // No app lists in setting, MOCK_UID1 should downgrade to NETWORK permission.
+ when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(new ArraySet<>());
+ contentObserver.onChange(true /* selfChange */);
+ mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
+
+ // MOCK_PACKAGE1 removed, should revoke permission from MOCK_UID1.
+ when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{MOCK_PACKAGE2});
+ removePackageForUsers(new UserHandle[]{MOCK_USER1}, MOCK_PACKAGE1, MOCK_UID1);
+ mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
+ }
+}
\ No newline at end of file