Move "metered" persistence to WifiConfiguration.

For a long time we've had a nasty tangled dependency between Wi-Fi
and NPMS, since they both persisted different details for configured
networks.  As part of preparing for new carrier data plan APIs, move
the tracking of meteredness over to WifiConfiguration.

This also cleans up how meteredness is communicated through
NetworkAgents to rely completely on NET_CAPABILITY_NOT_METERED by
removing the metered flag on NetworkInfo, which has caused confusion
and staleness.

Migrates any existing user-configured metered networks over to
WifiConfiguration once the device finishes booting.

Remove support for NetworkQuotaInfo, since this information can no
longer be made available to apps.  Frustratingly, some apps are
using it, so keep the object around returning stub values, and shame
them in the logs.

Bug: 63391323
Test: builds, boots, Wi-Fi policy is upgraded
Exempt-From-Owner-Approval: Bug 63673347
Change-Id: I64f865ddeb65cfcd330f8d2a847368abdf960a07
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 2979cd8..7a1d85c 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -1720,14 +1720,8 @@
         // ignored
     }
 
-    /**
-     * Return quota status for the current active network, or {@code null} if no
-     * network is active. Quota status can change rapidly, so these values
-     * shouldn't be cached.
-     *
-     * @hide
-     */
-    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+    /** {@hide} */
+    @Deprecated
     public NetworkQuotaInfo getActiveNetworkQuotaInfo() {
         try {
             return mService.getActiveNetworkQuotaInfo();
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index 63bbd96..7b1e61e 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -63,9 +63,9 @@
     int getRestrictBackgroundByCaller();
 
     void setDeviceIdleMode(boolean enabled);
+    void setWifiMeteredOverride(String networkId, int meteredOverride);
 
     NetworkQuotaInfo getNetworkQuotaInfo(in NetworkState state);
-    boolean isNetworkMetered(in NetworkState state);
 
     void factoryReset(String subscriber);
 }
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 84c32be..818aa21 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -16,8 +16,8 @@
 
 package android.net;
 
-import android.os.Parcelable;
 import android.os.Parcel;
+import android.os.Parcelable;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -121,7 +121,6 @@
     private boolean mIsFailover;
     private boolean mIsAvailable;
     private boolean mIsRoaming;
-    private boolean mIsMetered;
 
     /**
      * @hide
@@ -154,7 +153,6 @@
                 mIsFailover = source.mIsFailover;
                 mIsAvailable = source.mIsAvailable;
                 mIsRoaming = source.mIsRoaming;
-                mIsMetered = source.mIsMetered;
             }
         }
     }
@@ -327,31 +325,6 @@
     }
 
     /**
-     * Returns if this network is metered. A network is classified as metered
-     * when the user is sensitive to heavy data usage on that connection due to
-     * monetary costs, data limitations or battery/performance issues. You
-     * should check this before doing large data transfers, and warn the user or
-     * delay the operation until another network is available.
-     *
-     * @return {@code true} if large transfers should be avoided, otherwise
-     *         {@code false}.
-     * @hide
-     */
-    public boolean isMetered() {
-        synchronized (this) {
-            return mIsMetered;
-        }
-    }
-
-    /** {@hide} */
-    @VisibleForTesting
-    public void setMetered(boolean isMetered) {
-        synchronized (this) {
-            mIsMetered = isMetered;
-        }
-    }
-
-    /**
      * Reports the current coarse-grained state of the network.
      * @return the coarse-grained state
      */
@@ -434,7 +407,6 @@
             append(", failover: ").append(mIsFailover).
             append(", available: ").append(mIsAvailable).
             append(", roaming: ").append(mIsRoaming).
-            append(", metered: ").append(mIsMetered).
             append("]");
             return builder.toString();
         }
@@ -457,7 +429,6 @@
             dest.writeInt(mIsFailover ? 1 : 0);
             dest.writeInt(mIsAvailable ? 1 : 0);
             dest.writeInt(mIsRoaming ? 1 : 0);
-            dest.writeInt(mIsMetered ? 1 : 0);
             dest.writeString(mReason);
             dest.writeString(mExtraInfo);
         }
@@ -476,7 +447,6 @@
             netInfo.mIsFailover = in.readInt() != 0;
             netInfo.mIsAvailable = in.readInt() != 0;
             netInfo.mIsRoaming = in.readInt() != 0;
-            netInfo.mIsMetered = in.readInt() != 0;
             netInfo.mReason = in.readString();
             netInfo.mExtraInfo = in.readString();
             return netInfo;
diff --git a/core/java/android/net/NetworkPolicy.java b/core/java/android/net/NetworkPolicy.java
index 9870e7b..5830667 100644
--- a/core/java/android/net/NetworkPolicy.java
+++ b/core/java/android/net/NetworkPolicy.java
@@ -52,7 +52,7 @@
     public long limitBytes;
     public long lastWarningSnooze;
     public long lastLimitSnooze;
-    public boolean metered;
+    @Deprecated public boolean metered;
     public boolean inferred;
 
     private static final long DEFAULT_MTU = 1500;
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 4d94a55..fc96004 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -26,6 +26,8 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.Signature;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.DebugUtils;
@@ -400,4 +402,13 @@
     public static boolean isProcStateAllowedWhileOnRestrictBackground(int procState) {
         return procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
     }
+
+    public static String resolveNetworkId(WifiConfiguration config) {
+        return WifiInfo.removeDoubleQuotes(config.isPasspoint()
+                ? config.providerFriendlyName : config.SSID);
+    }
+
+    public static String resolveNetworkId(String ssid) {
+        return WifiInfo.removeDoubleQuotes(ssid);
+    }
 }
diff --git a/core/java/android/net/NetworkQuotaInfo.java b/core/java/android/net/NetworkQuotaInfo.java
index 1725ed7..b95f1d9 100644
--- a/core/java/android/net/NetworkQuotaInfo.java
+++ b/core/java/android/net/NetworkQuotaInfo.java
@@ -20,41 +20,32 @@
 import android.os.Parcelable;
 
 /**
- * Information about quota status on a specific network.
- *
+ * @deprecated nobody should be using this, but keep it around returning stub
+ *             values to prevent app crashes.
  * @hide
  */
+@Deprecated
 public class NetworkQuotaInfo implements Parcelable {
-    private final long mEstimatedBytes;
-    private final long mSoftLimitBytes;
-    private final long mHardLimitBytes;
-
     public static final long NO_LIMIT = -1;
 
     /** {@hide} */
-    public NetworkQuotaInfo(long estimatedBytes, long softLimitBytes, long hardLimitBytes) {
-        mEstimatedBytes = estimatedBytes;
-        mSoftLimitBytes = softLimitBytes;
-        mHardLimitBytes = hardLimitBytes;
+    public NetworkQuotaInfo() {
     }
 
     /** {@hide} */
     public NetworkQuotaInfo(Parcel in) {
-        mEstimatedBytes = in.readLong();
-        mSoftLimitBytes = in.readLong();
-        mHardLimitBytes = in.readLong();
     }
 
     public long getEstimatedBytes() {
-        return mEstimatedBytes;
+        return 0;
     }
 
     public long getSoftLimitBytes() {
-        return mSoftLimitBytes;
+        return NO_LIMIT;
     }
 
     public long getHardLimitBytes() {
-        return mHardLimitBytes;
+        return NO_LIMIT;
     }
 
     @Override
@@ -64,9 +55,6 @@
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
-        out.writeLong(mEstimatedBytes);
-        out.writeLong(mSoftLimitBytes);
-        out.writeLong(mHardLimitBytes);
     }
 
     public static final Creator<NetworkQuotaInfo> CREATOR = new Creator<NetworkQuotaInfo>() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/NetworkPolicyEditor.java b/packages/SettingsLib/src/com/android/settingslib/NetworkPolicyEditor.java
index 2e77f42..3640bfa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/NetworkPolicyEditor.java
+++ b/packages/SettingsLib/src/com/android/settingslib/NetworkPolicyEditor.java
@@ -21,6 +21,7 @@
 import static android.net.NetworkPolicy.SNOOZE_NEVER;
 import static android.net.NetworkPolicy.WARNING_DISABLED;
 import static android.net.NetworkTemplate.MATCH_WIFI;
+
 import static com.android.internal.util.Preconditions.checkNotNull;
 
 import android.net.NetworkPolicy;
@@ -204,53 +205,6 @@
         writeAsync();
     }
 
-    public boolean getPolicyMetered(NetworkTemplate template) {
-        NetworkPolicy policy = getPolicy(template);
-        if (policy != null) {
-            return policy.metered;
-        } else {
-            return false;
-        }
-    }
-
-    public void setPolicyMetered(NetworkTemplate template, boolean metered) {
-        boolean modified = false;
-
-        NetworkPolicy policy = getPolicy(template);
-        if (metered) {
-            if (policy == null) {
-                policy = buildDefaultPolicy(template);
-                policy.metered = true;
-                policy.inferred = false;
-                mPolicies.add(policy);
-                modified = true;
-            } else if (!policy.metered) {
-                policy.metered = true;
-                policy.inferred = false;
-                modified = true;
-            }
-
-        } else {
-            if (policy == null) {
-                // ignore when policy doesn't exist
-            } else if (policy.metered) {
-                policy.metered = false;
-                policy.inferred = false;
-                modified = true;
-            }
-        }
-
-        // Remove legacy unquoted policies while we're here
-        final NetworkTemplate unquoted = buildUnquotedNetworkTemplate(template);
-        final NetworkPolicy unquotedPolicy = getPolicy(unquoted);
-        if (unquotedPolicy != null) {
-            mPolicies.remove(unquotedPolicy);
-            modified = true;
-        }
-
-        if (modified) writeAsync();
-    }
-
     /**
      * Build a revised {@link NetworkTemplate} that matches the same rule, but
      * with an unquoted {@link NetworkTemplate#getNetworkId()}. Used to work
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index ec41415..7bbc598 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -552,15 +552,11 @@
     }
 
     /**
-     * Returns if the network is marked metered. Metering can be marked through its config in
-     * {@link WifiConfiguration}, after connection in {@link WifiInfo}, or from a score config in
-     * {@link ScoredNetwork}.
+     * Returns if the network should be considered metered.
      */
     public boolean isMetered() {
         return mIsScoredNetworkMetered
-                || (mConfig != null && mConfig.meteredHint)
-                || (mInfo != null && mInfo.getMeteredHint()
-                || (mNetworkInfo != null && mNetworkInfo.isMetered()));
+                || WifiConfiguration.isMetered(mConfig, mInfo);
     }
 
     public NetworkInfo getNetworkInfo() {
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index c08dd6e..aed0270 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -298,23 +298,6 @@
     }
 
     @Test
-    public void testIsMetered_returnTrueWhenNetworkInfoIsMetered() {
-        WifiConfiguration configuration = createWifiConfiguration();
-
-        NetworkInfo networkInfo =
-                new NetworkInfo(ConnectivityManager.TYPE_WIFI, 2, "WIFI", "WIFI_SUBTYPE");
-        networkInfo.setMetered(true);
-        AccessPoint accessPoint = new AccessPoint(mContext, configuration);
-        WifiInfo wifiInfo = new WifiInfo();
-        wifiInfo.setSSID(WifiSsid.createFromAsciiEncoded(configuration.SSID));
-        wifiInfo.setBSSID(configuration.BSSID);
-        wifiInfo.setNetworkId(configuration.networkId);
-        accessPoint.update(configuration, wifiInfo, networkInfo);
-
-        assertThat(accessPoint.isMetered()).isTrue();
-    }
-
-    @Test
     public void testIsMetered_returnTrueWhenScoredNetworkIsMetered() {
         AccessPoint ap = createAccessPointWithScanResultCache();
 
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index a2e74b6..8200289 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -1036,8 +1036,7 @@
     /**
      * Apply any relevant filters to {@link NetworkState} for the given UID. For
      * example, this may mark the network as {@link DetailedState#BLOCKED} based
-     * on {@link #isNetworkWithLinkPropertiesBlocked}, or
-     * {@link NetworkInfo#isMetered()} based on network policies.
+     * on {@link #isNetworkWithLinkPropertiesBlocked}.
      */
     private void filterNetworkStateForUid(NetworkState state, int uid, boolean ignoreBlocked) {
         if (state == null || state.networkInfo == null || state.linkProperties == null) return;
@@ -1048,15 +1047,6 @@
         if (mLockdownTracker != null) {
             mLockdownTracker.augmentNetworkInfo(state.networkInfo);
         }
-
-        // TODO: apply metered state closer to NetworkAgentInfo
-        final long token = Binder.clearCallingIdentity();
-        try {
-            state.networkInfo.setMetered(mPolicyManager.isNetworkMetered(state));
-        } catch (RemoteException e) {
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
     }
 
     /**
@@ -1326,30 +1316,24 @@
     }
 
     @Override
+    @Deprecated
     public NetworkQuotaInfo getActiveNetworkQuotaInfo() {
-        enforceAccessPermission();
-        final int uid = Binder.getCallingUid();
-        final long token = Binder.clearCallingIdentity();
-        try {
-            final NetworkState state = getUnfilteredActiveNetworkState(uid);
-            if (state.networkInfo != null) {
-                try {
-                    return mPolicyManager.getNetworkQuotaInfo(state);
-                } catch (RemoteException e) {
-                }
-            }
-            return null;
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
+        Log.w(TAG, "Shame on UID " + Binder.getCallingUid()
+                + " for calling the hidden API getNetworkQuotaInfo(). Shame!");
+        return new NetworkQuotaInfo();
     }
 
     @Override
     public boolean isActiveNetworkMetered() {
         enforceAccessPermission();
 
-        final NetworkInfo info = getActiveNetworkInfo();
-        return (info != null) ? info.isMetered() : false;
+        final NetworkCapabilities caps = getNetworkCapabilities(getActiveNetwork());
+        if (caps != null) {
+            return !caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+        } else {
+            // Always return the most conservative value
+            return true;
+        }
     }
 
     private INetworkManagementEventObserver mDataActivityObserver = new BaseNetworkObserver() {
@@ -2759,7 +2743,8 @@
         enforceAccessPermission();
 
         NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
-        if (nai != null && !nai.networkInfo.isMetered()) {
+        if (nai != null && nai.networkCapabilities
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)) {
             return ConnectivityManager.MULTIPATH_PREFERENCE_UNMETERED;
         }
 
diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
index 17c8928..78367fe 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -85,7 +85,7 @@
     @Override
     public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
         if (jobStatus.hasConnectivityConstraint()) {
-            updateConstraintsSatisfied(jobStatus, null);
+            updateConstraintsSatisfied(jobStatus);
             mTrackedJobs.add(jobStatus);
             jobStatus.setTrackingController(JobStatus.TRACKING_CONNECTIVITY);
         }
@@ -99,23 +99,25 @@
         }
     }
 
-    private boolean updateConstraintsSatisfied(JobStatus jobStatus,
-            NetworkCapabilities capabilities) {
+    private boolean updateConstraintsSatisfied(JobStatus jobStatus) {
         final int jobUid = jobStatus.getSourceUid();
         final boolean ignoreBlocked = (jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
         final NetworkInfo info = mConnManager.getActiveNetworkInfoForUid(jobUid, ignoreBlocked);
-        if (capabilities == null) {
-            final Network network = mConnManager.getActiveNetworkForUid(jobUid, ignoreBlocked);
-            capabilities = mConnManager.getNetworkCapabilities(network);
-        }
+        final Network network = mConnManager.getActiveNetworkForUid(jobUid, ignoreBlocked);
+        final NetworkCapabilities capabilities = (network != null)
+                ? mConnManager.getNetworkCapabilities(network) : null;
 
-        final boolean validated = capabilities != null
+        final boolean validated = (capabilities != null)
                 && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
-        final boolean connected = info != null && info.isConnected();
+        final boolean connected = (info != null) && info.isConnected();
         final boolean connectionUsable = connected && validated;
-        final boolean metered = connected && info.isMetered();
-        final boolean unmetered = connected && !info.isMetered();
-        final boolean notRoaming = connected && !info.isRoaming();
+
+        final boolean metered = connected && (capabilities != null)
+                && !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+        final boolean unmetered = connected && (capabilities != null)
+                && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+        final boolean notRoaming = connected && (info != null)
+                && !info.isRoaming();
 
         boolean changed = false;
         changed |= jobStatus.setConnectivityConstraintSatisfied(connectionUsable);
@@ -148,13 +150,13 @@
      * @param uid only update jobs belonging to this UID, or {@code -1} to
      *            update all tracked jobs.
      */
-    private void updateTrackedJobs(int uid, NetworkCapabilities capabilities) {
+    private void updateTrackedJobs(int uid) {
         synchronized (mLock) {
             boolean changed = false;
             for (int i = mTrackedJobs.size()-1; i >= 0; i--) {
                 final JobStatus js = mTrackedJobs.valueAt(i);
                 if (uid == -1 || uid == js.getSourceUid()) {
-                    changed |= updateConstraintsSatisfied(js, capabilities);
+                    changed |= updateConstraintsSatisfied(js);
                 }
             }
             if (changed) {
@@ -187,7 +189,7 @@
             if (DEBUG) {
                 Slog.v(TAG, "onCapabilitiesChanged() : " + networkCapabilities);
             }
-            updateTrackedJobs(-1, networkCapabilities);
+            updateTrackedJobs(-1);
         }
 
         @Override
@@ -195,7 +197,7 @@
             if (DEBUG) {
                 Slog.v(TAG, "Network lost");
             }
-            updateTrackedJobs(-1, null);
+            updateTrackedJobs(-1);
         }
     };
 
@@ -205,7 +207,7 @@
             if (DEBUG) {
                 Slog.v(TAG, "Uid rules changed for " + uid);
             }
-            updateTrackedJobs(uid, null);
+            updateTrackedJobs(uid);
         }
 
         @Override
@@ -218,7 +220,7 @@
             if (DEBUG) {
                 Slog.v(TAG, "Background restriction change to " + restrictBackground);
             }
-            updateTrackedJobs(-1, null);
+            updateTrackedJobs(-1);
         }
 
         @Override
@@ -226,7 +228,7 @@
             if (DEBUG) {
                 Slog.v(TAG, "Uid policy changed for " + uid);
             }
-            updateTrackedJobs(uid, null);
+            updateTrackedJobs(uid);
         }
     };
 
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index b6af076..8b8811e 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -18,7 +18,6 @@
 
 import static android.Manifest.permission.ACCESS_NETWORK_STATE;
 import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
-import static android.Manifest.permission.DUMP;
 import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
 import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
 import static android.Manifest.permission.READ_PHONE_STATE;
@@ -33,9 +32,6 @@
 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
 import static android.net.ConnectivityManager.TYPE_MOBILE;
-import static android.net.ConnectivityManager.TYPE_WIMAX;
-import static android.net.ConnectivityManager.isNetworkTypeMobile;
-import static android.net.NetworkPolicy.CYCLE_NONE;
 import static android.net.NetworkPolicy.LIMIT_DISABLED;
 import static android.net.NetworkPolicy.SNOOZE_NEVER;
 import static android.net.NetworkPolicy.WARNING_DISABLED;
@@ -46,13 +42,13 @@
 import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW;
 import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
 import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY;
+import static android.net.NetworkPolicyManager.MASK_ALL_NETWORKS;
+import static android.net.NetworkPolicyManager.MASK_METERED_NETWORKS;
 import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND;
 import static android.net.NetworkPolicyManager.POLICY_NONE;
 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
 import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
 import static android.net.NetworkPolicyManager.RULE_ALLOW_METERED;
-import static android.net.NetworkPolicyManager.MASK_METERED_NETWORKS;
-import static android.net.NetworkPolicyManager.MASK_ALL_NETWORKS;
 import static android.net.NetworkPolicyManager.RULE_NONE;
 import static android.net.NetworkPolicyManager.RULE_REJECT_ALL;
 import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
@@ -60,6 +56,7 @@
 import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground;
+import static android.net.NetworkPolicyManager.resolveNetworkId;
 import static android.net.NetworkPolicyManager.uidPoliciesToString;
 import static android.net.NetworkPolicyManager.uidRulesToString;
 import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
@@ -68,16 +65,9 @@
 import static android.net.NetworkTemplate.MATCH_WIFI;
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
 import static android.net.TrafficStats.MB_IN_BYTES;
-import static android.net.wifi.WifiManager.CHANGE_REASON_ADDED;
-import static android.net.wifi.WifiManager.CHANGE_REASON_REMOVED;
-import static android.net.wifi.WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION;
-import static android.net.wifi.WifiManager.EXTRA_CHANGE_REASON;
-import static android.net.wifi.WifiManager.EXTRA_NETWORK_INFO;
-import static android.net.wifi.WifiManager.EXTRA_WIFI_CONFIGURATION;
-import static android.net.wifi.WifiManager.EXTRA_WIFI_INFO;
 import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
-import static android.telephony.CarrierConfigManager.DATA_CYCLE_USE_PLATFORM_DEFAULT;
 import static android.telephony.CarrierConfigManager.DATA_CYCLE_THRESHOLD_DISABLED;
+import static android.telephony.CarrierConfigManager.DATA_CYCLE_USE_PLATFORM_DEFAULT;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 
 import static com.android.internal.util.ArrayUtils.appendInt;
@@ -127,15 +117,12 @@
 import android.net.INetworkStatsService;
 import android.net.LinkProperties;
 import android.net.NetworkIdentity;
-import android.net.NetworkInfo;
 import android.net.NetworkPolicy;
 import android.net.NetworkQuotaInfo;
 import android.net.NetworkState;
 import android.net.NetworkTemplate;
 import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.os.PowerSaveState;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.Handler;
@@ -147,6 +134,7 @@
 import android.os.PersistableBundle;
 import android.os.PowerManager;
 import android.os.PowerManagerInternal;
+import android.os.PowerSaveState;
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -190,8 +178,8 @@
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
 import com.android.server.SystemConfig;
-
 import com.android.server.power.BatterySaverPolicy.ServiceType;
+
 import libcore.io.IoUtils;
 
 import com.google.android.collect.Lists;
@@ -212,8 +200,8 @@
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.List;
 import java.util.Calendar;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -756,15 +744,10 @@
             mContext.registerReceiver(mSnoozeWarningReceiver, snoozeWarningFilter,
                     MANAGE_NETWORK_POLICY, mHandler);
 
-            // listen for configured wifi networks to be removed
-            final IntentFilter wifiConfigFilter =
-                    new IntentFilter(CONFIGURED_NETWORKS_CHANGED_ACTION);
-            mContext.registerReceiver(mWifiConfigReceiver, wifiConfigFilter, null, mHandler);
-
-            // listen for wifi state changes to catch metered hint
-            final IntentFilter wifiStateFilter = new IntentFilter(
-                    WifiManager.NETWORK_STATE_CHANGED_ACTION);
-            mContext.registerReceiver(mWifiStateReceiver, wifiStateFilter, null, mHandler);
+            // listen for configured wifi networks to be loaded
+            final IntentFilter wifiFilter =
+                    new IntentFilter(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
+            mContext.registerReceiver(mWifiReceiver, wifiFilter, null, mHandler);
 
             // listen for carrier config changes to update data cycle information
             final IntentFilter carrierConfigFilter = new IntentFilter(
@@ -960,80 +943,22 @@
     };
 
     /**
-     * Receiver that watches for {@link WifiConfiguration} to be changed.
+     * Receiver that watches for {@link WifiConfiguration} to be loaded so that
+     * we can perform upgrade logic.
      */
-    final private BroadcastReceiver mWifiConfigReceiver = new BroadcastReceiver() {
+    final private BroadcastReceiver mWifiReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            // on background handler thread, and verified CONNECTIVITY_INTERNAL
-            // permission above.
-
-            final int reason = intent.getIntExtra(EXTRA_CHANGE_REASON, CHANGE_REASON_ADDED);
-            if (reason == CHANGE_REASON_REMOVED) {
-                final WifiConfiguration config = intent.getParcelableExtra(
-                        EXTRA_WIFI_CONFIGURATION);
-                if (config.SSID != null) {
-                    final NetworkTemplate template = NetworkTemplate.buildTemplateWifi(config.SSID);
-                    synchronized (mUidRulesFirstLock) {
-                        synchronized (mNetworkPoliciesSecondLock) {
-                            if (mNetworkPolicy.containsKey(template)) {
-                                mNetworkPolicy.remove(template);
-                                writePolicyAL();
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    };
-
-    /**
-     * Receiver that watches {@link WifiInfo} state changes to infer metered
-     * state. Ignores hints when policy is user-defined.
-     */
-    final private BroadcastReceiver mWifiStateReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            // on background handler thread, and verified CONNECTIVITY_INTERNAL
-            // permission above.
-
-            // ignore when not connected
-            final NetworkInfo netInfo = intent.getParcelableExtra(EXTRA_NETWORK_INFO);
-            if (!netInfo.isConnected()) return;
-
-            final WifiInfo info = intent.getParcelableExtra(EXTRA_WIFI_INFO);
-            final boolean meteredHint = info.getMeteredHint();
-
-            final NetworkTemplate template = NetworkTemplate.buildTemplateWifi(info.getSSID());
             synchronized (mUidRulesFirstLock) {
                 synchronized (mNetworkPoliciesSecondLock) {
-                    NetworkPolicy policy = mNetworkPolicy.get(template);
-                    if (policy == null && meteredHint) {
-                        // policy doesn't exist, and AP is hinting that it's
-                        // metered: create an inferred policy.
-                        policy = newWifiPolicy(template, meteredHint);
-                        addNetworkPolicyAL(policy);
-
-                    } else if (policy != null && policy.inferred) {
-                        // policy exists, and was inferred: update its current
-                        // metered state.
-                        policy.metered = meteredHint;
-
-                        // since this is inferred for each wifi session, just update
-                        // rules without persisting.
-                        updateNetworkRulesNL();
-                    }
+                    upgradeWifiMeteredOverrideAL();
                 }
             }
+            // Only need to perform upgrade logic once
+            mContext.unregisterReceiver(this);
         }
     };
 
-    static NetworkPolicy newWifiPolicy(NetworkTemplate template, boolean metered) {
-        return new NetworkPolicy(template, CYCLE_NONE, Time.TIMEZONE_UTC,
-                WARNING_DISABLED, LIMIT_DISABLED, SNOOZE_NEVER, SNOOZE_NEVER,
-                metered, true);
-    }
-
     /**
      * Observer that watches for {@link INetworkManagementService} alerts.
      */
@@ -1905,7 +1830,6 @@
                                     cycleTimezone, warningBytes, limitBytes, lastWarningSnooze,
                                     lastLimitSnooze, metered, inferred));
                         }
-
                     } else if (TAG_UID_POLICY.equals(tag)) {
                         final int uid = readIntAttribute(in, ATTR_UID);
                         final int policy = readIntAttribute(in, ATTR_POLICY);
@@ -1991,6 +1915,40 @@
         }
     }
 
+    /**
+     * Perform upgrade step of moving any user-defined meterness overrides over
+     * into {@link WifiConfiguration}.
+     */
+    private void upgradeWifiMeteredOverrideAL() {
+        boolean modified = false;
+        final WifiManager wm = mContext.getSystemService(WifiManager.class);
+        final List<WifiConfiguration> configs = wm.getConfiguredNetworks();
+        for (int i = 0; i < mNetworkPolicy.size(); ) {
+            final NetworkPolicy policy = mNetworkPolicy.valueAt(i);
+            if (policy.template.getMatchRule() == NetworkTemplate.MATCH_WIFI
+                    && !policy.inferred) {
+                mNetworkPolicy.removeAt(i);
+                modified = true;
+
+                final String networkId = resolveNetworkId(policy.template.getNetworkId());
+                for (WifiConfiguration config : configs) {
+                    if (Objects.equals(resolveNetworkId(config), networkId)) {
+                        Slog.d(TAG, "Found network " + networkId + "; upgrading metered hint");
+                        config.meteredOverride = policy.metered
+                                ? WifiConfiguration.METERED_OVERRIDE_METERED
+                                : WifiConfiguration.METERED_OVERRIDE_NOT_METERED;
+                        wm.updateNetwork(config);
+                    }
+                }
+            } else {
+                i++;
+            }
+        }
+        if (modified) {
+            writePolicyAL();
+        }
+    }
+
     void writePolicyAL() {
         if (LOGV) Slog.v(TAG, "writePolicyAL()");
 
@@ -2495,81 +2453,30 @@
         }
     }
 
-    private NetworkPolicy findPolicyForNetworkNL(NetworkIdentity ident) {
-        for (int i = mNetworkPolicy.size()-1; i >= 0; i--) {
-            NetworkPolicy policy = mNetworkPolicy.valueAt(i);
-            if (policy.template.matches(ident)) {
-                return policy;
-            }
-        }
-        return null;
-    }
-
     @Override
-    public NetworkQuotaInfo getNetworkQuotaInfo(NetworkState state) {
-        mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG);
-
-        // only returns usage summary, so we don't require caller to have
-        // READ_NETWORK_USAGE_HISTORY.
+    public void setWifiMeteredOverride(String networkId, int meteredOverride) {
+        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
         final long token = Binder.clearCallingIdentity();
         try {
-            return getNetworkQuotaInfoUnchecked(state);
+            final WifiManager wm = mContext.getSystemService(WifiManager.class);
+            final List<WifiConfiguration> configs = wm.getConfiguredNetworks();
+            for (WifiConfiguration config : configs) {
+                if (Objects.equals(resolveNetworkId(config), networkId)) {
+                    config.meteredOverride = meteredOverride;
+                    wm.updateNetwork(config);
+                }
+            }
         } finally {
             Binder.restoreCallingIdentity(token);
         }
     }
 
-    private NetworkQuotaInfo getNetworkQuotaInfoUnchecked(NetworkState state) {
-        final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state);
-
-        final NetworkPolicy policy;
-        synchronized (mNetworkPoliciesSecondLock) {
-            policy = findPolicyForNetworkNL(ident);
-        }
-
-        if (policy == null || !policy.hasCycle()) {
-            // missing policy means we can't derive useful quota info
-            return null;
-        }
-
-        final long currentTime = currentTimeMillis();
-
-        // find total bytes used under policy
-        final long start = computeLastCycleBoundary(currentTime, policy);
-        final long end = currentTime;
-        final long totalBytes = getTotalBytes(policy.template, start, end);
-
-        // report soft and hard limits under policy
-        final long softLimitBytes = policy.warningBytes != WARNING_DISABLED ? policy.warningBytes
-                : NetworkQuotaInfo.NO_LIMIT;
-        final long hardLimitBytes = policy.limitBytes != LIMIT_DISABLED ? policy.limitBytes
-                : NetworkQuotaInfo.NO_LIMIT;
-
-        return new NetworkQuotaInfo(totalBytes, softLimitBytes, hardLimitBytes);
-    }
-
     @Override
-    public boolean isNetworkMetered(NetworkState state) {
-        if (state.networkInfo == null) {
-            return false;
-        }
-
-        final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state);
-
-        final NetworkPolicy policy;
-        synchronized (mNetworkPoliciesSecondLock) {
-            policy = findPolicyForNetworkNL(ident);
-        }
-
-        if (policy != null) {
-            return policy.metered;
-        } else {
-            final int type = state.networkInfo.getType();
-            if ((isNetworkTypeMobile(type) && ident.getMetered()) || type == TYPE_WIMAX) {
-                return true;
-            }
-            return false;
-        }
+    @Deprecated
+    public NetworkQuotaInfo getNetworkQuotaInfo(NetworkState state) {
+        Log.w(TAG, "Shame on UID " + Binder.getCallingUid()
+                + " for calling the hidden API getNetworkQuotaInfo(). Shame!");
+        return new NetworkQuotaInfo();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java b/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java
index 8ced1c2..b65b9d7 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java
@@ -19,26 +19,17 @@
 import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND;
 import static android.net.NetworkPolicyManager.POLICY_NONE;
 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
-import static android.net.wifi.WifiInfo.removeDoubleQuotes;
-
-import static com.android.server.net.NetworkPolicyManagerService.newWifiPolicy;
-import static com.android.server.net.NetworkPolicyManagerService.TAG;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
 
 import android.content.Context;
 import android.net.INetworkPolicyManager;
-import android.net.NetworkPolicy;
-import android.net.NetworkTemplate;
+import android.net.NetworkPolicyManager;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
 import android.os.RemoteException;
 import android.os.ShellCommand;
-import android.util.Log;
+
+import java.io.PrintWriter;
+import java.util.List;
 
 class NetworkPolicyManagerShellCommand extends ShellCommand {
 
@@ -90,7 +81,7 @@
         pw.println("    Adds a UID to the blacklist for restrict background usage.");
         pw.println("  get restrict-background");
         pw.println("    Gets the global restrict background usage status.");
-        pw.println("  list wifi-networks [BOOLEAN]");
+        pw.println("  list wifi-networks [true|false]");
         pw.println("    Lists all saved wifi networks and whether they are metered or not.");
         pw.println("    If a boolean argument is passed, filters just the metered (or unmetered)");
         pw.println("    networks.");
@@ -102,7 +93,7 @@
         pw.println("    Removes a UID from the whitelist for restrict background usage.");
         pw.println("  remove restrict-background-blacklist UID");
         pw.println("    Removes a UID from the blacklist for restrict background usage.");
-        pw.println("  set metered-network ID BOOLEAN");
+        pw.println("  set metered-network ID [undefined|true|false]");
         pw.println("    Toggles whether the given wi-fi network is metered.");
         pw.println("  set restrict-background BOOLEAN");
         pw.println("    Sets the global restrict background usage status.");
@@ -276,107 +267,60 @@
         return resetUidPolicy("not blacklisted", POLICY_REJECT_METERED_BACKGROUND);
     }
 
-    private int listWifiNetworks() throws RemoteException {
+    private int listWifiNetworks() {
         final PrintWriter pw = getOutPrintWriter();
         final String arg = getNextArg();
-        final Boolean filter = arg == null ? null : Boolean.valueOf(arg);
-        for (NetworkPolicy policy : getWifiPolicies()) {
-            if (filter != null && filter.booleanValue() != policy.metered) {
-                continue;
+        final int match;
+        if (arg == null) {
+            match = WifiConfiguration.METERED_OVERRIDE_NONE;
+        } else if (Boolean.parseBoolean(arg)) {
+            match = WifiConfiguration.METERED_OVERRIDE_METERED;
+        } else {
+            match = WifiConfiguration.METERED_OVERRIDE_NOT_METERED;
+        }
+
+        final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
+        for (WifiConfiguration config : configs) {
+            if (arg == null || config.meteredOverride == match) {
+                pw.print(NetworkPolicyManager.resolveNetworkId(config));
+                pw.print(';');
+                pw.println(overrideToString(config.meteredOverride));
             }
-            pw.print(getNetworkId(policy));
-            pw.print(';');
-            pw.println(policy.metered);
         }
         return 0;
     }
 
     private int setMeteredWifiNetwork() throws RemoteException {
         final PrintWriter pw = getOutPrintWriter();
-        final String id = getNextArg();
-        if (id == null) {
-            pw.println("Error: didn't specify ID");
+        final String networkId = getNextArg();
+        if (networkId == null) {
+            pw.println("Error: didn't specify networkId");
             return -1;
         }
         final String arg = getNextArg();
         if (arg == null) {
-            pw.println("Error: didn't specify BOOLEAN");
+            pw.println("Error: didn't specify meteredOverride");
             return -1;
         }
-        final boolean metered = Boolean.valueOf(arg);
-        final NetworkPolicy[] policies = mInterface.getNetworkPolicies(null);
-        boolean changed = false;
-        // First try to find a policy with such id
-        for (NetworkPolicy policy : policies) {
-            if (policy.template.isMatchRuleMobile() || policy.metered == metered) {
-                continue;
-            }
-            final String networkId = getNetworkId(policy);
-            if (id.equals(networkId)) {
-                Log.i(TAG, "Changing " + networkId + " metered status to " + metered);
-                policy.metered = metered;
-                changed = true;
-            }
-        }
-        if (changed) {
-            mInterface.setNetworkPolicies(policies);
-            return 0;
-        }
-        // Policy not found: check if there is a saved wi-fi with such id.
-        for (WifiConfiguration config : mWifiManager.getConfiguredNetworks()) {
-            final String ssid = removeDoubleQuotes(config.SSID);
-            if (id.equals(ssid)) {
-                final NetworkPolicy policy = newPolicy(ssid);
-                policy.metered = true;
-                Log.i(TAG, "Creating new policy for " + ssid + ": " + policy);
-                final NetworkPolicy[] newPolicies = new NetworkPolicy[policies.length + 1];
-                System.arraycopy(policies, 0, newPolicies, 0, policies.length);
-                newPolicies[newPolicies.length - 1] = policy;
-                mInterface.setNetworkPolicies(newPolicies);
-                return 0;
-            }
-        }
-        pw.print("Error: didn't find network with SSID "); pw.println(id);
+        mInterface.setWifiMeteredOverride(NetworkPolicyManager.resolveNetworkId(networkId),
+                stringToOverride(arg));
         return -1;
     }
 
-    private List<NetworkPolicy> getWifiPolicies() throws RemoteException {
-        // First gets a list of saved wi-fi networks.
-        final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
-        final int size = configs != null ? configs.size() : 0;
-        final Set<String> ssids = new HashSet<>(size);
-        if (configs != null) {
-            for (WifiConfiguration config : configs) {
-                ssids.add(removeDoubleQuotes(config.SSID));
-            }
+    private static String overrideToString(int override) {
+        switch (override) {
+            case WifiConfiguration.METERED_OVERRIDE_METERED: return "true";
+            case WifiConfiguration.METERED_OVERRIDE_NOT_METERED: return "false";
+            default: return "none";
         }
-
-        // Then gets the saved policies.
-        final NetworkPolicy[] policies = mInterface.getNetworkPolicies(null);
-        final List<NetworkPolicy> wifiPolicies = new ArrayList<NetworkPolicy>(policies.length);
-        for (NetworkPolicy policy: policies) {
-            if (!policy.template.isMatchRuleMobile()) {
-                wifiPolicies.add(policy);
-                final String netId = getNetworkId(policy);
-                ssids.remove(netId);
-            }
-        }
-        // Finally, creates new default policies for saved WI-FIs not policied yet.
-        for (String ssid : ssids) {
-            final NetworkPolicy policy = newPolicy(ssid);
-            wifiPolicies.add(policy);
-        }
-        return wifiPolicies;
     }
 
-    private NetworkPolicy newPolicy(String ssid) {
-        final NetworkTemplate template = NetworkTemplate.buildTemplateWifi(ssid);
-        final NetworkPolicy policy = newWifiPolicy(template, false);
-        return policy;
-    }
-
-    private String getNetworkId(NetworkPolicy policy) {
-        return removeDoubleQuotes(policy.template.getNetworkId());
+    private static int stringToOverride(String override) {
+        switch (override) {
+            case "true": return WifiConfiguration.METERED_OVERRIDE_METERED;
+            case "false": return WifiConfiguration.METERED_OVERRIDE_NOT_METERED;
+            default: return WifiConfiguration.METERED_OVERRIDE_NONE;
+        }
     }
 
     private int getNextBooleanArg() {
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index e7fbe4f..13d54ab 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -245,6 +245,7 @@
      * (e.g., {@code 01a243f405}).
      */
     public String SSID;
+
     /**
      * When set, this network configuration entry should only be used when
      * associating with the AP having the specified BSSID. The value is
@@ -740,22 +741,58 @@
     }
 
     /**
+     * Indicates if the creator of this configuration has expressed that it
+     * should be considered metered.
+     *
+     * @see #isMetered(WifiConfiguration, WifiInfo)
      * @hide
-     * A hint about whether or not the network represented by this WifiConfiguration
-     * is metered. This is hinted at via the meteredHint bit on DHCP results set in
-     * {@link com.android.server.wifi.WifiStateMachine}, or via a network score in
-     * {@link com.android.server.wifi.ExternalScoreEvaluator}.
      */
     @SystemApi
     public boolean meteredHint;
 
+    /** {@hide} */
+    public static final int METERED_OVERRIDE_NONE = 0;
+    /** {@hide} */
+    public static final int METERED_OVERRIDE_METERED = 1;
+    /** {@hide} */
+    public static final int METERED_OVERRIDE_NOT_METERED = 2;
+
     /**
+     * Indicates if the end user has expressed an explicit opinion about the
+     * meteredness of this network, such as through the Settings app.
+     * <p>
+     * This should always override any values from {@link #meteredHint} or
+     * {@link WifiInfo#getMeteredHint()}.
+     *
+     * @see #isMetered(WifiConfiguration, WifiInfo)
      * @hide
-     * Indicates if a user has specified the WifiConfiguration to be metered. Users
-     * can toggle if a network is metered within Settings -> Data Usage -> Network
-     * Restrictions.
      */
-    public boolean meteredOverride;
+    public int meteredOverride = METERED_OVERRIDE_NONE;
+
+    /**
+     * Blend together all the various opinions to decide if the given network
+     * should be considered metered or not.
+     *
+     * @hide
+     */
+    public static boolean isMetered(WifiConfiguration config, WifiInfo info) {
+        boolean metered = false;
+        if (info != null && info.getMeteredHint()) {
+            metered = true;
+        }
+        if (config != null && config.meteredHint) {
+            metered = true;
+        }
+        if (config != null
+                && config.meteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED) {
+            metered = true;
+        }
+        if (config != null
+                && config.meteredOverride == WifiConfiguration.METERED_OVERRIDE_NOT_METERED) {
+            metered = false;
+        }
+        return metered;
+    }
 
     /**
      * @hide
@@ -1426,7 +1463,7 @@
         didSelfAdd = false;
         ephemeral = false;
         meteredHint = false;
-        meteredOverride = false;
+        meteredOverride = METERED_OVERRIDE_NONE;
         useExternalScores = false;
         validatedInternetAccess = false;
         mIpConfiguration = new IpConfiguration();
@@ -1520,23 +1557,24 @@
             sbuf.append(this.numNoInternetAccessReports).append("\n");
         }
         if (this.updateTime != null) {
-            sbuf.append("update ").append(this.updateTime).append("\n");
+            sbuf.append(" update ").append(this.updateTime).append("\n");
         }
         if (this.creationTime != null) {
-            sbuf.append("creation").append(this.creationTime).append("\n");
+            sbuf.append(" creation ").append(this.creationTime).append("\n");
         }
         if (this.didSelfAdd) sbuf.append(" didSelfAdd");
         if (this.selfAdded) sbuf.append(" selfAdded");
         if (this.validatedInternetAccess) sbuf.append(" validatedInternetAccess");
         if (this.ephemeral) sbuf.append(" ephemeral");
         if (this.meteredHint) sbuf.append(" meteredHint");
-        if (this.meteredOverride) sbuf.append(" meteredOverride");
         if (this.useExternalScores) sbuf.append(" useExternalScores");
         if (this.didSelfAdd || this.selfAdded || this.validatedInternetAccess
-            || this.ephemeral || this.meteredHint || this.meteredOverride
-            || this.useExternalScores) {
+            || this.ephemeral || this.meteredHint || this.useExternalScores) {
             sbuf.append("\n");
         }
+        if (this.meteredOverride != METERED_OVERRIDE_NONE) {
+            sbuf.append(" meteredOverride ").append(meteredOverride).append("\n");
+        }
         sbuf.append(" KeyMgmt:");
         for (int k = 0; k < this.allowedKeyManagement.size(); k++) {
             if (this.allowedKeyManagement.get(k)) {
@@ -2062,7 +2100,7 @@
         dest.writeInt(isLegacyPasspointConfig ? 1 : 0);
         dest.writeInt(ephemeral ? 1 : 0);
         dest.writeInt(meteredHint ? 1 : 0);
-        dest.writeInt(meteredOverride ? 1 : 0);
+        dest.writeInt(meteredOverride);
         dest.writeInt(useExternalScores ? 1 : 0);
         dest.writeInt(creatorUid);
         dest.writeInt(lastConnectUid);
@@ -2129,7 +2167,7 @@
                 config.isLegacyPasspointConfig = in.readInt() != 0;
                 config.ephemeral = in.readInt() != 0;
                 config.meteredHint = in.readInt() != 0;
-                config.meteredOverride = in.readInt() != 0;
+                config.meteredOverride = in.readInt();
                 config.useExternalScores = in.readInt() != 0;
                 config.creatorUid = in.readInt();
                 config.lastConnectUid = in.readInt();
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index e48f7bdb..bd25356 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -459,7 +459,13 @@
         return mMacAddress != null && !DEFAULT_MAC_ADDRESS.equals(mMacAddress);
     }
 
-    /** {@hide} */
+    /**
+     * Indicates if we've dynamically detected this active network connection as
+     * being metered.
+     *
+     * @see WifiConfiguration#isMetered(WifiConfiguration, WifiInfo)
+     * @hide
+     */
     public void setMeteredHint(boolean meteredHint) {
         mMeteredHint = meteredHint;
     }