Add DPM API to block apps from using metered data.

Bug: 63700027
Test: cts-tradefed run singleCommand cts-dev -m CtsDevicePolicyManagerTestCases -t \
      com.android.cts.devicepolicy.MixedDeviceOwnerTest#testSetMeteredDataDisabled
Test: cts-tradefed run singleCommand cts-dev -m CtsDevicePolicyManagerTestCases -t \
      com.android.cts.devicepolicy.MixedProfileOwnerTest#testSetMeteredDataDisabled
Test: atest com.android.server.devicepolicy.DevicePolicyManagerTest#testSetGetMeteredDataDisabled
Change-Id: I147399d316f375c68b415dc6ede837c53cd1aad0
diff --git a/api/current.txt b/api/current.txt
index 97acea9..07f91b4 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6422,6 +6422,7 @@
     method public android.content.ComponentName getMandatoryBackupTransport();
     method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
     method public long getMaximumTimeToLock(android.content.ComponentName);
+    method public java.util.List<java.lang.String> getMeteredDataDisabled(android.content.ComponentName);
     method public int getOrganizationColor(android.content.ComponentName);
     method public java.lang.CharSequence getOrganizationName(android.content.ComponentName);
     method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName);
@@ -6525,6 +6526,7 @@
     method public void setMasterVolumeMuted(android.content.ComponentName, boolean);
     method public void setMaximumFailedPasswordsForWipe(android.content.ComponentName, int);
     method public void setMaximumTimeToLock(android.content.ComponentName, long);
+    method public java.util.List<java.lang.String> setMeteredDataDisabled(android.content.ComponentName, java.util.List<java.lang.String>);
     method public void setNetworkLoggingEnabled(android.content.ComponentName, boolean);
     method public void setOrganizationColor(android.content.ComponentName, int);
     method public void setOrganizationName(android.content.ComponentName, java.lang.CharSequence);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 0be5564..cffe036 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -8219,6 +8219,47 @@
     }
 
     /**
+     * Called by a device or profile owner to restrict packages from accessing metered data.
+     *
+     * @param admin which {@link DeviceAdminReceiver} this request is associated with.
+     * @param packageNames the list of package names to be restricted.
+     * @return a list of package names which could not be restricted.
+     * @throws SecurityException if {@code admin} is not a device or profile owner.
+     */
+    public @NonNull List<String> setMeteredDataDisabled(@NonNull ComponentName admin,
+            @NonNull List<String> packageNames) {
+        throwIfParentInstance("setMeteredDataDisabled");
+        if (mService != null) {
+            try {
+                return mService.setMeteredDataDisabled(admin, packageNames);
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+        }
+        return packageNames;
+    }
+
+    /**
+     * Called by a device or profile owner to retrieve the list of packages which are restricted
+     * by the admin from accessing metered data.
+     *
+     * @param admin which {@link DeviceAdminReceiver} this request is associated with.
+     * @return the list of restricted package names.
+     * @throws SecurityException if {@code admin} is not a device or profile owner.
+     */
+    public @NonNull List<String> getMeteredDataDisabled(@NonNull ComponentName admin) {
+        throwIfParentInstance("getMeteredDataDisabled");
+        if (mService != null) {
+            try {
+                return mService.getMeteredDataDisabled(admin);
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+        }
+        return new ArrayList<>();
+    }
+
+    /**
      * Called by device owners to retrieve device logs from before the device's last reboot.
      * <p>
      * <strong> This API is not supported on all devices. Calling this API on unsupported devices
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index d2a2be7..514dca9 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -400,4 +400,7 @@
     void setPrintingEnabled(in ComponentName admin, boolean enabled);
     boolean isPrintingEnabled();
     CharSequence getPrintingDisabledReason();
+
+    List<String> setMeteredDataDisabled(in ComponentName admin, in List<String> packageNames);
+    List<String> getMeteredDataDisabled(in ComponentName admin);
 }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index 2bd9cab..b4bc7f5 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -37,6 +37,7 @@
 import java.text.SimpleDateFormat;
 import java.util.Arrays;
 import java.util.Date;
+import java.util.Set;
 
 public class NetworkPolicyLogger {
     static final String TAG = "NetworkPolicy";
@@ -62,6 +63,7 @@
     private static final int EVENT_TEMP_POWER_SAVE_WL_CHANGED = 10;
     private static final int EVENT_UID_FIREWALL_RULE_CHANGED = 11;
     private static final int EVENT_FIREWALL_CHAIN_ENABLED = 12;
+    private static final int EVENT_UPDATE_METERED_RESTRICTED_PKGS = 13;
 
     static final int NTWK_BLOCKED_POWER = 0;
     static final int NTWK_ALLOWED_NON_METERED = 1;
@@ -179,6 +181,14 @@
         }
     }
 
+    void meteredRestrictedPkgsChanged(Set<Integer> restrictedUids) {
+        synchronized (mLock) {
+            final String log = "Metered restricted uids: " + restrictedUids;
+            if (LOGD) Slog.d(TAG, log);
+            mEventsBuffer.event(log);
+        }
+    }
+
     void dumpLogs(IndentingPrintWriter pw) {
         synchronized (mLock) {
             pw.println();
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
index 971ac8b..6490964 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
@@ -19,6 +19,8 @@
 import android.net.Network;
 import android.telephony.SubscriptionPlan;
 
+import java.util.Set;
+
 /**
  * Network Policy Manager local system service interface.
  *
@@ -71,4 +73,21 @@
      * Informs that admin data is loaded and available.
      */
     public abstract void onAdminDataAvailable();
+
+    /**
+     * Sets a list of packages which are restricted by admin from accessing metered data.
+     *
+     * @param packageNames the list of restricted packages.
+     * @param userId the userId in which {@param packagesNames} are restricted.
+     */
+    public abstract void setMeteredRestrictedPackages(
+            Set<String> packageNames, int userId);
+
+
+    /**
+     * Similar to {@link #setMeteredRestrictedPackages(Set, int)} but updates the restricted
+     * packages list asynchronously.
+     */
+    public abstract void setMeteredRestrictedPackagesAsync(
+            Set<String> packageNames, int userId);
 }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index e406d51..0e54768 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -232,6 +232,7 @@
 import java.util.Calendar;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -349,6 +350,7 @@
     private static final int MSG_POLICIES_CHANGED = 13;
     private static final int MSG_RESET_FIREWALL_RULES_BY_UID = 15;
     private static final int MSG_SUBSCRIPTION_OVERRIDE = 16;
+    private static final int MSG_METERED_RESTRICTED_PACKAGES_CHANGED = 17;
 
     private static final int UID_MSG_STATE_CHANGED = 100;
     private static final int UID_MSG_GONE = 101;
@@ -480,6 +482,13 @@
     @GuardedBy("mNetworkPoliciesSecondLock")
     private final SparseIntArray mNetIdToSubId = new SparseIntArray();
 
+    /**
+     * Indicates the uids restricted by admin from accessing metered data. It's a mapping from
+     * userId to restricted uids which belong to that user.
+     */
+    @GuardedBy("mUidRulesFirstLock")
+    private final SparseArray<Set<Integer>> mMeteredRestrictedUids = new SparseArray<>();
+
     private final RemoteCallbackList<INetworkPolicyListener>
             mListeners = new RemoteCallbackList<>();
 
@@ -898,6 +907,9 @@
                         // Remove any persistable state for the given user; both cleaning up after a
                         // USER_REMOVED, and one last sanity check during USER_ADDED
                         removeUserStateUL(userId, true);
+                        // Removing outside removeUserStateUL since that can also be called when
+                        // user resets app preferences.
+                        mMeteredRestrictedUids.remove(userId);
                         if (action == ACTION_USER_ADDED) {
                             // Add apps that are whitelisted by default.
                             addDefaultRestrictBackgroundWhitelistUidsUL(userId);
@@ -3137,6 +3149,15 @@
                 }
                 fout.decreaseIndent();
 
+                fout.println("Admin restricted uids for metered data:");
+                fout.increaseIndent();
+                size = mMeteredRestrictedUids.size();
+                for (int i = 0; i < size; ++i) {
+                    fout.print("u" + mMeteredRestrictedUids.keyAt(i) + ": ");
+                    fout.println(mMeteredRestrictedUids.valueAt(i));
+                }
+                fout.decreaseIndent();
+
                 mLogger.dumpLogs(fout);
             }
         }
@@ -3705,6 +3726,7 @@
         final int uidPolicy = mUidPolicy.get(uid, POLICY_NONE);
         final int oldUidRules = mUidRules.get(uid, RULE_NONE);
         final boolean isForeground = isUidForegroundOnRestrictBackgroundUL(uid);
+        final boolean isRestrictedByAdmin = isRestrictedByAdminUL(uid);
 
         final boolean isBlacklisted = (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
         final boolean isWhitelisted = (uidPolicy & POLICY_ALLOW_METERED_BACKGROUND) != 0;
@@ -3712,7 +3734,9 @@
         int newRule = RULE_NONE;
 
         // First step: define the new rule based on user restrictions and foreground state.
-        if (isForeground) {
+        if (isRestrictedByAdmin) {
+            newRule = RULE_REJECT_METERED;
+        } else if (isForeground) {
             if (isBlacklisted || (mRestrictBackground && !isWhitelisted)) {
                 newRule = RULE_TEMPORARY_ALLOW_METERED;
             } else if (isWhitelisted) {
@@ -3732,6 +3756,7 @@
                     + ": isForeground=" +isForeground
                     + ", isBlacklisted=" + isBlacklisted
                     + ", isWhitelisted=" + isWhitelisted
+                    + ", isRestrictedByAdmin=" + isRestrictedByAdmin
                     + ", oldRule=" + uidRulesToString(oldRule)
                     + ", newRule=" + uidRulesToString(newRule)
                     + ", newUidRules=" + uidRulesToString(newUidRules)
@@ -3767,13 +3792,13 @@
                 if (!isWhitelisted) {
                     setMeteredNetworkWhitelist(uid, false);
                 }
-                if (isBlacklisted) {
+                if (isBlacklisted || isRestrictedByAdmin) {
                     setMeteredNetworkBlacklist(uid, true);
                 }
             } else if (hasRule(newRule, RULE_REJECT_METERED)
                     || hasRule(oldRule, RULE_REJECT_METERED)) {
                 // Flip state because app was explicitly added or removed to blacklist.
-                setMeteredNetworkBlacklist(uid, isBlacklisted);
+                setMeteredNetworkBlacklist(uid, (isBlacklisted || isRestrictedByAdmin));
                 if (hasRule(oldRule, RULE_REJECT_METERED) && isWhitelisted) {
                     // Since blacklist prevails over whitelist, we need to handle the special case
                     // where app is whitelisted and blacklisted at the same time (although such
@@ -3790,6 +3815,7 @@
                         + ": foreground=" + isForeground
                         + ", whitelisted=" + isWhitelisted
                         + ", blacklisted=" + isBlacklisted
+                        + ", isRestrictedByAdmin=" + isRestrictedByAdmin
                         + ", newRule=" + uidRulesToString(newUidRules)
                         + ", oldRule=" + uidRulesToString(oldUidRules));
             }
@@ -4102,6 +4128,12 @@
                     mListeners.finishBroadcast();
                     return true;
                 }
+                case MSG_METERED_RESTRICTED_PACKAGES_CHANGED: {
+                    final int userId = msg.arg1;
+                    final Set<String> packageNames = (Set<String>) msg.obj;
+                    setMeteredRestrictedPackagesInternal(packageNames, userId);
+                    return true;
+                }
                 default: {
                     return false;
                 }
@@ -4605,6 +4637,42 @@
         public void onAdminDataAvailable() {
             mAdminDataAvailableLatch.countDown();
         }
+
+        @Override
+        public void setMeteredRestrictedPackages(Set<String> packageNames, int userId) {
+            setMeteredRestrictedPackagesInternal(packageNames, userId);
+        }
+
+        @Override
+        public void setMeteredRestrictedPackagesAsync(Set<String> packageNames, int userId) {
+            mHandler.obtainMessage(MSG_METERED_RESTRICTED_PACKAGES_CHANGED,
+                    userId, 0, packageNames).sendToTarget();
+        }
+    }
+
+    private void setMeteredRestrictedPackagesInternal(Set<String> packageNames, int userId) {
+        synchronized (mUidRulesFirstLock) {
+            final Set<Integer> newRestrictedUids = new ArraySet<>();
+            for (String packageName : packageNames) {
+                final int uid = getUidForPackage(packageName, userId);
+                if (uid >= 0) {
+                    newRestrictedUids.add(uid);
+                }
+            }
+            final Set<Integer> oldRestrictedUids = mMeteredRestrictedUids.get(userId);
+            mMeteredRestrictedUids.put(userId, newRestrictedUids);
+            handleRestrictedPackagesChangeUL(oldRestrictedUids, newRestrictedUids);
+            mLogger.meteredRestrictedPkgsChanged(newRestrictedUids);
+        }
+    }
+
+    private int getUidForPackage(String packageName, int userId) {
+        try {
+            return mContext.getPackageManager().getPackageUidAsUser(packageName,
+                    PackageManager.MATCH_KNOWN_PACKAGES, userId);
+        } catch (NameNotFoundException e) {
+            return -1;
+        }
     }
 
     private int parseSubId(NetworkState state) {
@@ -4642,6 +4710,32 @@
         }
     }
 
+    private void handleRestrictedPackagesChangeUL(Set<Integer> oldRestrictedUids,
+            Set<Integer> newRestrictedUids) {
+        if (oldRestrictedUids == null) {
+            for (int uid : newRestrictedUids) {
+                updateRulesForDataUsageRestrictionsUL(uid);
+            }
+            return;
+        }
+        for (int uid : oldRestrictedUids) {
+            if (!newRestrictedUids.contains(uid)) {
+                updateRulesForDataUsageRestrictionsUL(uid);
+            }
+        }
+        for (int uid : newRestrictedUids) {
+            if (!oldRestrictedUids.contains(uid)) {
+                updateRulesForDataUsageRestrictionsUL(uid);
+            }
+        }
+    }
+
+    private boolean isRestrictedByAdminUL(int uid) {
+        final Set<Integer> restrictedUids = mMeteredRestrictedUids.get(
+                UserHandle.getUserId(uid));
+        return restrictedUids != null && restrictedUids.contains(uid);
+    }
+
     private static boolean hasRule(int uidRules, int rule) {
         return (uidRules & rule) != 0;
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 7a0b1bf..4f2866b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -26,6 +26,7 @@
 import com.android.internal.R;
 import com.android.server.SystemService;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -135,4 +136,14 @@
     public CharSequence getPrintingDisabledReason() {
         return null;
     }
+
+    @Override
+    public List<String> setMeteredDataDisabled(ComponentName admin, List<String> packageNames) {
+        return packageNames;
+    }
+
+    @Override
+    public List<String> getMeteredDataDisabled(ComponentName admin) {
+        return new ArrayList<>();
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8d91288..137887c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -806,6 +806,8 @@
         private static final String TAG_MANDATORY_BACKUP_TRANSPORT = "mandatory_backup_transport";
         private static final String TAG_START_USER_SESSION_MESSAGE = "start_user_session_message";
         private static final String TAG_END_USER_SESSION_MESSAGE = "end_user_session_message";
+        private static final String TAG_METERED_DATA_DISABLED_PACKAGES
+                = "metered_data_disabled_packages";
 
         DeviceAdminInfo info;
 
@@ -872,6 +874,9 @@
             }
         }
 
+        // The list of packages which are not allowed to use metered data.
+        List<String> meteredDisabledPackages;
+
         final Set<String> accountTypesWithManagementDisabled = new ArraySet<>();
 
         // The list of permitted accessibility services package namesas set by a profile
@@ -1153,6 +1158,7 @@
             writePackageListToXml(out, TAG_PERMITTED_NOTIFICATION_LISTENERS,
                     permittedNotificationListeners);
             writePackageListToXml(out, TAG_KEEP_UNINSTALLED_PACKAGES, keepUninstalledPackages);
+            writePackageListToXml(out, TAG_METERED_DATA_DISABLED_PACKAGES, meteredDisabledPackages);
             if (hasUserRestrictions()) {
                 UserRestrictionsUtils.writeRestrictions(
                         out, userRestrictions, TAG_USER_RESTRICTIONS);
@@ -1349,6 +1355,8 @@
                     permittedNotificationListeners = readPackageList(parser, tag);
                 } else if (TAG_KEEP_UNINSTALLED_PACKAGES.equals(tag)) {
                     keepUninstalledPackages = readPackageList(parser, tag);
+                } else if (TAG_METERED_DATA_DISABLED_PACKAGES.equals(tag)) {
+                    meteredDisabledPackages = readPackageList(parser, tag);
                 } else if (TAG_USER_RESTRICTIONS.equals(tag)) {
                     userRestrictions = UserRestrictionsUtils.readRestrictions(parser);
                 } else if (TAG_DEFAULT_ENABLED_USER_RESTRICTIONS.equals(tag)) {
@@ -1647,6 +1655,7 @@
                             policy.mAdminList.remove(i);
                             policy.mAdminMap.remove(aa.info.getComponent());
                             pushActiveAdminPackagesLocked(userHandle);
+                            pushMeteredDisabledPackagesLocked(userHandle);
                         }
                     }
                 } catch (RemoteException re) {
@@ -3502,6 +3511,7 @@
         mInjector.postOnSystemServerInitThreadPool(() -> {
             pushActiveAdminPackages();
             mUsageStatsManagerInternal.onAdminDataAvailable();
+            pushAllMeteredRestrictedPackages();
             mInjector.getNetworkPolicyManagerInternal().onAdminDataAvailable();
         });
     }
@@ -3517,6 +3527,17 @@
         }
     }
 
+    private void pushAllMeteredRestrictedPackages() {
+        synchronized (this) {
+            final List<UserInfo> users = mUserManager.getUsers();
+            for (int i = users.size() - 1; i >= 0; --i) {
+                final int userId = users.get(i).id;
+                mInjector.getNetworkPolicyManagerInternal().setMeteredRestrictedPackagesAsync(
+                        getMeteredDisabledPackagesLocked(userId), userId);
+            }
+        }
+    }
+
     private void pushActiveAdminPackagesLocked(int userId) {
         mUsageStatsManagerInternal.setActiveAdminApps(
                 getActiveAdminPackagesLocked(userId), userId);
@@ -11028,6 +11049,93 @@
     }
 
     @Override
+    public List<String> setMeteredDataDisabled(ComponentName who, List<String> packageNames) {
+        Preconditions.checkNotNull(who);
+        Preconditions.checkNotNull(packageNames);
+
+        if (!mHasFeature) {
+            return packageNames;
+        }
+        synchronized (this) {
+            final ActiveAdmin admin = getActiveAdminForCallerLocked(who,
+                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            final int callingUserId = mInjector.userHandleGetCallingUserId();
+            final long identity = mInjector.binderClearCallingIdentity();
+            try {
+                final List<String> excludedPkgs
+                        = removeInvalidPkgsForMeteredDataRestriction(callingUserId, packageNames);
+                admin.meteredDisabledPackages = packageNames;
+                pushMeteredDisabledPackagesLocked(callingUserId);
+                saveSettingsLocked(callingUserId);
+                return excludedPkgs;
+            } finally {
+                mInjector.binderRestoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    private List<String> removeInvalidPkgsForMeteredDataRestriction(
+            int userId, List<String> pkgNames) {
+        final Set<String> activeAdmins = getActiveAdminPackagesLocked(userId);
+        final List<String> excludedPkgs = new ArrayList<>();
+        for (int i = pkgNames.size() - 1; i >= 0; --i) {
+            final String pkgName = pkgNames.get(i);
+            // If the package is an active admin, don't restrict it.
+            if (activeAdmins.contains(pkgName)) {
+                excludedPkgs.add(pkgName);
+                continue;
+            }
+            // If the package doesn't exist, don't restrict it.
+            try {
+                if (!mInjector.getIPackageManager().isPackageAvailable(pkgName, userId)) {
+                    excludedPkgs.add(pkgName);
+                }
+            } catch (RemoteException e) {
+                // Should not happen
+            }
+        }
+        pkgNames.removeAll(excludedPkgs);
+        return excludedPkgs;
+    }
+
+    @Override
+    public List<String> getMeteredDataDisabled(ComponentName who) {
+        Preconditions.checkNotNull(who);
+
+        if (!mHasFeature) {
+            return new ArrayList<>();
+        }
+        synchronized (this) {
+            final ActiveAdmin admin = getActiveAdminForCallerLocked(who,
+                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            return admin.meteredDisabledPackages == null
+                    ? new ArrayList<>() : admin.meteredDisabledPackages;
+        }
+    }
+
+    private void pushMeteredDisabledPackagesLocked(int userId) {
+        mInjector.getNetworkPolicyManagerInternal().setMeteredRestrictedPackages(
+                getMeteredDisabledPackagesLocked(userId), userId);
+    }
+
+    private Set<String> getMeteredDisabledPackagesLocked(int userId) {
+        final DevicePolicyData policy = getUserData(userId);
+        final Set<String> restrictedPkgs = new ArraySet<>();
+        for (int i = policy.mAdminList.size() - 1; i >= 0; --i) {
+            final ActiveAdmin admin = policy.mAdminList.get(i);
+            if (!isActiveAdminWithPolicyForUserLocked(admin,
+                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, userId)) {
+                // Not a profile or device owner, ignore
+                continue;
+            }
+            if (admin.meteredDisabledPackages != null) {
+                restrictedPkgs.addAll(admin.meteredDisabledPackages);
+            }
+        }
+        return restrictedPkgs;
+    }
+
+    @Override
     public void setAffiliationIds(ComponentName admin, List<String> ids) {
         if (!mHasFeature) {
             return;
@@ -11386,6 +11494,7 @@
                 resetGlobalProxyLocked(policy);
             }
             pushActiveAdminPackagesLocked(userHandle);
+            pushMeteredDisabledPackagesLocked(userHandle);
             saveSettingsLocked(userHandle);
             updateMaximumTimeToLockLocked(userHandle);
             policy.mRemovingAdmins.remove(adminReceiver);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index d26a3c7..26da436 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -126,6 +126,7 @@
             permission.MANAGE_DEVICE_ADMINS, permission.MANAGE_PROFILE_AND_DEVICE_OWNERS,
             permission.MANAGE_USERS, permission.INTERACT_ACROSS_USERS_FULL);
     public static final String NOT_DEVICE_OWNER_MSG = "does not own the device";
+    public static final String NOT_PROFILE_OWNER_MSG = "does not own the profile";
     public static final String ONGOING_CALL_MSG = "ongoing call on the device";
 
     // TODO replace all instances of this with explicit {@link #mServiceContext}.
@@ -301,10 +302,10 @@
 
         // Verify
         verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
-                MockUtils.checkAdminApps(admin1.getPackageName()),
+                MockUtils.checkApps(admin1.getPackageName()),
                 eq(UserHandle.USER_SYSTEM));
         verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
-                MockUtils.checkAdminApps(admin2.getPackageName(),
+                MockUtils.checkApps(admin2.getPackageName(),
                         adminAnotherPackage.getPackageName()),
                 eq(DpmMockContext.CALLER_USER_HANDLE));
         verify(getServices().usageStatsManagerInternal).onAdminDataAvailable();
@@ -705,7 +706,7 @@
 
         assertFalse(dpm.isAdminActiveAsUser(admin1, DpmMockContext.CALLER_USER_HANDLE));
         verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
-                MockUtils.checkAdminApps(admin2.getPackageName()),
+                MockUtils.checkApps(admin2.getPackageName()),
                 eq(DpmMockContext.CALLER_USER_HANDLE));
 
         // Again broadcast from saveSettingsLocked().
@@ -1360,6 +1361,7 @@
                 eq(packageName),
                 anyInt(),
                 eq(userId));
+        doReturn(true).when(getServices().ipackageManager).isPackageAvailable(packageName, userId);
         // Setup application UID with the PackageManager
         doReturn(uid).when(getServices().packageManager).getPackageUidAsUser(
                 eq(packageName),
@@ -2101,6 +2103,53 @@
         }
     }
 
+    public void testSetGetMeteredDataDisabled() throws Exception {
+        setAsProfileOwner(admin1);
+
+        final ArrayList<String> emptyList = new ArrayList<>();
+        assertEquals(emptyList, dpm.getMeteredDataDisabled(admin1));
+
+        // Setup
+        final ArrayList<String> pkgsToRestrict = new ArrayList<>();
+        final String package1 = "com.example.one";
+        final String package2 = "com.example.two";
+        pkgsToRestrict.add(package1);
+        pkgsToRestrict.add(package2);
+        setupPackageInPackageManager(package1, DpmMockContext.CALLER_USER_HANDLE, 123, 0);
+        setupPackageInPackageManager(package2, DpmMockContext.CALLER_USER_HANDLE, 456, 0);
+        List<String> excludedPkgs = dpm.setMeteredDataDisabled(admin1, pkgsToRestrict);
+
+        // Verify
+        assertEquals(emptyList, excludedPkgs);
+        assertEquals(pkgsToRestrict, dpm.getMeteredDataDisabled(admin1));
+        verify(getServices().networkPolicyManagerInternal).setMeteredRestrictedPackages(
+                MockUtils.checkApps(pkgsToRestrict.toArray(new String[0])),
+                eq(DpmMockContext.CALLER_USER_HANDLE));
+
+        // Setup
+        pkgsToRestrict.remove(package1);
+        excludedPkgs = dpm.setMeteredDataDisabled(admin1, pkgsToRestrict);
+
+        // Verify
+        assertEquals(emptyList, excludedPkgs);
+        assertEquals(pkgsToRestrict, dpm.getMeteredDataDisabled(admin1));
+        verify(getServices().networkPolicyManagerInternal).setMeteredRestrictedPackages(
+                MockUtils.checkApps(pkgsToRestrict.toArray(new String[0])),
+                eq(DpmMockContext.CALLER_USER_HANDLE));
+    }
+
+    public void testSetGetMeteredDataDisabled_deviceAdmin() {
+        mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
+        dpm.setActiveAdmin(admin1, true);
+        assertTrue(dpm.isAdminActive(admin1));
+        mContext.callerPermissions.remove(permission.MANAGE_DEVICE_ADMINS);
+
+        assertExpectException(SecurityException.class,  /* messageRegex= */ NOT_PROFILE_OWNER_MSG,
+                () -> dpm.setMeteredDataDisabled(admin1, new ArrayList<>()));
+        assertExpectException(SecurityException.class,  /* messageRegex= */ NOT_PROFILE_OWNER_MSG,
+                () -> dpm.getMeteredDataDisabled(admin1));
+    }
+
     public void testCreateAdminSupportIntent() throws Exception {
         // Setup device owner.
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
index dec962e..92ea766 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
@@ -119,25 +119,25 @@
         return MockitoHamcrest.argThat(m);
     }
 
-    public static Set<String> checkAdminApps(String... adminApps) {
+    public static Set<String> checkApps(String... adminApps) {
         final Matcher<Set<String>> m = new BaseMatcher<Set<String>>() {
             @Override
             public boolean matches(Object item) {
                 if (item == null) return false;
-                final Set<String> actualAdminApps = (Set<String>) item;
-                if (adminApps.length != actualAdminApps.size()) {
+                final Set<String> actualApps = (Set<String>) item;
+                if (adminApps.length != actualApps.size()) {
                     return false;
                 }
-                final Set<String> copyOfAdmins = new ArraySet<>(actualAdminApps);
+                final Set<String> copyOfApps = new ArraySet<>(actualApps);
                 for (String adminApp : adminApps) {
-                    copyOfAdmins.remove(adminApp);
+                    copyOfApps.remove(adminApp);
                 }
-                return copyOfAdmins.isEmpty();
+                return copyOfApps.isEmpty();
             }
 
             @Override
             public void describeTo(Description description) {
-                description.appendText("Admin apps=" + Arrays.toString(adminApps));
+                description.appendText("Apps=" + Arrays.toString(adminApps));
             }
         };
         return MockitoHamcrest.argThat(m);