Offer to "merge" subscribers for data usage.

There are some cases where multiple subscriber identities (IMSI)
should be treated as "merged together" from a data usage
perspective.  This is done by extending the template used for
matching purposes to support multiple subscribers.

Then, when we query historical usage or set network policies, we
normalize the matching template to merge to any other identities
that should be included.  When normalizing, the "lowest" identity
is always used for equality and storage purposes, which allows
identities to come and go over time.

This change also fixes data usage recording for multi-SIM devices
by passing along the concrete subscriber identity for each network
interface.  Also correctly create default policies for multi-SIM
devices.  This change also drops setPolicyDataEnable() until it can
be wired up to the right underlying NetworkAgent.  (This means we
still bring up the network, and then rely on iptables rules to block
traffic when over the limit, instead of proactively disabling the
connection.)

Bug: 18012787
Change-Id: If6acf32009fdfea2b836f5aff8e2f3e5e0248b4a
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 8021210..d9921a6 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -68,9 +68,6 @@
 
     boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress);
 
-    /** Policy control over specific {@link NetworkStateTracker}. */
-    void setPolicyDataEnable(int networkType, boolean enabled);
-
     int tether(String iface);
 
     int untether(String iface);
diff --git a/core/java/android/net/NetworkIdentity.java b/core/java/android/net/NetworkIdentity.java
index d36707e..6864749 100644
--- a/core/java/android/net/NetworkIdentity.java
+++ b/core/java/android/net/NetworkIdentity.java
@@ -25,6 +25,7 @@
 import android.net.wifi.WifiManager;
 import android.os.Build;
 import android.telephony.TelephonyManager;
+import android.util.Slog;
 
 import java.util.Objects;
 
@@ -35,6 +36,8 @@
  * @hide
  */
 public class NetworkIdentity implements Comparable<NetworkIdentity> {
+    private static final String TAG = "NetworkIdentity";
+
     /**
      * When enabled, combine all {@link #mSubType} together under
      * {@link #SUBTYPE_COMBINED}.
@@ -133,6 +136,18 @@
     }
 
     /**
+     * Scrub given IMSI on production builds.
+     */
+    public static String[] scrubSubscriberId(String[] subscriberId) {
+        if (subscriberId == null) return null;
+        final String[] res = new String[subscriberId.length];
+        for (int i = 0; i < res.length; i++) {
+            res[i] = NetworkIdentity.scrubSubscriberId(subscriberId[i]);
+        }
+        return res;
+    }
+
+    /**
      * Build a {@link NetworkIdentity} from the given {@link NetworkState},
      * assuming that any mobile networks are using the current IMSI.
      */
@@ -140,23 +155,18 @@
         final int type = state.networkInfo.getType();
         final int subType = state.networkInfo.getSubtype();
 
-        // TODO: consider moving subscriberId over to LinkCapabilities, so it
-        // comes from an authoritative source.
-
         String subscriberId = null;
         String networkId = null;
         boolean roaming = false;
 
         if (isNetworkTypeMobile(type)) {
-            final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
-                    Context.TELEPHONY_SERVICE);
-            roaming = telephony.isNetworkRoaming();
-            if (state.subscriberId != null) {
-                subscriberId = state.subscriberId;
-            } else {
-                subscriberId = telephony.getSubscriberId();
+            if (state.subscriberId == null) {
+                Slog.w(TAG, "Active mobile network without subscriber!");
             }
 
+            subscriberId = state.subscriberId;
+            roaming = state.networkInfo.isRoaming();
+
         } else if (type == TYPE_WIFI) {
             if (state.networkId != null) {
                 networkId = state.networkId;
diff --git a/core/java/android/net/NetworkMisc.java b/core/java/android/net/NetworkMisc.java
index 5d2a43d..b92c9e3 100644
--- a/core/java/android/net/NetworkMisc.java
+++ b/core/java/android/net/NetworkMisc.java
@@ -20,15 +20,18 @@
 import android.os.Parcelable;
 
 /**
- * A grab-bag of information (metadata, policies, properties, etc) about a {@link Network}.
+ * A grab-bag of information (metadata, policies, properties, etc) about a
+ * {@link Network}. Since this contains PII, it should not be sent outside the
+ * system.
  *
  * @hide
  */
 public class NetworkMisc implements Parcelable {
 
     /**
-     * If the {@link Network} is a VPN, whether apps are allowed to bypass the VPN. This is set by
-     * a {@link VpnService} and used by {@link ConnectivityService} when creating a VPN.
+     * If the {@link Network} is a VPN, whether apps are allowed to bypass the
+     * VPN. This is set by a {@link VpnService} and used by
+     * {@link ConnectivityManager} when creating a VPN.
      */
     public boolean allowBypass;
 
@@ -41,6 +44,11 @@
      */
     public boolean explicitlySelected;
 
+    /**
+     * For mobile networks, this is the subscriber ID (such as IMSI).
+     */
+    public String subscriberId;
+
     public NetworkMisc() {
     }
 
@@ -48,6 +56,7 @@
         if (nm != null) {
             allowBypass = nm.allowBypass;
             explicitlySelected = nm.explicitlySelected;
+            subscriberId = nm.subscriberId;
         }
     }
 
@@ -60,6 +69,7 @@
     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(allowBypass ? 1 : 0);
         out.writeInt(explicitlySelected ? 1 : 0);
+        out.writeString(subscriberId);
     }
 
     public static final Creator<NetworkMisc> CREATOR = new Creator<NetworkMisc>() {
@@ -68,6 +78,7 @@
             NetworkMisc networkMisc = new NetworkMisc();
             networkMisc.allowBypass = in.readInt() != 0;
             networkMisc.explicitlySelected = in.readInt() != 0;
+            networkMisc.subscriberId = in.readString();
             return networkMisc;
         }
 
diff --git a/core/java/android/net/NetworkPolicy.java b/core/java/android/net/NetworkPolicy.java
index 10c686b..e88bc26 100644
--- a/core/java/android/net/NetworkPolicy.java
+++ b/core/java/android/net/NetworkPolicy.java
@@ -35,7 +35,7 @@
     public static final long LIMIT_DISABLED = -1;
     public static final long SNOOZE_NEVER = -1;
 
-    public final NetworkTemplate template;
+    public NetworkTemplate template;
     public int cycleDay;
     public String cycleTimezone;
     public long warningBytes;
diff --git a/core/java/android/net/NetworkState.java b/core/java/android/net/NetworkState.java
index d26c70d..933287f 100644
--- a/core/java/android/net/NetworkState.java
+++ b/core/java/android/net/NetworkState.java
@@ -30,16 +30,10 @@
     public final LinkProperties linkProperties;
     public final NetworkCapabilities networkCapabilities;
     public final Network network;
-    /** Currently only used by testing. */
     public final String subscriberId;
     public final String networkId;
 
     public NetworkState(NetworkInfo networkInfo, LinkProperties linkProperties,
-            NetworkCapabilities networkCapabilities, Network network) {
-        this(networkInfo, linkProperties, networkCapabilities, network, null, null);
-    }
-
-    public NetworkState(NetworkInfo networkInfo, LinkProperties linkProperties,
             NetworkCapabilities networkCapabilities, Network network, String subscriberId,
             String networkId) {
         this.networkInfo = networkInfo;
@@ -85,5 +79,4 @@
             return new NetworkState[size];
         }
     };
-
 }
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index b839e0a..6cfab92 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -22,7 +22,6 @@
 import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
 import static android.net.ConnectivityManager.TYPE_WIMAX;
 import static android.net.NetworkIdentity.COMBINE_SUBTYPE_ENABLED;
-import static android.net.NetworkIdentity.scrubSubscriberId;
 import static android.net.wifi.WifiInfo.removeDoubleQuotes;
 import static android.telephony.TelephonyManager.NETWORK_CLASS_2_G;
 import static android.telephony.TelephonyManager.NETWORK_CLASS_3_G;
@@ -36,7 +35,9 @@
 import android.os.Parcelable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 
+import java.util.Arrays;
 import java.util.Objects;
 
 /**
@@ -146,17 +147,35 @@
 
     private final int mMatchRule;
     private final String mSubscriberId;
+
+    /**
+     * Ugh, templates are designed to target a single subscriber, but we might
+     * need to match several "merged" subscribers. These are the subscribers
+     * that should be considered to match this template.
+     * <p>
+     * Since the merge set is dynamic, it should <em>not</em> be persisted or
+     * used for determining equality.
+     */
+    private final String[] mMatchSubscriberIds;
+
     private final String mNetworkId;
 
     public NetworkTemplate(int matchRule, String subscriberId, String networkId) {
+        this(matchRule, subscriberId, new String[] { subscriberId }, networkId);
+    }
+
+    public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
+            String networkId) {
         mMatchRule = matchRule;
         mSubscriberId = subscriberId;
+        mMatchSubscriberIds = matchSubscriberIds;
         mNetworkId = networkId;
     }
 
     private NetworkTemplate(Parcel in) {
         mMatchRule = in.readInt();
         mSubscriberId = in.readString();
+        mMatchSubscriberIds = in.createStringArray();
         mNetworkId = in.readString();
     }
 
@@ -164,6 +183,7 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mMatchRule);
         dest.writeString(mSubscriberId);
+        dest.writeStringArray(mMatchSubscriberIds);
         dest.writeString(mNetworkId);
     }
 
@@ -177,7 +197,12 @@
         final StringBuilder builder = new StringBuilder("NetworkTemplate: ");
         builder.append("matchRule=").append(getMatchRuleName(mMatchRule));
         if (mSubscriberId != null) {
-            builder.append(", subscriberId=").append(scrubSubscriberId(mSubscriberId));
+            builder.append(", subscriberId=").append(
+                    NetworkIdentity.scrubSubscriberId(mSubscriberId));
+        }
+        if (mMatchSubscriberIds != null) {
+            builder.append(", matchSubscriberIds=").append(
+                    Arrays.toString(NetworkIdentity.scrubSubscriberId(mMatchSubscriberIds)));
         }
         if (mNetworkId != null) {
             builder.append(", networkId=").append(mNetworkId);
@@ -201,6 +226,18 @@
         return false;
     }
 
+    public boolean isMatchRuleMobile() {
+        switch (mMatchRule) {
+            case MATCH_MOBILE_3G_LOWER:
+            case MATCH_MOBILE_4G:
+            case MATCH_MOBILE_ALL:
+            case MATCH_MOBILE_WILDCARD:
+                return true;
+            default:
+                return false;
+        }
+    }
+
     public int getMatchRule() {
         return mMatchRule;
     }
@@ -247,8 +284,9 @@
             // TODO: consider matching against WiMAX subscriber identity
             return true;
         } else {
-            return ((sForceAllNetworkTypes || contains(DATA_USAGE_NETWORK_TYPES, ident.mType))
-                    && Objects.equals(mSubscriberId, ident.mSubscriberId));
+            final boolean matchesType = (sForceAllNetworkTypes
+                    || contains(DATA_USAGE_NETWORK_TYPES, ident.mType));
+            return matchesType && ArrayUtils.contains(mMatchSubscriberIds, ident.mSubscriberId);
         }
     }
 
@@ -368,6 +406,27 @@
         }
     }
 
+    /**
+     * Examine the given template and normalize if it refers to a "merged"
+     * mobile subscriber. We pick the "lowest" merged subscriber as the primary
+     * for key purposes, and expand the template to match all other merged
+     * subscribers.
+     * <p>
+     * For example, given an incoming template matching B, and the currently
+     * active merge set [A,B], we'd return a new template that primarily matches
+     * A, but also matches B.
+     */
+    public static NetworkTemplate normalize(NetworkTemplate template, String[] merged) {
+        if (template.isMatchRuleMobile() && ArrayUtils.contains(merged, template.mSubscriberId)) {
+            // Requested template subscriber is part of the merge group; return
+            // a template that matches all merged subscribers.
+            return new NetworkTemplate(template.mMatchRule, merged[0], merged,
+                    template.mNetworkId);
+        } else {
+            return template;
+        }
+    }
+
     public static final Creator<NetworkTemplate> CREATOR = new Creator<NetworkTemplate>() {
         @Override
         public NetworkTemplate createFromParcel(Parcel in) {
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index 8e786da..f908fcb 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -128,6 +128,20 @@
     }
 
     /**
+     * Checks if given array is null or has zero elements.
+     */
+    public static boolean isEmpty(int[] array) {
+        return array == null || array.length == 0;
+    }
+
+    /**
+     * Checks if given array is null or has zero elements.
+     */
+    public static boolean isEmpty(long[] array) {
+        return array == null || array.length == 0;
+    }
+
+    /**
      * Checks that value is present as at least one of the elements of the array.
      * @param array the array to check in
      * @param value the value to check for
@@ -157,6 +171,7 @@
      * Test if all {@code check} items are contained in {@code array}.
      */
     public static <T> boolean containsAll(T[] array, T[] check) {
+        if (check == null) return true;
         for (T checkItem : check) {
             if (!contains(array, checkItem)) {
                 return false;
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 555a121..f3ffec6 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -20,22 +20,8 @@
 import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
-import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
-import static android.net.ConnectivityManager.TYPE_DUMMY;
-import static android.net.ConnectivityManager.TYPE_MOBILE;
-import static android.net.ConnectivityManager.TYPE_MOBILE_CBS;
-import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
-import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
-import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
-import static android.net.ConnectivityManager.TYPE_MOBILE_IA;
-import static android.net.ConnectivityManager.TYPE_MOBILE_IMS;
-import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
-import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL;
 import static android.net.ConnectivityManager.TYPE_NONE;
-import static android.net.ConnectivityManager.TYPE_PROXY;
 import static android.net.ConnectivityManager.TYPE_VPN;
-import static android.net.ConnectivityManager.TYPE_WIFI;
-import static android.net.ConnectivityManager.TYPE_WIMAX;
 import static android.net.ConnectivityManager.getNetworkTypeName;
 import static android.net.ConnectivityManager.isNetworkTypeValid;
 import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
@@ -45,11 +31,9 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
-import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
-import android.content.ContextWrapper;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
@@ -62,7 +46,6 @@
 import android.net.INetworkPolicyListener;
 import android.net.INetworkPolicyManager;
 import android.net.INetworkStatsService;
-import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.LinkProperties.CompareResult;
 import android.net.MobileDataStateTracker;
@@ -72,7 +55,6 @@
 import android.net.NetworkConfig;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkFactory;
 import android.net.NetworkMisc;
 import android.net.NetworkQuotaInfo;
 import android.net.NetworkRequest;
@@ -80,16 +62,12 @@
 import android.net.NetworkStateTracker;
 import android.net.NetworkUtils;
 import android.net.Proxy;
-import android.net.ProxyDataTracker;
 import android.net.ProxyInfo;
 import android.net.RouteInfo;
 import android.net.SamplingDataTracker;
 import android.net.UidRange;
 import android.net.Uri;
-import android.net.wimax.WimaxManagerConstants;
-import android.os.AsyncTask;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.FileUtils;
 import android.os.Handler;
@@ -103,7 +81,6 @@
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -126,9 +103,6 @@
 import com.android.internal.net.VpnConfig;
 import com.android.internal.net.VpnProfile;
 import com.android.internal.telephony.DctConstants;
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.XmlUtils;
@@ -146,8 +120,6 @@
 import com.google.android.collect.Lists;
 import com.google.android.collect.Sets;
 
-import dalvik.system.DexClassLoader;
-
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -157,30 +129,19 @@
 import java.io.FileReader;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.lang.reflect.Constructor;
-import java.net.HttpURLConnection;
 import java.net.Inet4Address;
-import java.net.Inet6Address;
 import java.net.InetAddress;
-import java.net.URL;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.GregorianCalendar;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Random;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLSession;
-
 /**
  * @hide
  */
@@ -307,12 +268,6 @@
     private static final int EVENT_SEND_STICKY_BROADCAST_INTENT = 11;
 
     /**
-     * Used internally to
-     * {@link NetworkStateTracker#setPolicyDataEnable(boolean)}.
-     */
-    private static final int EVENT_SET_POLICY_DATA_ENABLE = 12;
-
-    /**
      * Used internally to disable fail fast of mobile data
      */
     private static final int EVENT_ENABLE_FAIL_FAST_MOBILE_DATA = 14;
@@ -818,6 +773,7 @@
         LinkProperties lp = null;
         NetworkCapabilities nc = null;
         Network network = null;
+        String subscriberId = null;
 
         if (mLegacyTypeTracker.isTypeSupported(networkType)) {
             NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
@@ -827,6 +783,7 @@
                     lp = new LinkProperties(nai.linkProperties);
                     nc = new NetworkCapabilities(nai.networkCapabilities);
                     network = new Network(nai.network);
+                    subscriberId = (nai.networkMisc != null) ? nai.networkMisc.subscriberId : null;
                 }
                 info.setType(networkType);
             } else {
@@ -840,7 +797,7 @@
             info = getFilteredNetworkInfo(info, lp, uid);
         }
 
-        return new NetworkState(info, lp, nc, network);
+        return new NetworkState(info, lp, nc, network, subscriberId, null);
     }
 
     private NetworkAgentInfo getNetworkAgentInfoForNetwork(Network network) {
@@ -857,6 +814,7 @@
         LinkProperties lp = null;
         NetworkCapabilities nc = null;
         Network network = null;
+        String subscriberId = null;
 
         NetworkAgentInfo nai = mNetworkForRequestId.get(mDefaultRequest.requestId);
 
@@ -888,10 +846,11 @@
                 lp = new LinkProperties(nai.linkProperties);
                 nc = new NetworkCapabilities(nai.networkCapabilities);
                 network = new Network(nai.network);
+                subscriberId = (nai.networkMisc != null) ? nai.networkMisc.subscriberId : null;
             }
         }
 
-        return new NetworkState(info, lp, nc, network);
+        return new NetworkState(info, lp, nc, network, subscriberId, null);
     }
 
     /**
@@ -1188,14 +1147,19 @@
 
     @Override
     public NetworkState[] getAllNetworkState() {
-        enforceAccessPermission();
-        final int uid = Binder.getCallingUid();
+        // Require internal since we're handing out IMSI details
+        enforceConnectivityInternalPermission();
+
         final ArrayList<NetworkState> result = Lists.newArrayList();
-        for (int networkType = 0; networkType <= ConnectivityManager.MAX_NETWORK_TYPE;
-                networkType++) {
-            NetworkState state = getFilteredNetworkState(networkType, uid);
-            if (state.networkInfo != null) {
-                result.add(state);
+        for (Network network : getAllNetworks()) {
+            final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+            if (nai != null) {
+                synchronized (nai) {
+                    final String subscriberId = (nai.networkMisc != null)
+                            ? nai.networkMisc.subscriberId : null;
+                    result.add(new NetworkState(nai.networkInfo, nai.linkProperties,
+                            nai.networkCapabilities, network, subscriberId, null));
+                }
             }
         }
         return result.toArray(new NetworkState[result.size()]);
@@ -1420,25 +1384,6 @@
         }
     };
 
-    @Override
-    public void setPolicyDataEnable(int networkType, boolean enabled) {
-        // only someone like NPMS should only be calling us
-        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
-
-        mHandler.sendMessage(mHandler.obtainMessage(
-                EVENT_SET_POLICY_DATA_ENABLE, networkType, (enabled ? ENABLED : DISABLED)));
-    }
-
-    private void handleSetPolicyDataEnable(int networkType, boolean enabled) {
-   // TODO - handle this passing to factories
-//        if (isNetworkTypeValid(networkType)) {
-//            final NetworkStateTracker tracker = mNetTrackers[networkType];
-//            if (tracker != null) {
-//                tracker.setPolicyDataEnable(enabled);
-//            }
-//        }
-    }
-
     private void enforceInternetPermission() {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.INTERNET,
@@ -2392,12 +2337,6 @@
                     sendStickyBroadcast(intent);
                     break;
                 }
-                case EVENT_SET_POLICY_DATA_ENABLE: {
-                    final int networkType = msg.arg1;
-                    final boolean enabled = msg.arg2 == ENABLED;
-                    handleSetPolicyDataEnable(networkType, enabled);
-                    break;
-                }
                 case EVENT_ENABLE_FAIL_FAST_MOBILE_DATA: {
                     int tag = mEnableFailFastMobileDataTag.get();
                     if (msg.arg1 == tag) {
@@ -3793,7 +3732,6 @@
         mNumDnsEntries = last;
     }
 
-
     private void updateCapabilities(NetworkAgentInfo networkAgent,
             NetworkCapabilities networkCapabilities) {
         if (!Objects.equals(networkAgent.networkCapabilities, networkCapabilities)) {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index cf19416..2be591b 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -120,7 +120,9 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.text.format.Formatter;
 import android.text.format.Time;
 import android.util.ArrayMap;
@@ -136,16 +138,17 @@
 import android.util.TrustedTime;
 import android.util.Xml;
 
+import libcore.io.IoUtils;
+
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
 import com.google.android.collect.Lists;
 
-import libcore.io.IoUtils;
-
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -160,7 +163,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Objects;
 
 /**
  * Service that maintains low-level network policy rules, using
@@ -253,38 +255,38 @@
     private final boolean mSuppressDefaultPolicy;
 
     /** Defined network policies. */
-    final ArrayMap<NetworkTemplate, NetworkPolicy> mNetworkPolicy = new ArrayMap<
-            NetworkTemplate, NetworkPolicy>();
+    final ArrayMap<NetworkTemplate, NetworkPolicy> mNetworkPolicy = new ArrayMap<>();
     /** Currently active network rules for ifaces. */
-    private final ArrayMap<NetworkPolicy, String[]> mNetworkRules = new ArrayMap<
-            NetworkPolicy, String[]>();
+    final ArrayMap<NetworkPolicy, String[]> mNetworkRules = new ArrayMap<>();
 
     /** Defined UID policies. */
     final SparseIntArray mUidPolicy = new SparseIntArray();
     /** Currently derived rules for each UID. */
-    private final SparseIntArray mUidRules = new SparseIntArray();
+    final SparseIntArray mUidRules = new SparseIntArray();
 
-    /** UIDs that have been white-listed to always be able to have network access in
-     * power save mode. */
+    /**
+     * UIDs that have been white-listed to always be able to have network access
+     * in power save mode.
+     */
     private final SparseBooleanArray mPowerSaveWhitelistAppIds = new SparseBooleanArray();
 
     /** Set of ifaces that are metered. */
-    private ArraySet<String> mMeteredIfaces = new ArraySet<String>();
+    private ArraySet<String> mMeteredIfaces = new ArraySet<>();
     /** Set of over-limit templates that have been notified. */
-    private final ArraySet<NetworkTemplate> mOverLimitNotified = new ArraySet<NetworkTemplate>();
+    private final ArraySet<NetworkTemplate> mOverLimitNotified = new ArraySet<>();
 
     /** Set of currently active {@link Notification} tags. */
     private final ArraySet<String> mActiveNotifs = new ArraySet<String>();
 
     /** Foreground at both UID and PID granularity. */
-    private final SparseIntArray mUidState = new SparseIntArray();
-    final SparseArray<SparseIntArray> mUidPidState = new SparseArray<SparseIntArray>();
+    final SparseIntArray mUidState = new SparseIntArray();
+    final SparseArray<SparseIntArray> mUidPidState = new SparseArray<>();
 
     /** The current maximum process state that we are considering to be foreground. */
     private int mCurForegroundState = ActivityManager.PROCESS_STATE_TOP;
 
-    private final RemoteCallbackList<INetworkPolicyListener> mListeners = new RemoteCallbackList<
-            INetworkPolicyListener>();
+    private final RemoteCallbackList<INetworkPolicyListener>
+            mListeners = new RemoteCallbackList<>();
 
     final Handler mHandler;
 
@@ -740,21 +742,24 @@
      * data connection status.
      */
     private boolean isTemplateRelevant(NetworkTemplate template) {
-        final TelephonyManager tele = TelephonyManager.from(mContext);
+        if (template.isMatchRuleMobile()) {
+            final TelephonyManager tele = TelephonyManager.from(mContext);
+            final SubscriptionManager sub = SubscriptionManager.from(mContext);
 
-        switch (template.getMatchRule()) {
-            case MATCH_MOBILE_3G_LOWER:
-            case MATCH_MOBILE_4G:
-            case MATCH_MOBILE_ALL:
-                // mobile templates are relevant when SIM is ready and
-                // subscriberId matches.
-                if (tele.getSimState() == SIM_STATE_READY) {
-                    return Objects.equals(tele.getSubscriberId(), template.getSubscriberId());
-                } else {
-                    return false;
+            // Mobile template is relevant when any active subscriber matches
+            final int[] subIds = sub.getActiveSubscriptionIdList();
+            for (int subId : subIds) {
+                final String subscriberId = tele.getSubscriberId(subId);
+                final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE,
+                        TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false);
+                if (template.matches(probeIdent)) {
+                    return true;
                 }
+            }
+            return false;
+        } else {
+            return true;
         }
-        return true;
     }
 
     /**
@@ -961,6 +966,7 @@
             maybeRefreshTrustedTime();
             synchronized (mRulesLock) {
                 ensureActiveMobilePolicyLocked();
+                normalizePoliciesLocked();
                 updateNetworkEnabledLocked();
                 updateNetworkRulesLocked();
                 updateNotificationsLocked();
@@ -1001,33 +1007,12 @@
     }
 
     /**
-     * Control {@link IConnectivityManager#setPolicyDataEnable(int, boolean)}
-     * for the given {@link NetworkTemplate}.
+     * Proactively disable networks that match the given
+     * {@link NetworkTemplate}.
      */
     private void setNetworkTemplateEnabled(NetworkTemplate template, boolean enabled) {
-        final TelephonyManager tele = TelephonyManager.from(mContext);
-
-        switch (template.getMatchRule()) {
-            case MATCH_MOBILE_3G_LOWER:
-            case MATCH_MOBILE_4G:
-            case MATCH_MOBILE_ALL:
-                // TODO: offer more granular control over radio states once
-                // 4965893 is available.
-                if (tele.getSimState() == SIM_STATE_READY
-                        && Objects.equals(tele.getSubscriberId(), template.getSubscriberId())) {
-                    setPolicyDataEnable(TYPE_MOBILE, enabled);
-                    setPolicyDataEnable(TYPE_WIMAX, enabled);
-                }
-                break;
-            case MATCH_WIFI:
-                setPolicyDataEnable(TYPE_WIFI, enabled);
-                break;
-            case MATCH_ETHERNET:
-                setPolicyDataEnable(TYPE_ETHERNET, enabled);
-                break;
-            default:
-                throw new IllegalArgumentException("unexpected template");
-        }
+        // TODO: reach into ConnectivityManager to proactively disable bringing
+        // up this network, since we know that traffic will be blocked.
     }
 
     /**
@@ -1036,7 +1021,7 @@
      * remaining quota based on usage cycle and historical stats.
      */
     void updateNetworkRulesLocked() {
-        if (LOGV) Slog.v(TAG, "updateIfacesLocked()");
+        if (LOGV) Slog.v(TAG, "updateNetworkRulesLocked()");
 
         final NetworkState[] states;
         try {
@@ -1203,42 +1188,47 @@
         if (mSuppressDefaultPolicy) return;
 
         final TelephonyManager tele = TelephonyManager.from(mContext);
+        final SubscriptionManager sub = SubscriptionManager.from(mContext);
 
-        // avoid creating policy when SIM isn't ready
-        if (tele.getSimState() != SIM_STATE_READY) return;
+        final int[] subIds = sub.getActiveSubscriptionIdList();
+        for (int subId : subIds) {
+            final String subscriberId = tele.getSubscriberId(subId);
+            ensureActiveMobilePolicyLocked(subscriberId);
+        }
+    }
 
-        final String subscriberId = tele.getSubscriberId();
-        final NetworkIdentity probeIdent = new NetworkIdentity(
-                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false);
-
-        // examine to see if any policy is defined for active mobile
-        boolean mobileDefined = false;
-        for (int i = mNetworkPolicy.size()-1; i >= 0; i--) {
-            if (mNetworkPolicy.valueAt(i).template.matches(probeIdent)) {
-                mobileDefined = true;
-                break;
+    private void ensureActiveMobilePolicyLocked(String subscriberId) {
+        // Poke around to see if we already have a policy
+        final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE,
+                TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false);
+        for (int i = mNetworkPolicy.size() - 1; i >= 0; i--) {
+            final NetworkTemplate template = mNetworkPolicy.keyAt(i);
+            if (template.matches(probeIdent)) {
+                if (LOGD) {
+                    Slog.d(TAG, "Found template " + template + " which matches subscriber "
+                            + NetworkIdentity.scrubSubscriberId(subscriberId));
+                }
+                return;
             }
         }
 
-        if (!mobileDefined) {
-            Slog.i(TAG, "no policy for active mobile network; generating default policy");
+        Slog.i(TAG, "No policy for subscriber " + NetworkIdentity.scrubSubscriberId(subscriberId)
+                + "; generating default policy");
 
-            // build default mobile policy, and assume usage cycle starts today
-            final long warningBytes = mContext.getResources().getInteger(
-                    com.android.internal.R.integer.config_networkPolicyDefaultWarning)
-                    * MB_IN_BYTES;
+        // Build default mobile policy, and assume usage cycle starts today
+        final long warningBytes = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_networkPolicyDefaultWarning) * MB_IN_BYTES;
 
-            final Time time = new Time();
-            time.setToNow();
+        final Time time = new Time();
+        time.setToNow();
 
-            final int cycleDay = time.monthDay;
-            final String cycleTimezone = time.timezone;
+        final int cycleDay = time.monthDay;
+        final String cycleTimezone = time.timezone;
 
-            final NetworkTemplate template = buildTemplateMobileAll(subscriberId);
-            final NetworkPolicy policy = new NetworkPolicy(template, cycleDay, cycleTimezone,
-                    warningBytes, LIMIT_DISABLED, SNOOZE_NEVER, SNOOZE_NEVER, true, true);
-            addNetworkPolicyLocked(policy);
-        }
+        final NetworkTemplate template = buildTemplateMobileAll(subscriberId);
+        final NetworkPolicy policy = new NetworkPolicy(template, cycleDay, cycleTimezone,
+                warningBytes, LIMIT_DISABLED, SNOOZE_NEVER, SNOOZE_NEVER, true, true);
+        addNetworkPolicyLocked(policy);
     }
 
     private void readPolicyLocked() {
@@ -1321,8 +1311,8 @@
                             inferred = false;
                         }
 
-                        final NetworkTemplate template = new NetworkTemplate(
-                                networkTemplate, subscriberId, networkId);
+                        final NetworkTemplate template = new NetworkTemplate(networkTemplate,
+                                subscriberId, networkId);
                         mNetworkPolicy.put(template, new NetworkPolicy(template, cycleDay,
                                 cycleTimezone, warningBytes, limitBytes, lastWarningSnooze,
                                 lastLimitSnooze, metered, inferred));
@@ -1593,11 +1583,7 @@
 
         maybeRefreshTrustedTime();
         synchronized (mRulesLock) {
-            mNetworkPolicy.clear();
-            for (NetworkPolicy policy : policies) {
-                mNetworkPolicy.put(policy.template, policy);
-            }
-
+            normalizePoliciesLocked(policies);
             updateNetworkEnabledLocked();
             updateNetworkRulesLocked();
             updateNotificationsLocked();
@@ -1606,12 +1592,9 @@
     }
 
     void addNetworkPolicyLocked(NetworkPolicy policy) {
-        mNetworkPolicy.put(policy.template, policy);
-
-        updateNetworkEnabledLocked();
-        updateNetworkRulesLocked();
-        updateNotificationsLocked();
-        writePolicyLocked();
+        NetworkPolicy[] policies = getNetworkPolicies();
+        policies = ArrayUtils.appendElement(NetworkPolicy.class, policies, policy);
+        setNetworkPolicies(policies);
     }
 
     @Override
@@ -1620,7 +1603,35 @@
         mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, TAG);
 
         synchronized (mRulesLock) {
-            return mNetworkPolicy.values().toArray(new NetworkPolicy[mNetworkPolicy.size()]);
+            final int size = mNetworkPolicy.size();
+            final NetworkPolicy[] policies = new NetworkPolicy[size];
+            for (int i = 0; i < size; i++) {
+                policies[i] = mNetworkPolicy.valueAt(i);
+            }
+            return policies;
+        }
+    }
+
+    private void normalizePoliciesLocked() {
+        normalizePoliciesLocked(getNetworkPolicies());
+    }
+
+    private void normalizePoliciesLocked(NetworkPolicy[] policies) {
+        final TelephonyManager tele = TelephonyManager.from(mContext);
+        final String[] merged = tele.getMergedSubscriberIds();
+
+        mNetworkPolicy.clear();
+        for (NetworkPolicy policy : policies) {
+            // When two normalized templates conflict, prefer the most
+            // restrictive policy
+            policy.template = NetworkTemplate.normalize(policy.template, merged);
+            final NetworkPolicy existing = mNetworkPolicy.get(policy.template);
+            if (existing == null || existing.compareTo(policy) > 0) {
+                if (existing != null) {
+                    Slog.d(TAG, "Normalization replaced " + existing + " with " + policy);
+                }
+                mNetworkPolicy.put(policy.template, policy);
+            }
         }
     }
 
@@ -1657,6 +1668,7 @@
                     throw new IllegalArgumentException("unexpected type");
             }
 
+            normalizePoliciesLocked();
             updateNetworkEnabledLocked();
             updateNetworkRulesLocked();
             updateNotificationsLocked();
@@ -1784,6 +1796,7 @@
                     mNetworkPolicy.valueAt(i).clearSnooze();
                 }
 
+                normalizePoliciesLocked();
                 updateNetworkEnabledLocked();
                 updateNetworkRulesLocked();
                 updateNotificationsLocked();
@@ -1976,6 +1989,7 @@
 
         // If the set of restricted networks may have changed, re-evaluate those.
         if (restrictedNetworksChanged) {
+            normalizePoliciesLocked();
             updateNetworkRulesLocked();
         }
     }
@@ -2162,17 +2176,6 @@
         }
     }
 
-    /**
-     * Control {@link IConnectivityManager#setPolicyDataEnable(int, boolean)}.
-     */
-    private void setPolicyDataEnable(int networkType, boolean enabled) {
-        try {
-            mConnManager.setPolicyDataEnable(networkType, enabled);
-        } catch (RemoteException e) {
-            // ignored; service lives in system_server
-        }
-    }
-
     private long getTotalBytes(NetworkTemplate template, long start, long end) {
         try {
             return mNetworkStats.getNetworkTotalBytes(template, start, end);
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index b74716f..0b4d42e 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -916,10 +916,8 @@
     }
 
     private Future<Void> expectPolicyDataEnable(int type, boolean enabled) throws Exception {
-        final FutureAnswer future = new FutureAnswer();
-        mConnManager.setPolicyDataEnable(type, enabled);
-        expectLastCall().andAnswer(future);
-        return future;
+        // TODO: bring back this test
+        return null;
     }
 
     private void expectAdvisePersistThreshold() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index f9a03fc..7383478 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -1023,7 +1023,7 @@
         info.setDetailedState(DetailedState.CONNECTED, null, null);
         final LinkProperties prop = new LinkProperties();
         prop.setInterfaceName(iface);
-        return new NetworkState(info, prop, null, null);
+        return new NetworkState(info, prop, null, null, null, null);
     }
 
     private NetworkStats buildEmptyStats() {
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index abf1ead..20cd037 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -16,6 +16,7 @@
 
 package android.telephony;
 
+import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.content.Context;
@@ -1064,7 +1065,7 @@
      *         is never null but the length maybe 0.
      * @hide
      */
-    public int[] getActiveSubscriptionIdList() {
+    public @NonNull int[] getActiveSubscriptionIdList() {
         int[] subId = null;
 
         try {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 751e11b..83bf04f 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -16,6 +16,7 @@
 
 package android.telephony;
 
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
@@ -40,6 +41,7 @@
 
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -1879,6 +1881,23 @@
     }
 
     /**
+     * Return the set of subscriber IDs that should be considered as "merged
+     * together" for data usage purposes. This is commonly {@code null} to
+     * indicate no merging is required. Any returned subscribers are sorted in a
+     * deterministic order.
+     *
+     * @hide
+     */
+    public @Nullable String[] getMergedSubscriberIds() {
+        try {
+            return getITelephony().getMergedSubscriberIds();
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+        return null;
+    }
+
+    /**
      * Returns the MSISDN string.
      * for a GSM phone. Return null if it is unavailable.
      * <p>
@@ -3593,5 +3612,3 @@
         }
     }
 }
-
-
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index bf7f332..d19fa2c 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -772,6 +772,8 @@
      */
     String getLine1AlphaTagForDisplay(int subId);
 
+    String[] getMergedSubscriberIds();
+
     /**
      * Override the operator branding for the current ICCID.
      *