Merge "Add more unit tests for ConnectivityManager S APIs" into sc-dev
diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java
index a71dbbf..afc76d6 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;
         }
@@ -357,7 +358,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;
         }
 
@@ -458,6 +459,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;
         }
 
@@ -491,12 +495,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 daf231f..6027a99 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -1497,12 +1497,7 @@
                 ConnectivitySettingsManager.WIFI_ALWAYS_REQUESTED, false /* defaultValue */);
         final boolean vehicleAlwaysRequested = mResources.get().getBoolean(
                 R.bool.config_vehicleInternalNetworkAlwaysRequested);
-        // TODO (b/183076074): remove legacy fallback after migrating overlays
-        final boolean legacyAlwaysRequested = mContext.getResources().getBoolean(
-                mContext.getResources().getIdentifier(
-                        "config_vehicleInternalNetworkAlwaysRequested", "bool", "android"));
-        handleAlwaysOnNetworkRequest(mDefaultVehicleRequest,
-                vehicleAlwaysRequested || legacyAlwaysRequested);
+        handleAlwaysOnNetworkRequest(mDefaultVehicleRequest, vehicleAlwaysRequested);
     }
 
     private void registerSettingsCallbacks() {
@@ -2218,7 +2213,7 @@
     @NonNull
     public List<NetworkStateSnapshot> getAllNetworkStateSnapshots() {
         // This contains IMSI details, so make sure the caller is privileged.
-        PermissionUtils.enforceNetworkStackPermission(mContext);
+        enforceNetworkStackOrSettingsPermission();
 
         final ArrayList<NetworkStateSnapshot> result = new ArrayList<>();
         for (Network network : getAllNetworks()) {
@@ -6823,14 +6818,6 @@
         int mark = mResources.get().getInteger(R.integer.config_networkWakeupPacketMark);
         int mask = mResources.get().getInteger(R.integer.config_networkWakeupPacketMask);
 
-        // TODO (b/183076074): remove legacy fallback after migrating overlays
-        final int legacyMark = mContext.getResources().getInteger(mContext.getResources()
-                .getIdentifier("config_networkWakeupPacketMark", "integer", "android"));
-        final int legacyMask = mContext.getResources().getInteger(mContext.getResources()
-                .getIdentifier("config_networkWakeupPacketMask", "integer", "android"));
-        mark = mark == 0 ? legacyMark : mark;
-        mask = mask == 0 ? legacyMask : mask;
-
         // Mask/mark of zero will not detect anything interesting.
         // Don't install rules unless both values are nonzero.
         if (mark == 0 || mask == 0) {
@@ -8584,11 +8571,7 @@
         final UnderlyingNetworkInfo[] underlyingNetworkInfos = getAllVpnInfo();
         try {
             final ArrayList<NetworkStateSnapshot> snapshots = new ArrayList<>();
-            // TODO: Directly use NetworkStateSnapshot when feasible.
-            for (final NetworkState state : getAllNetworkState()) {
-                final NetworkStateSnapshot snapshot = new NetworkStateSnapshot(state.network,
-                        state.networkCapabilities, state.linkProperties, state.subscriberId,
-                        state.legacyNetworkType);
+            for (final NetworkStateSnapshot snapshot : getAllNetworkStateSnapshots()) {
                 snapshots.add(snapshot);
             }
             mStatsManager.notifyNetworkStatus(getDefaultNetworks(),
diff --git a/service/src/com/android/server/connectivity/KeepaliveTracker.java b/service/src/com/android/server/connectivity/KeepaliveTracker.java
index acf39f0..7d922a4 100644
--- a/service/src/com/android/server/connectivity/KeepaliveTracker.java
+++ b/service/src/com/android/server/connectivity/KeepaliveTracker.java
@@ -114,18 +114,11 @@
         mContext = context;
         mSupportedKeepalives = KeepaliveUtils.getSupportedKeepalives(mContext);
 
-        // TODO (b/183076074): stop reading legacy resources after migrating overlays
-        final int legacyReservedSlots = mContext.getResources().getInteger(
-                mContext.getResources().getIdentifier(
-                        "config_reservedPrivilegedKeepaliveSlots", "integer", "android"));
-        final int legacyAllowedSlots = mContext.getResources().getInteger(
-                mContext.getResources().getIdentifier(
-                        "config_allowedUnprivilegedKeepalivePerUid", "integer", "android"));
         final ConnectivityResources res = new ConnectivityResources(mContext);
-        mReservedPrivilegedSlots = Math.min(legacyReservedSlots, res.get().getInteger(
-                R.integer.config_reservedPrivilegedKeepaliveSlots));
-        mAllowedUnprivilegedSlotsForUid = Math.min(legacyAllowedSlots, res.get().getInteger(
-                R.integer.config_allowedUnprivilegedKeepalivePerUid));
+        mReservedPrivilegedSlots = res.get().getInteger(
+                R.integer.config_reservedPrivilegedKeepaliveSlots);
+        mAllowedUnprivilegedSlotsForUid = res.get().getInteger(
+                R.integer.config_allowedUnprivilegedKeepalivePerUid);
     }
 
     /**
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
index 5886b1a..9bda59c 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.DEVICE_INITIAL_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
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index fd374bc..c32c1d2 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -152,6 +152,8 @@
     private static final String TEST_SSID = "AndroidAP";
 
     private static NetworkTemplate sTemplateWifi = buildTemplateWifi(TEST_SSID);
+    private static NetworkTemplate sTemplateCarrierWifi1 =
+            buildTemplateWifi(NetworkTemplate.WIFI_NETWORKID_ALL, IMSI_1);
     private static NetworkTemplate sTemplateImsi1 = buildTemplateMobileAll(IMSI_1);
     private static NetworkTemplate sTemplateImsi2 = buildTemplateMobileAll(IMSI_2);
 
@@ -297,45 +299,82 @@
         mHandlerThread.quitSafely();
     }
 
-    @Test
-    public void testNetworkStatsWifi() throws Exception {
+    private void initWifiStats(NetworkStateSnapshot snapshot) throws Exception {
         // pretend that wifi network comes online; service should ask about full
         // network state, and poll any existing interfaces before updating.
         expectDefaultSettings();
-        NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()};
+        NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {snapshot};
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states),
+        mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
                 new UnderlyingNetworkInfo[0]);
+    }
 
-        // verify service has empty history for wifi
+    private void incrementWifiStats(long durationMillis, String iface,
+            long rxb, long rxp, long txb, long txp) throws Exception {
+        incrementCurrentTime(durationMillis);
+        expectDefaultSettings();
+        expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+                .insertEntry(iface, rxb, rxp, txb, txp));
+        expectNetworkStatsUidDetail(buildEmptyStats());
+        forcePollAndWaitForIdle();
+    }
+
+    @Test
+    public void testNetworkStatsCarrierWifi() throws Exception {
+        initWifiStats(buildWifiState(true, TEST_IFACE, IMSI_1));
+        // verify service has empty history for carrier merged wifi and non-carrier wifi
+        assertNetworkTotal(sTemplateCarrierWifi1, 0L, 0L, 0L, 0L, 0);
         assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
 
         // modify some number on wifi, and trigger poll event
-        incrementCurrentTime(HOUR_IN_MILLIS);
-        expectDefaultSettings();
-        expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
-                .insertEntry(TEST_IFACE, 1024L, 1L, 2048L, 2L));
-        expectNetworkStatsUidDetail(buildEmptyStats());
-        forcePollAndWaitForIdle();
+        incrementWifiStats(HOUR_IN_MILLIS, TEST_IFACE, 1024L, 1L, 2048L, 2L);
 
         // verify service recorded history
-        assertNetworkTotal(sTemplateWifi, 1024L, 1L, 2048L, 2L, 0);
+        assertNetworkTotal(sTemplateCarrierWifi1, 1024L, 1L, 2048L, 2L, 0);
+
+        // verify service recorded history for wifi with SSID filter
+        assertNetworkTotal(sTemplateWifi,  1024L, 1L, 2048L, 2L, 0);
 
 
         // and bump forward again, with counters going higher. this is
         // important, since polling should correctly subtract last snapshot.
-        incrementCurrentTime(DAY_IN_MILLIS);
-        expectDefaultSettings();
-        expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
-                .insertEntry(TEST_IFACE, 4096L, 4L, 8192L, 8L));
-        expectNetworkStatsUidDetail(buildEmptyStats());
-        forcePollAndWaitForIdle();
+        incrementWifiStats(DAY_IN_MILLIS, TEST_IFACE, 4096L, 4L, 8192L, 8L);
+
+        // verify service recorded history
+        assertNetworkTotal(sTemplateCarrierWifi1, 4096L, 4L, 8192L, 8L, 0);
+        // verify service recorded history for wifi with SSID filter
+        assertNetworkTotal(sTemplateWifi, 4096L, 4L, 8192L, 8L, 0);
+    }
+
+    @Test
+    public void testNetworkStatsNonCarrierWifi() throws Exception {
+        initWifiStats(buildWifiState());
+
+        // verify service has empty history for wifi
+        assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
+        // verify service has empty history for carrier merged wifi
+        assertNetworkTotal(sTemplateCarrierWifi1, 0L, 0L, 0L, 0L, 0);
+
+        // modify some number on wifi, and trigger poll event
+        incrementWifiStats(HOUR_IN_MILLIS, TEST_IFACE, 1024L, 1L, 2048L, 2L);
+
+        // verify service recorded history
+        assertNetworkTotal(sTemplateWifi, 1024L, 1L, 2048L, 2L, 0);
+        // verify service has empty history for carrier wifi since current network is non carrier
+        // wifi
+        assertNetworkTotal(sTemplateCarrierWifi1, 0L, 0L, 0L, 0L, 0);
+
+        // and bump forward again, with counters going higher. this is
+        // important, since polling should correctly subtract last snapshot.
+        incrementWifiStats(DAY_IN_MILLIS, TEST_IFACE, 4096L, 4L, 8192L, 8L);
 
         // verify service recorded history
         assertNetworkTotal(sTemplateWifi, 4096L, 4L, 8192L, 8L, 0);
-
+        // verify service has empty history for carrier wifi since current network is non carrier
+        // wifi
+        assertNetworkTotal(sTemplateCarrierWifi1, 0L, 0L, 0L, 0L, 0);
     }
 
     @Test
@@ -349,7 +388,7 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states),
+        mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
                 new UnderlyingNetworkInfo[0]);
 
         // verify service has empty history for wifi
@@ -423,7 +462,7 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states),
+        mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
                 new UnderlyingNetworkInfo[0]);
 
         // modify some number on wifi, and trigger poll event
@@ -464,7 +503,7 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states),
+        mService.notifyNetworkStatus(NETWORKS_MOBILE, states, getActiveIface(states),
                 new UnderlyingNetworkInfo[0]);
 
         // create some traffic on first network
@@ -499,7 +538,7 @@
                 .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
                 .insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
 
-        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states),
+        mService.notifyNetworkStatus(NETWORKS_MOBILE, states, getActiveIface(states),
                 new UnderlyingNetworkInfo[0]);
         forcePollAndWaitForIdle();
 
@@ -539,7 +578,7 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states),
+        mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
                 new UnderlyingNetworkInfo[0]);
 
         // create some traffic
@@ -607,7 +646,7 @@
         expectNetworkStatsUidDetail(buildEmptyStats());
 
         setMobileRatTypeAndWaitForIdle(TelephonyManager.NETWORK_TYPE_UMTS);
-        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states),
+        mService.notifyNetworkStatus(NETWORKS_MOBILE, states, getActiveIface(states),
                 new UnderlyingNetworkInfo[0]);
 
         // Create some traffic.
@@ -699,7 +738,7 @@
                 new int[]{NetworkCapabilities.NET_CAPABILITY_OEM_PAID})};
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states),
+        mService.notifyNetworkStatus(NETWORKS_MOBILE, states, getActiveIface(states),
                 new UnderlyingNetworkInfo[0]);
 
         // Create some traffic.
@@ -714,7 +753,7 @@
                 new int[]{NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE})};
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states),
+        mService.notifyNetworkStatus(NETWORKS_MOBILE, states, getActiveIface(states),
                 new UnderlyingNetworkInfo[0]);
 
         // Create some traffic.
@@ -730,7 +769,7 @@
                           NetworkCapabilities.NET_CAPABILITY_OEM_PAID})};
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states),
+        mService.notifyNetworkStatus(NETWORKS_MOBILE, states, getActiveIface(states),
                 new UnderlyingNetworkInfo[0]);
 
         // Create some traffic.
@@ -744,7 +783,7 @@
         states = new NetworkStateSnapshot[]{buildOemManagedMobileState(IMSI_1, false, new int[]{})};
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states),
+        mService.notifyNetworkStatus(NETWORKS_MOBILE, states, getActiveIface(states),
                 new UnderlyingNetworkInfo[0]);
 
         // Create some traffic.
@@ -797,7 +836,7 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states),
+        mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
                 new UnderlyingNetworkInfo[0]);
 
         // create some traffic for two apps
@@ -856,7 +895,7 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states),
+        mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
                 new UnderlyingNetworkInfo[0]);
 
         NetworkStats.Entry entry1 = new NetworkStats.Entry(
@@ -900,7 +939,7 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states),
+        mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
                 new UnderlyingNetworkInfo[0]);
 
         NetworkStats.Entry uidStats = new NetworkStats.Entry(
@@ -931,7 +970,7 @@
 
         // mStatsFactory#readNetworkStatsDetail() has the following invocations:
         // 1) NetworkStatsService#systemReady from #setUp.
-        // 2) mService#forceUpdateIfaces in the test above.
+        // 2) mService#notifyNetworkStatus in the test above.
         //
         // Additionally, we should have one call from the above call to mService#getDetailedUidStats
         // with the augmented ifaceFilter.
@@ -955,7 +994,7 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states),
+        mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
                 new UnderlyingNetworkInfo[0]);
 
         // create some initial traffic
@@ -1013,7 +1052,7 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states),
+        mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
                 new UnderlyingNetworkInfo[0]);
 
         // create some initial traffic
@@ -1053,7 +1092,7 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states),
+        mService.notifyNetworkStatus(NETWORKS_MOBILE, states, getActiveIface(states),
                 new UnderlyingNetworkInfo[0]);
 
         // Create some traffic
@@ -1092,7 +1131,7 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states),
+        mService.notifyNetworkStatus(NETWORKS_MOBILE, states, getActiveIface(states),
                 new UnderlyingNetworkInfo[0]);
 
         // create some tethering traffic
@@ -1149,7 +1188,7 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states),
+        mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
                 new UnderlyingNetworkInfo[0]);
 
         // verify service has empty history for wifi
@@ -1255,7 +1294,7 @@
                 mService.registerNetworkStatsProvider("TEST", provider);
         assertNotNull(cb);
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states),
+        mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
                 new UnderlyingNetworkInfo[0]);
 
         // Verifies that one requestStatsUpdate will be called during iface update.
@@ -1320,7 +1359,7 @@
                 mService.registerNetworkStatsProvider("TEST", provider);
         assertNotNull(cb);
 
-        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states),
+        mService.notifyNetworkStatus(NETWORKS_MOBILE, states, getActiveIface(states),
                 new UnderlyingNetworkInfo[0]);
 
         // Verifies that one requestStatsUpdate will be called during iface update.
@@ -1378,7 +1417,7 @@
         expectDefaultSettings();
         NetworkStateSnapshot[] states =
                 new NetworkStateSnapshot[]{buildWifiState(true /* isMetered */, TEST_IFACE)};
-        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states),
+        mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
                 new UnderlyingNetworkInfo[0]);
 
         // Register custom provider and retrieve callback.
@@ -1428,7 +1467,7 @@
 
         // 3G network comes online.
         setMobileRatTypeAndWaitForIdle(TelephonyManager.NETWORK_TYPE_UMTS);
-        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states),
+        mService.notifyNetworkStatus(NETWORKS_MOBILE, states, getActiveIface(states),
                 new UnderlyingNetworkInfo[0]);
 
         // Create some traffic.
@@ -1450,7 +1489,8 @@
         setCombineSubtypeEnabled(true);
 
         // Call handleOnCollapsedRatTypeChanged manually to simulate the callback fired
-        // when stopping monitor, this is needed by NetworkStatsService to trigger updateIfaces.
+        // when stopping monitor, this is needed by NetworkStatsService to trigger
+        // handleNotifyNetworkStatus.
         mService.handleOnCollapsedRatTypeChanged();
         HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
         // Create some traffic.
@@ -1499,7 +1539,7 @@
         NetworkStateSnapshot[] states = new NetworkStateSnapshot[]{
                 buildWifiState(true /*isMetered*/, TEST_IFACE2), buildMobile3gState(IMSI_1)};
         expectNetworkStatsUidDetail(buildEmptyStats());
-        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states),
+        mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
                 new UnderlyingNetworkInfo[0]);
 
         // Create some traffic on mobile network.
@@ -1661,10 +1701,15 @@
     }
 
     private static NetworkStateSnapshot buildWifiState() {
-        return buildWifiState(false, TEST_IFACE);
+        return buildWifiState(false, TEST_IFACE, null);
     }
 
     private static NetworkStateSnapshot buildWifiState(boolean isMetered, @NonNull String iface) {
+        return buildWifiState(isMetered, iface, null);
+    }
+
+    private static NetworkStateSnapshot buildWifiState(boolean isMetered, @NonNull String iface,
+            String subscriberId) {
         final LinkProperties prop = new LinkProperties();
         prop.setInterfaceName(iface);
         final NetworkCapabilities capabilities = new NetworkCapabilities();
@@ -1672,7 +1717,7 @@
         capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true);
         capabilities.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
         capabilities.setSSID(TEST_SSID);
-        return new NetworkStateSnapshot(WIFI_NETWORK, capabilities, prop, null, TYPE_WIFI);
+        return new NetworkStateSnapshot(WIFI_NETWORK, capabilities, prop, subscriberId, TYPE_WIFI);
     }
 
     private static NetworkStateSnapshot buildMobile3gState(String subscriberId) {