Controls to set expensive (metered) networks.

Add UI to change metered flag on NetworkPolicy, and support Wi-Fi
policies per-SSID.  Create Wi-Fi policies as needed, but leave cycle
undefined.

Only show and mutate mobile policies when SIM state is ready.

Bug: 3001465, 3291052
Change-Id: I481a202fe0e68fc2f5adfd3b3a6f40347d2b168c
diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java
index d398e0b..656288a 100644
--- a/src/com/android/settings/DataUsageSummary.java
+++ b/src/com/android/settings/DataUsageSummary.java
@@ -35,11 +35,12 @@
 import static android.net.NetworkTemplate.buildTemplateMobile3gLower;
 import static android.net.NetworkTemplate.buildTemplateMobile4g;
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
-import static android.net.NetworkTemplate.buildTemplateWifi;
+import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
 import static android.net.TrafficStats.GB_IN_BYTES;
 import static android.net.TrafficStats.MB_IN_BYTES;
 import static android.net.TrafficStats.UID_REMOVED;
 import static android.net.TrafficStats.UID_TETHERING;
+import static android.telephony.TelephonyManager.SIM_STATE_READY;
 import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
 import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
@@ -83,6 +84,7 @@
 import android.os.SystemProperties;
 import android.os.UserId;
 import android.preference.Preference;
+import android.preference.PreferenceActivity;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -127,6 +129,7 @@
 import com.android.settings.drawable.InsetBoundsDrawable;
 import com.android.settings.net.ChartData;
 import com.android.settings.net.ChartDataLoader;
+import com.android.settings.net.DataUsageMeteredSettings;
 import com.android.settings.net.NetworkPolicyEditor;
 import com.android.settings.net.SummaryForAllUidLoader;
 import com.android.settings.net.UidDetail;
@@ -144,8 +147,8 @@
 import libcore.util.Objects;
 
 /**
- * Panel show data usage history across various networks, including options to
- * inspect based on usage cycle and control through {@link NetworkPolicy}.
+ * Panel showing data usage history across various networks, including options
+ * to inspect based on usage cycle and control through {@link NetworkPolicy}.
  */
 public class DataUsageSummary extends Fragment {
     private static final String TAG = "DataUsage";
@@ -180,7 +183,7 @@
 
     private INetworkManagementService mNetworkService;
     private INetworkStatsService mStatsService;
-    private INetworkPolicyManager mPolicyService;
+    private NetworkPolicyManager mPolicyManager;
     private ConnectivityManager mConnService;
 
     private static final String PREF_FILE = "data_usage";
@@ -253,19 +256,18 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        final Context context = getActivity();
 
         mNetworkService = INetworkManagementService.Stub.asInterface(
                 ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
         mStatsService = INetworkStatsService.Stub.asInterface(
                 ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
-        mPolicyService = INetworkPolicyManager.Stub.asInterface(
-                ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
-        mConnService = (ConnectivityManager) getActivity().getSystemService(
-                Context.CONNECTIVITY_SERVICE);
+        mPolicyManager = NetworkPolicyManager.from(context);
+        mConnService = ConnectivityManager.from(context);
 
         mPrefs = getActivity().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
 
-        mPolicyEditor = new NetworkPolicyEditor(mPolicyService);
+        mPolicyEditor = new NetworkPolicyEditor(mPolicyManager);
         mPolicyEditor.read();
 
         mShowWifi = mPrefs.getBoolean(PREF_SHOW_WIFI, false);
@@ -431,19 +433,19 @@
         final boolean appDetailMode = isAppDetailMode();
 
         mMenuDataRoaming = menu.findItem(R.id.data_usage_menu_roaming);
-        mMenuDataRoaming.setVisible(hasMobileRadio(context) && !appDetailMode);
+        mMenuDataRoaming.setVisible(hasReadyMobileRadio(context) && !appDetailMode);
         mMenuDataRoaming.setChecked(getDataRoaming());
 
         mMenuRestrictBackground = menu.findItem(R.id.data_usage_menu_restrict_background);
-        mMenuRestrictBackground.setVisible(hasMobileRadio(context) && !appDetailMode);
-        mMenuRestrictBackground.setChecked(getRestrictBackground());
+        mMenuRestrictBackground.setVisible(hasReadyMobileRadio(context) && !appDetailMode);
+        mMenuRestrictBackground.setChecked(mPolicyManager.getRestrictBackground());
 
         final MenuItem split4g = menu.findItem(R.id.data_usage_menu_split_4g);
         split4g.setVisible(hasMobile4gRadio(context) && !appDetailMode);
         split4g.setChecked(isMobilePolicySplit());
 
         final MenuItem showWifi = menu.findItem(R.id.data_usage_menu_show_wifi);
-        if (hasWifiRadio(context) && hasMobileRadio(context)) {
+        if (hasWifiRadio(context) && hasReadyMobileRadio(context)) {
             showWifi.setVisible(!appDetailMode);
             showWifi.setChecked(mShowWifi);
         } else {
@@ -452,13 +454,20 @@
         }
 
         final MenuItem showEthernet = menu.findItem(R.id.data_usage_menu_show_ethernet);
-        if (hasEthernet(context) && hasMobileRadio(context)) {
+        if (hasEthernet(context) && hasReadyMobileRadio(context)) {
             showEthernet.setVisible(!appDetailMode);
             showEthernet.setChecked(mShowEthernet);
         } else {
             showEthernet.setVisible(false);
             mShowEthernet = true;
         }
+
+        final MenuItem metered = menu.findItem(R.id.data_usage_menu_metered);
+        if (hasReadyMobileRadio(context) || hasWifiRadio(context)) {
+            metered.setVisible(!appDetailMode);
+        } else {
+            metered.setVisible(false);
+        }
     }
 
     @Override
@@ -505,6 +514,12 @@
                 updateTabs();
                 return true;
             }
+            case R.id.data_usage_menu_metered: {
+                final PreferenceActivity activity = (PreferenceActivity) getActivity();
+                activity.startPreferencePanel(DataUsageMeteredSettings.class.getCanonicalName(), null,
+                        R.string.data_usage_metered_title, null, this, 0);
+                return true;
+            }
         }
         return false;
     }
@@ -572,7 +587,7 @@
         if (mobileSplit && hasMobile4gRadio(context)) {
             mTabHost.addTab(buildTabSpec(TAB_3G, R.string.data_usage_tab_3g));
             mTabHost.addTab(buildTabSpec(TAB_4G, R.string.data_usage_tab_4g));
-        } else if (hasMobileRadio(context)) {
+        } else if (hasReadyMobileRadio(context)) {
             mTabHost.addTab(buildTabSpec(TAB_MOBILE, R.string.data_usage_tab_mobile));
         }
         if (mShowWifi && hasWifiRadio(context)) {
@@ -650,6 +665,9 @@
 
         mDataEnabledView.setVisibility(View.VISIBLE);
 
+        // TODO: remove mobile tabs when SIM isn't ready
+        final TelephonyManager tele = TelephonyManager.from(context);
+
         if (TAB_MOBILE.equals(currentTab)) {
             setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_mobile);
             setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_mobile_limit);
@@ -671,7 +689,7 @@
             // wifi doesn't have any controls
             mDataEnabledView.setVisibility(View.GONE);
             mDisableAtLimitView.setVisibility(View.GONE);
-            mTemplate = buildTemplateWifi();
+            mTemplate = buildTemplateWifiWildcard();
 
         } else if (TAB_ETHERNET.equals(currentTab)) {
             // ethernet doesn't have any controls
@@ -755,8 +773,8 @@
 
         updateDetailData();
 
-        if (UserId.isApp(appId) && !getRestrictBackground() && isBandwidthControlEnabled()
-                && hasMobileRadio(context)) {
+        if (UserId.isApp(appId) && !mPolicyManager.getRestrictBackground()
+                && isBandwidthControlEnabled() && hasReadyMobileRadio(context)) {
             setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background);
             setPreferenceSummary(mAppRestrictView,
                     getString(R.string.data_usage_app_restrict_background_summary));
@@ -829,48 +847,22 @@
         mMenuDataRoaming.setChecked(enabled);
     }
 
-    private boolean getRestrictBackground() {
-        try {
-            return mPolicyService.getRestrictBackground();
-        } catch (RemoteException e) {
-            Log.w(TAG, "problem talking with policy service: " + e);
-            return false;
-        }
-    }
-
-    private void setRestrictBackground(boolean restrictBackground) {
-        if (LOGD) Log.d(TAG, "setRestrictBackground()");
-        try {
-            mPolicyService.setRestrictBackground(restrictBackground);
-            mMenuRestrictBackground.setChecked(restrictBackground);
-        } catch (RemoteException e) {
-            Log.w(TAG, "problem talking with policy service: " + e);
-        }
+    public void setRestrictBackground(boolean restrictBackground) {
+        mPolicyManager.setRestrictBackground(restrictBackground);
+        mMenuRestrictBackground.setChecked(restrictBackground);
     }
 
     private boolean getAppRestrictBackground() {
         final int appId = mCurrentApp.appId;
-        final int uidPolicy;
-        try {
-            uidPolicy = mPolicyService.getAppPolicy(appId);
-        } catch (RemoteException e) {
-            // since we can't do much without policy, we bail hard.
-            throw new RuntimeException("problem reading network policy", e);
-        }
-
+        final int uidPolicy = mPolicyManager.getAppPolicy(appId);
         return (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
     }
 
     private void setAppRestrictBackground(boolean restrictBackground) {
         if (LOGD) Log.d(TAG, "setAppRestrictBackground()");
         final int appId = mCurrentApp.appId;
-        try {
-            mPolicyService.setAppPolicy(appId,
-                    restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE);
-        } catch (RemoteException e) {
-            throw new RuntimeException("unable to save policy", e);
-        }
-
+        mPolicyManager.setAppPolicy(appId,
+                restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE);
         mAppRestrict.setChecked(restrictBackground);
     }
 
@@ -1201,23 +1193,25 @@
 
     private boolean isMobilePolicySplit() {
         final Context context = getActivity();
-        if (hasMobileRadio(context)) {
-            final String subscriberId = getActiveSubscriberId(context);
-            return mPolicyEditor.isMobilePolicySplit(subscriberId);
+        if (hasReadyMobileRadio(context)) {
+            final TelephonyManager tele = TelephonyManager.from(context);
+            return mPolicyEditor.isMobilePolicySplit(getActiveSubscriberId(context));
         } else {
             return false;
         }
     }
 
     private void setMobilePolicySplit(boolean split) {
-        final String subscriberId = getActiveSubscriberId(getActivity());
-        mPolicyEditor.setMobilePolicySplit(subscriberId, split);
+        final Context context = getActivity();
+        if (hasReadyMobileRadio(context)) {
+            final TelephonyManager tele = TelephonyManager.from(context);
+            mPolicyEditor.setMobilePolicySplit(getActiveSubscriberId(context), split);
+        }
     }
 
     private static String getActiveSubscriberId(Context context) {
-        final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
-                Context.TELEPHONY_SERVICE);
-        final String actualSubscriberId = telephony.getSubscriberId();
+        final TelephonyManager tele = TelephonyManager.from(context);
+        final String actualSubscriberId = tele.getSubscriberId();
         return SystemProperties.get(TEST_SUBSCRIBER_PROP, actualSubscriberId);
     }
 
@@ -2048,22 +2042,24 @@
     }
 
     /**
-     * Test if device has a mobile data radio.
+     * Test if device has a mobile data radio with SIM in ready state.
      */
-    private static boolean hasMobileRadio(Context context) {
+    public static boolean hasReadyMobileRadio(Context context) {
         if (TEST_RADIOS) {
             return SystemProperties.get(TEST_RADIOS_PROP).contains("mobile");
         }
 
-        final ConnectivityManager conn = (ConnectivityManager) context.getSystemService(
-                Context.CONNECTIVITY_SERVICE);
-        return conn.isNetworkSupported(TYPE_MOBILE);
+        final ConnectivityManager conn = ConnectivityManager.from(context);
+        final TelephonyManager tele = TelephonyManager.from(context);
+
+        // require both supported network and ready SIM
+        return conn.isNetworkSupported(TYPE_MOBILE) && tele.getSimState() == SIM_STATE_READY;
     }
 
     /**
      * Test if device has a mobile 4G data radio.
      */
-    private static boolean hasMobile4gRadio(Context context) {
+    public static boolean hasMobile4gRadio(Context context) {
         if (!NetworkPolicyEditor.ENABLE_SPLIT_POLICIES) {
             return false;
         }
@@ -2071,39 +2067,35 @@
             return SystemProperties.get(TEST_RADIOS_PROP).contains("4g");
         }
 
-        final ConnectivityManager conn = (ConnectivityManager) context.getSystemService(
-                Context.CONNECTIVITY_SERVICE);
-        final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
-                Context.TELEPHONY_SERVICE);
+        final ConnectivityManager conn = ConnectivityManager.from(context);
+        final TelephonyManager tele = TelephonyManager.from(context);
 
         final boolean hasWimax = conn.isNetworkSupported(TYPE_WIMAX);
-        final boolean hasLte = telephony.getLteOnCdmaMode() == Phone.LTE_ON_CDMA_TRUE;
+        final boolean hasLte = tele.getLteOnCdmaMode() == Phone.LTE_ON_CDMA_TRUE;
         return hasWimax || hasLte;
     }
 
     /**
      * Test if device has a Wi-Fi data radio.
      */
-    private static boolean hasWifiRadio(Context context) {
+    public static boolean hasWifiRadio(Context context) {
         if (TEST_RADIOS) {
             return SystemProperties.get(TEST_RADIOS_PROP).contains("wifi");
         }
 
-        final ConnectivityManager conn = (ConnectivityManager) context.getSystemService(
-                Context.CONNECTIVITY_SERVICE);
+        final ConnectivityManager conn = ConnectivityManager.from(context);
         return conn.isNetworkSupported(TYPE_WIFI);
     }
 
     /**
      * Test if device has an ethernet network connection.
      */
-    private static boolean hasEthernet(Context context) {
+    public static boolean hasEthernet(Context context) {
         if (TEST_RADIOS) {
             return SystemProperties.get(TEST_RADIOS_PROP).contains("ethernet");
         }
 
-        final ConnectivityManager conn = (ConnectivityManager) context.getSystemService(
-                Context.CONNECTIVITY_SERVICE);
+        final ConnectivityManager conn = ConnectivityManager.from(context);
         return conn.isNetworkSupported(TYPE_ETHERNET);
     }
 
@@ -2138,6 +2130,7 @@
      * Build string describing currently limited networks, which defines when
      * background data is restricted.
      */
+    @Deprecated
     private CharSequence buildLimitedNetworksString() {
         final List<CharSequence> limited = buildLimitedNetworksList();
 
@@ -2153,22 +2146,28 @@
      * Build list of currently limited networks, which defines when background
      * data is restricted.
      */
+    @Deprecated
     private List<CharSequence> buildLimitedNetworksList() {
         final Context context = getActivity();
-        final String subscriberId = getActiveSubscriberId(context);
 
         // build combined list of all limited networks
         final ArrayList<CharSequence> limited = Lists.newArrayList();
-        if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobileAll(subscriberId))) {
-            limited.add(getText(R.string.data_usage_list_mobile));
+
+        final TelephonyManager tele = TelephonyManager.from(context);
+        if (tele.getSimState() == SIM_STATE_READY) {
+            final String subscriberId = getActiveSubscriberId(context);
+            if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobileAll(subscriberId))) {
+                limited.add(getText(R.string.data_usage_list_mobile));
+            }
+            if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobile3gLower(subscriberId))) {
+                limited.add(getText(R.string.data_usage_tab_3g));
+            }
+            if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobile4g(subscriberId))) {
+                limited.add(getText(R.string.data_usage_tab_4g));
+            }
         }
-        if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobile3gLower(subscriberId))) {
-            limited.add(getText(R.string.data_usage_tab_3g));
-        }
-        if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobile4g(subscriberId))) {
-            limited.add(getText(R.string.data_usage_tab_4g));
-        }
-        if (mPolicyEditor.hasLimitedPolicy(buildTemplateWifi())) {
+
+        if (mPolicyEditor.hasLimitedPolicy(buildTemplateWifiWildcard())) {
             limited.add(getText(R.string.data_usage_tab_wifi));
         }
         if (mPolicyEditor.hasLimitedPolicy(buildTemplateEthernet())) {
diff --git a/src/com/android/settings/net/DataUsageMeteredSettings.java b/src/com/android/settings/net/DataUsageMeteredSettings.java
new file mode 100644
index 0000000..d069a71
--- /dev/null
+++ b/src/com/android/settings/net/DataUsageMeteredSettings.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.net;
+
+import static com.android.settings.DataUsageSummary.hasReadyMobileRadio;
+import static com.android.settings.DataUsageSummary.hasWifiRadio;
+
+import android.content.Context;
+import android.net.NetworkPolicy;
+import android.net.NetworkPolicyManager;
+import android.net.NetworkTemplate;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.PreferenceCategory;
+import android.telephony.TelephonyManager;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+
+/**
+ * Panel to configure {@link NetworkPolicy#metered} for networks.
+ */
+public class DataUsageMeteredSettings extends SettingsPreferenceFragment {
+
+    private NetworkPolicyManager mPolicyManager;
+    private WifiManager mWifiManager;
+
+    private NetworkPolicyEditor mPolicyEditor;
+
+    private PreferenceCategory mMobileCategory;
+    private PreferenceCategory mWifiCategory;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        final Context context = getActivity();
+
+        mPolicyManager = NetworkPolicyManager.from(context);
+        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+
+        mPolicyEditor = new NetworkPolicyEditor(mPolicyManager);
+        mPolicyEditor.read();
+
+        addPreferencesFromResource(R.xml.data_usage_metered_prefs);
+        mMobileCategory = (PreferenceCategory) findPreference("mobile");
+        mWifiCategory = (PreferenceCategory) findPreference("wifi");
+
+        updateNetworks(context);
+
+    }
+
+    private void updateNetworks(Context context) {
+        if (hasReadyMobileRadio(context)) {
+            mMobileCategory.removeAll();
+            mMobileCategory.addPreference(buildMobilePref(context));
+        } else {
+            getPreferenceScreen().removePreference(mMobileCategory);
+        }
+
+        if (hasWifiRadio(context)) {
+            mWifiCategory.removeAll();
+            for (WifiConfiguration config : mWifiManager.getConfiguredNetworks()) {
+                if (config.SSID != null) {
+                    mWifiCategory.addPreference(buildWifiPref(context, config));
+                }
+            }
+        } else {
+            getPreferenceScreen().removePreference(mWifiCategory);
+        }
+    }
+
+    private Preference buildMobilePref(Context context) {
+        final TelephonyManager tele = TelephonyManager.from(context);
+        final NetworkTemplate template = NetworkTemplate.buildTemplateMobileAll(
+                tele.getSubscriberId());
+        final MeteredPreference pref = new MeteredPreference(context, template);
+        pref.setTitle(tele.getNetworkOperatorName());
+        return pref;
+    }
+
+    private Preference buildWifiPref(Context context, WifiConfiguration config) {
+        final String networkId = removeDoubleQuotes(config.SSID);
+        final NetworkTemplate template = NetworkTemplate.buildTemplateWifi(networkId);
+        final MeteredPreference pref = new MeteredPreference(context, template);
+        pref.setTitle(networkId);
+        return pref;
+    }
+
+    private class MeteredPreference extends CheckBoxPreference {
+        private final NetworkTemplate mTemplate;
+
+        public MeteredPreference(Context context, NetworkTemplate template) {
+            super(context);
+            mTemplate = template;
+
+            setPersistent(false);
+            setChecked(mPolicyEditor.getPolicyMetered(mTemplate));
+        }
+
+        @Override
+        protected void notifyChanged() {
+            super.notifyChanged();
+            mPolicyEditor.setPolicyMetered(mTemplate, isChecked());
+        }
+    }
+
+
+    private static String removeDoubleQuotes(String string) {
+        final int length = string.length();
+        if ((length > 1) && (string.charAt(0) == '"') && (string.charAt(length - 1) == '"')) {
+            return string.substring(1, length - 1);
+        }
+        return string;
+    }
+
+}
diff --git a/src/com/android/settings/net/NetworkPolicyEditor.java b/src/com/android/settings/net/NetworkPolicyEditor.java
index 80fcdb5..07a2afb 100644
--- a/src/com/android/settings/net/NetworkPolicyEditor.java
+++ b/src/com/android/settings/net/NetworkPolicyEditor.java
@@ -16,6 +16,7 @@
 
 package com.android.settings.net;
 
+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;
@@ -27,11 +28,10 @@
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
 import static com.android.internal.util.Preconditions.checkNotNull;
 
-import android.net.INetworkPolicyManager;
 import android.net.NetworkPolicy;
+import android.net.NetworkPolicyManager;
 import android.net.NetworkTemplate;
 import android.os.AsyncTask;
-import android.os.RemoteException;
 import android.text.format.Time;
 
 import com.android.internal.util.Objects;
@@ -43,27 +43,23 @@
 
 /**
  * Utility class to modify list of {@link NetworkPolicy}. Specifically knows
- * about which policies can coexist. Not thread safe.
+ * about which policies can coexist. This editor offers thread safety when
+ * talking with {@link NetworkPolicyManager}.
  */
 public class NetworkPolicyEditor {
     // TODO: be more robust when missing policies from service
 
     public static final boolean ENABLE_SPLIT_POLICIES = false;
 
-    private INetworkPolicyManager mPolicyService;
+    private NetworkPolicyManager mPolicyManager;
     private ArrayList<NetworkPolicy> mPolicies = Lists.newArrayList();
 
-    public NetworkPolicyEditor(INetworkPolicyManager policyService) {
-        mPolicyService = checkNotNull(policyService);
+    public NetworkPolicyEditor(NetworkPolicyManager policyManager) {
+        mPolicyManager = checkNotNull(policyManager);
     }
 
     public void read() {
-        final NetworkPolicy[] policies;
-        try {
-            policies = mPolicyService.getNetworkPolicies();
-        } catch (RemoteException e) {
-            throw new RuntimeException("problem reading policies", e);
-        }
+        final NetworkPolicy[] policies = mPolicyManager.getNetworkPolicies();
 
         boolean modified = false;
         mPolicies.clear();
@@ -78,12 +74,6 @@
                 modified = true;
             }
 
-            // drop any WIFI policies that were defined
-            if (policy.template.getMatchRule() == MATCH_WIFI) {
-                modified = true;
-                continue;
-            }
-
             mPolicies.add(policy);
         }
 
@@ -109,11 +99,7 @@
     }
 
     public void write(NetworkPolicy[] policies) {
-        try {
-            mPolicyService.setNetworkPolicies(policies);
-        } catch (RemoteException e) {
-            throw new RuntimeException("problem writing policies", e);
-        }
+        mPolicyManager.setNetworkPolicies(policies);
     }
 
     public boolean hasLimitedPolicy(NetworkTemplate template) {
@@ -142,13 +128,24 @@
     @Deprecated
     private static NetworkPolicy buildDefaultPolicy(NetworkTemplate template) {
         // TODO: move this into framework to share with NetworkPolicyManagerService
-        final Time time = new Time();
-        time.setToNow();
-        final int cycleDay = time.monthDay;
-        final String cycleTimezone = time.timezone;
+        final int cycleDay;
+        final String cycleTimezone;
+        final boolean metered;
+
+        if (template.getMatchRule() == MATCH_WIFI) {
+            cycleDay = CYCLE_NONE;
+            cycleTimezone = Time.TIMEZONE_UTC;
+            metered = false;
+        } else {
+            final Time time = new Time();
+            time.setToNow();
+            cycleDay = time.monthDay;
+            cycleTimezone = time.timezone;
+            metered = true;
+        }
 
         return new NetworkPolicy(template, cycleDay, cycleTimezone, WARNING_DISABLED,
-                LIMIT_DISABLED, SNOOZE_NEVER, SNOOZE_NEVER, true, false);
+                LIMIT_DISABLED, SNOOZE_NEVER, SNOOZE_NEVER, metered, true);
     }
 
     public int getPolicyCycleDay(NetworkTemplate template) {
@@ -188,6 +185,52 @@
         writeAsync();
     }
 
+    public boolean getPolicyMetered(NetworkTemplate template) {
+        final 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.template.getMatchRule() == MATCH_WIFI
+                    && policy.warningBytes == WARNING_DISABLED
+                    && policy.limitBytes == LIMIT_DISABLED) {
+                // when WIFI goes unmetered, and no other warning/limit for
+                // policy, clean it up.
+                mPolicies.remove(policy);
+                modified = true;
+            } else if (policy.metered) {
+                policy.metered = false;
+                policy.inferred = false;
+                modified = true;
+            }
+        }
+
+        if (modified) writeAsync();
+    }
+
     /**
      * Remove any split {@link NetworkPolicy}.
      */