Merge "Add getters to NetworkStateSnapshot"
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index d42beae..6ccbab7 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -38,6 +38,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.function.Predicate;
@@ -1423,11 +1424,11 @@
      * @hide
      */
     public void migrateTun(int tunUid, @NonNull String tunIface,
-            @NonNull String[] underlyingIfaces) {
+            @NonNull List<String> underlyingIfaces) {
         // Combined usage by all apps using VPN.
         final Entry tunIfaceTotal = new Entry();
         // Usage by VPN, grouped by its {@code underlyingIfaces}.
-        final Entry[] perInterfaceTotal = new Entry[underlyingIfaces.length];
+        final Entry[] perInterfaceTotal = new Entry[underlyingIfaces.size()];
         // Usage by VPN, summed across all its {@code underlyingIfaces}.
         final Entry underlyingIfacesTotal = new Entry();
 
@@ -1468,7 +1469,7 @@
      *     {@code underlyingIfaces}
      */
     private void tunAdjustmentInit(int tunUid, @NonNull String tunIface,
-            @NonNull String[] underlyingIfaces, @NonNull Entry tunIfaceTotal,
+            @NonNull List<String> underlyingIfaces, @NonNull Entry tunIfaceTotal,
             @NonNull Entry[] perInterfaceTotal, @NonNull Entry underlyingIfacesTotal) {
         final Entry recycle = new Entry();
         for (int i = 0; i < size; i++) {
@@ -1488,8 +1489,8 @@
 
             if (recycle.uid == tunUid) {
                 // Add up traffic through tunUid's underlying interfaces.
-                for (int j = 0; j < underlyingIfaces.length; j++) {
-                    if (Objects.equals(underlyingIfaces[j], recycle.iface)) {
+                for (int j = 0; j < underlyingIfaces.size(); j++) {
+                    if (Objects.equals(underlyingIfaces.get(j), recycle.iface)) {
                         perInterfaceTotal[j].add(recycle);
                         underlyingIfacesTotal.add(recycle);
                         break;
@@ -1515,12 +1516,12 @@
      *     underlyingIfaces}
      */
     private Entry[] addTrafficToApplications(int tunUid, @NonNull String tunIface,
-            @NonNull String[] underlyingIfaces, @NonNull Entry tunIfaceTotal,
+            @NonNull List<String> underlyingIfaces, @NonNull Entry tunIfaceTotal,
             @NonNull Entry[] perInterfaceTotal, @NonNull Entry underlyingIfacesTotal) {
         // Traffic that should be moved off of each underlying interface for tunUid (see
         // deductTrafficFromVpnApp below).
-        final Entry[] moved = new Entry[underlyingIfaces.length];
-        for (int i = 0; i < underlyingIfaces.length; i++) {
+        final Entry[] moved = new Entry[underlyingIfaces.size()];
+        for (int i = 0; i < underlyingIfaces.size(); i++) {
             moved[i] = new Entry();
         }
 
@@ -1582,8 +1583,8 @@
             }
             // In a second pass, distribute these values across interfaces in the proportion that
             // each interface represents of the total traffic of the underlying interfaces.
-            for (int j = 0; j < underlyingIfaces.length; j++) {
-                tmpEntry.iface = underlyingIfaces[j];
+            for (int j = 0; j < underlyingIfaces.size(); j++) {
+                tmpEntry.iface = underlyingIfaces.get(j);
                 tmpEntry.rxBytes = 0;
                 // Reset 'set' to correct value since it gets updated when adding debug info below.
                 tmpEntry.set = set[i];
@@ -1638,14 +1639,14 @@
 
     private void deductTrafficFromVpnApp(
             int tunUid,
-            @NonNull String[] underlyingIfaces,
+            @NonNull List<String> underlyingIfaces,
             @NonNull Entry[] moved) {
-        for (int i = 0; i < underlyingIfaces.length; i++) {
+        for (int i = 0; i < underlyingIfaces.size(); i++) {
             moved[i].uid = tunUid;
             // Add debug info
             moved[i].set = SET_DBG_VPN_OUT;
             moved[i].tag = TAG_NONE;
-            moved[i].iface = underlyingIfaces[i];
+            moved[i].iface = underlyingIfaces.get(i);
             moved[i].metered = METERED_ALL;
             moved[i].roaming = ROAMING_ALL;
             moved[i].defaultNetwork = DEFAULT_NETWORK_ALL;
@@ -1658,7 +1659,7 @@
             // METERED_NO, which should be the case as it comes directly from the /proc file.
             // We only blend in the roaming data after applying these adjustments, by checking the
             // NetworkIdentity of the underlying iface.
-            final int idxVpnBackground = findIndex(underlyingIfaces[i], tunUid, SET_DEFAULT,
+            final int idxVpnBackground = findIndex(underlyingIfaces.get(i), tunUid, SET_DEFAULT,
                             TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO);
             if (idxVpnBackground != -1) {
                 // Note - tunSubtract also updates moved[i]; whatever traffic that's left is removed
@@ -1666,7 +1667,7 @@
                 tunSubtract(idxVpnBackground, this, moved[i]);
             }
 
-            final int idxVpnForeground = findIndex(underlyingIfaces[i], tunUid, SET_FOREGROUND,
+            final int idxVpnForeground = findIndex(underlyingIfaces.get(i), tunUid, SET_FOREGROUND,
                             TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO);
             if (idxVpnForeground != -1) {
                 tunSubtract(idxVpnForeground, this, moved[i]);
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index c83dd99..d3c8957 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -82,6 +82,24 @@
     public static final int MATCH_WIFI_WILDCARD = 7;
     public static final int MATCH_BLUETOOTH = 8;
     public static final int MATCH_PROXY = 9;
+    public static final int MATCH_CARRIER = 10;
+
+    /**
+     * Value of the match rule of the subscriberId to match networks with specific subscriberId.
+     */
+    public static final int SUBSCRIBER_ID_MATCH_RULE_EXACT = 0;
+    /**
+     * Value of the match rule of the subscriberId to match networks with any subscriberId which
+     * includes null and non-null.
+     */
+    public static final int SUBSCRIBER_ID_MATCH_RULE_ALL = 1;
+
+    /**
+     * Wi-Fi Network ID is never supposed to be null (if it is, it is a bug that
+     * should be fixed), so it's not possible to want to match null vs
+     * non-null. Therefore it's fine to use null as a sentinel for Network ID.
+     */
+    public static final String WIFI_NETWORKID_ALL = null;
 
     /**
      * Include all network types when filtering. This is meant to merge in with the
@@ -125,6 +143,7 @@
             case MATCH_WIFI_WILDCARD:
             case MATCH_BLUETOOTH:
             case MATCH_PROXY:
+            case MATCH_CARRIER:
                 return true;
 
             default:
@@ -168,10 +187,12 @@
             @NetworkType int ratType) {
         if (TextUtils.isEmpty(subscriberId)) {
             return new NetworkTemplate(MATCH_MOBILE_WILDCARD, null, null, null,
-                    METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, ratType, OEM_MANAGED_ALL);
+                    METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, ratType, OEM_MANAGED_ALL,
+                    SUBSCRIBER_ID_MATCH_RULE_EXACT);
         }
         return new NetworkTemplate(MATCH_MOBILE, subscriberId, new String[]{subscriberId}, null,
-                METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, ratType, OEM_MANAGED_ALL);
+                METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, ratType, OEM_MANAGED_ALL,
+                SUBSCRIBER_ID_MATCH_RULE_EXACT);
     }
 
     /**
@@ -189,6 +210,8 @@
      */
     @UnsupportedAppUsage
     public static NetworkTemplate buildTemplateWifiWildcard() {
+        // TODO: Consider replace this with MATCH_WIFI with NETWORK_ID_ALL
+        // and SUBSCRIBER_ID_MATCH_RULE_ALL.
         return new NetworkTemplate(MATCH_WIFI_WILDCARD, null, null);
     }
 
@@ -202,8 +225,27 @@
      * Template to match {@link ConnectivityManager#TYPE_WIFI} networks with the
      * given SSID.
      */
-    public static NetworkTemplate buildTemplateWifi(String networkId) {
-        return new NetworkTemplate(MATCH_WIFI, null, networkId);
+    public static NetworkTemplate buildTemplateWifi(@NonNull String networkId) {
+        Objects.requireNonNull(networkId);
+        return new NetworkTemplate(MATCH_WIFI, null /* subscriberId */,
+                new String[] { null } /* matchSubscriberIds */,
+                networkId, METERED_ALL, ROAMING_ALL,
+                DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL,
+                SUBSCRIBER_ID_MATCH_RULE_ALL);
+    }
+
+    /**
+     * Template to match all {@link ConnectivityManager#TYPE_WIFI} networks with the given SSID,
+     * and IMSI.
+     *
+     * Call with {@link #WIFI_NETWORKID_ALL} for {@code networkId} to get result regardless of SSID.
+     */
+    public static NetworkTemplate buildTemplateWifi(@Nullable String networkId,
+            @Nullable String subscriberId) {
+        return new NetworkTemplate(MATCH_WIFI, subscriberId, new String[] { subscriberId },
+                networkId, METERED_ALL, ROAMING_ALL,
+                DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL,
+                SUBSCRIBER_ID_MATCH_RULE_EXACT);
     }
 
     /**
@@ -231,6 +273,14 @@
         return new NetworkTemplate(MATCH_PROXY, null, null);
     }
 
+    /**
+     * Template to match all carrier networks with the given IMSI.
+     */
+    public static NetworkTemplate buildTemplateCarrier(@NonNull String subscriberId) {
+        Objects.requireNonNull(subscriberId);
+        return new NetworkTemplate(MATCH_CARRIER, subscriberId, null);
+    }
+
     private final int mMatchRule;
     private final String mSubscriberId;
 
@@ -251,10 +301,26 @@
     private final int mRoaming;
     private final int mDefaultNetwork;
     private final int mSubType;
+    private final int mSubscriberIdMatchRule;
 
     // Bitfield containing OEM network properties{@code NetworkIdentity#OEM_*}.
     private final int mOemManaged;
 
+    private void checkValidSubscriberIdMatchRule() {
+        switch (mMatchRule) {
+            case MATCH_MOBILE:
+            case MATCH_CARRIER:
+                // MOBILE and CARRIER templates must always specify a subscriber ID.
+                if (mSubscriberIdMatchRule == SUBSCRIBER_ID_MATCH_RULE_ALL) {
+                    throw new IllegalArgumentException("Invalid SubscriberIdMatchRule"
+                            + "on match rule: " + getMatchRuleName(mMatchRule));
+                }
+                return;
+            default:
+                return;
+        }
+    }
+
     @UnsupportedAppUsage
     public NetworkTemplate(int matchRule, String subscriberId, String networkId) {
         this(matchRule, subscriberId, new String[] { subscriberId }, networkId);
@@ -263,14 +329,25 @@
     public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
             String networkId) {
         this(matchRule, subscriberId, matchSubscriberIds, networkId, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL);
+                DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL,
+                SUBSCRIBER_ID_MATCH_RULE_EXACT);
+    }
+
+    // TODO: Remove it after updating all of the caller.
+    public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
+            String networkId, int metered, int roaming, int defaultNetwork, int subType,
+            int oemManaged) {
+        this(matchRule, subscriberId, matchSubscriberIds, networkId, metered, roaming,
+                defaultNetwork, subType, oemManaged, SUBSCRIBER_ID_MATCH_RULE_EXACT);
     }
 
     public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
             String networkId, int metered, int roaming, int defaultNetwork, int subType,
-            int oemManaged) {
+            int oemManaged, int subscriberIdMatchRule) {
         mMatchRule = matchRule;
         mSubscriberId = subscriberId;
+        // TODO: Check whether mMatchSubscriberIds = null or mMatchSubscriberIds = {null} when
+        // mSubscriberId is null
         mMatchSubscriberIds = matchSubscriberIds;
         mNetworkId = networkId;
         mMetered = metered;
@@ -278,7 +355,8 @@
         mDefaultNetwork = defaultNetwork;
         mSubType = subType;
         mOemManaged = oemManaged;
-
+        mSubscriberIdMatchRule = subscriberIdMatchRule;
+        checkValidSubscriberIdMatchRule();
         if (!isKnownMatchRule(matchRule)) {
             Log.e(TAG, "Unknown network template rule " + matchRule
                     + " will not match any identity.");
@@ -295,6 +373,7 @@
         mDefaultNetwork = in.readInt();
         mSubType = in.readInt();
         mOemManaged = in.readInt();
+        mSubscriberIdMatchRule = in.readInt();
     }
 
     @Override
@@ -308,6 +387,7 @@
         dest.writeInt(mDefaultNetwork);
         dest.writeInt(mSubType);
         dest.writeInt(mOemManaged);
+        dest.writeInt(mSubscriberIdMatchRule);
     }
 
     @Override
@@ -346,13 +426,15 @@
         if (mOemManaged != OEM_MANAGED_ALL) {
             builder.append(", oemManaged=").append(mOemManaged);
         }
+        builder.append(", subscriberIdMatchRule=")
+                .append(subscriberIdMatchRuleToString(mSubscriberIdMatchRule));
         return builder.toString();
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mMatchRule, mSubscriberId, mNetworkId, mMetered, mRoaming,
-                mDefaultNetwork, mSubType, mOemManaged);
+                mDefaultNetwork, mSubType, mOemManaged, mSubscriberIdMatchRule);
     }
 
     @Override
@@ -366,11 +448,23 @@
                     && mRoaming == other.mRoaming
                     && mDefaultNetwork == other.mDefaultNetwork
                     && mSubType == other.mSubType
-                    && mOemManaged == other.mOemManaged;
+                    && mOemManaged == other.mOemManaged
+                    && mSubscriberIdMatchRule == other.mSubscriberIdMatchRule;
         }
         return false;
     }
 
+    private String subscriberIdMatchRuleToString(int rule) {
+        switch (rule) {
+            case SUBSCRIBER_ID_MATCH_RULE_EXACT:
+                return "EXACT_MATCH";
+            case SUBSCRIBER_ID_MATCH_RULE_ALL:
+                return "ALL";
+            default:
+                return "Unknown rule " + rule;
+        }
+    }
+
     public boolean isMatchRuleMobile() {
         switch (mMatchRule) {
             case MATCH_MOBILE:
@@ -386,6 +480,14 @@
             case MATCH_MOBILE_WILDCARD:
             case MATCH_WIFI_WILDCARD:
                 return false;
+            case MATCH_CARRIER:
+                return mSubscriberId != null;
+            case MATCH_WIFI:
+                if (Objects.equals(mNetworkId, WIFI_NETWORKID_ALL)
+                        && mSubscriberIdMatchRule == SUBSCRIBER_ID_MATCH_RULE_ALL) {
+                    return false;
+                }
+                return true;
             default:
                 return true;
         }
@@ -405,6 +507,10 @@
         return mNetworkId;
     }
 
+    public int getSubscriberIdMatchRule() {
+        return mSubscriberIdMatchRule;
+    }
+
     /**
      * Test if given {@link NetworkIdentity} matches this template.
      */
@@ -429,6 +535,8 @@
                 return matchesBluetooth(ident);
             case MATCH_PROXY:
                 return matchesProxy(ident);
+            case MATCH_CARRIER:
+                return matchesCarrier(ident);
             default:
                 // We have no idea what kind of network template we are, so we
                 // just claim not to match anything.
@@ -466,8 +574,23 @@
                 || getCollapsedRatType(mSubType) == getCollapsedRatType(ident.mSubType);
     }
 
-    public boolean matchesSubscriberId(String subscriberId) {
-        return ArrayUtils.contains(mMatchSubscriberIds, subscriberId);
+    /**
+     * Check if this template matches {@code subscriberId}. Returns true if this
+     * template was created with {@code SUBSCRIBER_ID_MATCH_RULE_ALL}, or with a
+     * {@code mMatchSubscriberIds} array that contains {@code subscriberId}.
+     */
+    public boolean matchesSubscriberId(@Nullable String subscriberId) {
+        return mSubscriberIdMatchRule == SUBSCRIBER_ID_MATCH_RULE_ALL
+                || ArrayUtils.contains(mMatchSubscriberIds, subscriberId);
+    }
+
+    /**
+     * Check if network with matching SSID. Returns true when the SSID matches, or when
+     * {@code mNetworkId} is {@code WIFI_NETWORKID_ALL}.
+     */
+    private boolean matchesWifiNetworkId(@Nullable String networkId) {
+        return Objects.equals(mNetworkId, WIFI_NETWORKID_ALL)
+                || Objects.equals(sanitizeSsid(mNetworkId), sanitizeSsid(networkId));
     }
 
     /**
@@ -566,8 +689,8 @@
     private boolean matchesWifi(NetworkIdentity ident) {
         switch (ident.mType) {
             case TYPE_WIFI:
-                return Objects.equals(
-                        sanitizeSsid(mNetworkId), sanitizeSsid(ident.mNetworkId));
+                return matchesSubscriberId(ident.mSubscriberId)
+                        && matchesWifiNetworkId(ident.mNetworkId);
             default:
                 return false;
         }
@@ -583,6 +706,15 @@
         return false;
     }
 
+    /**
+     * Check if matches carrier network. The carrier networks means it includes the subscriberId.
+     */
+    private boolean matchesCarrier(NetworkIdentity ident) {
+        return ident.mSubscriberId != null
+                && !ArrayUtils.isEmpty(mMatchSubscriberIds)
+                && ArrayUtils.contains(mMatchSubscriberIds, ident.mSubscriberId);
+    }
+
     private boolean matchesMobileWildcard(NetworkIdentity ident) {
         if (ident.mType == TYPE_WIMAX) {
             return true;
@@ -635,6 +767,8 @@
                 return "BLUETOOTH";
             case MATCH_PROXY:
                 return "PROXY";
+            case MATCH_CARRIER:
+                return "CARRIER";
             default:
                 return "UNKNOWN(" + matchRule + ")";
         }
diff --git a/core/java/android/net/UnderlyingNetworkInfo.java b/core/java/android/net/UnderlyingNetworkInfo.java
index 7bf9231..459fdac 100644
--- a/core/java/android/net/UnderlyingNetworkInfo.java
+++ b/core/java/android/net/UnderlyingNetworkInfo.java
@@ -37,36 +37,56 @@
 @SystemApi(client = MODULE_LIBRARIES)
 public final class UnderlyingNetworkInfo implements Parcelable {
     /** The owner of this network. */
-    public final int ownerUid;
+    private final int mOwnerUid;
+
     /** The interface name of this network. */
     @NonNull
-    public final String iface;
+    private final String mIface;
+
     /** The names of the interfaces underlying this network. */
     @NonNull
-    public final List<String> underlyingIfaces;
+    private final List<String> mUnderlyingIfaces;
 
     public UnderlyingNetworkInfo(int ownerUid, @NonNull String iface,
             @NonNull List<String> underlyingIfaces) {
         Objects.requireNonNull(iface);
         Objects.requireNonNull(underlyingIfaces);
-        this.ownerUid = ownerUid;
-        this.iface = iface;
-        this.underlyingIfaces = Collections.unmodifiableList(new ArrayList<>(underlyingIfaces));
+        mOwnerUid = ownerUid;
+        mIface = iface;
+        mUnderlyingIfaces = Collections.unmodifiableList(new ArrayList<>(underlyingIfaces));
     }
 
     private UnderlyingNetworkInfo(@NonNull Parcel in) {
-        this.ownerUid = in.readInt();
-        this.iface = in.readString();
-        this.underlyingIfaces = new ArrayList<>();
-        in.readList(this.underlyingIfaces, null /*classLoader*/);
+        mOwnerUid = in.readInt();
+        mIface = in.readString();
+        List<String> underlyingIfaces = new ArrayList<>();
+        in.readList(underlyingIfaces, null /*classLoader*/);
+        mUnderlyingIfaces = Collections.unmodifiableList(underlyingIfaces);
+    }
+
+    /** Get the owner of this network. */
+    public int getOwnerUid() {
+        return mOwnerUid;
+    }
+
+    /** Get the interface name of this network. */
+    @NonNull
+    public String getIface() {
+        return mIface;
+    }
+
+    /** Get the names of the interfaces underlying this network. */
+    @NonNull
+    public List<String> getUnderlyingIfaces() {
+        return mUnderlyingIfaces;
     }
 
     @Override
     public String toString() {
         return "UnderlyingNetworkInfo{"
-                + "ownerUid=" + ownerUid
-                + ", iface='" + iface + '\''
-                + ", underlyingIfaces='" + underlyingIfaces.toString() + '\''
+                + "ownerUid=" + mOwnerUid
+                + ", iface='" + mIface + '\''
+                + ", underlyingIfaces='" + mUnderlyingIfaces.toString() + '\''
                 + '}';
     }
 
@@ -77,9 +97,9 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(ownerUid);
-        dest.writeString(iface);
-        dest.writeList(underlyingIfaces);
+        dest.writeInt(mOwnerUid);
+        dest.writeString(mIface);
+        dest.writeList(mUnderlyingIfaces);
     }
 
     @NonNull
@@ -103,13 +123,13 @@
         if (this == o) return true;
         if (!(o instanceof UnderlyingNetworkInfo)) return false;
         final UnderlyingNetworkInfo that = (UnderlyingNetworkInfo) o;
-        return ownerUid == that.ownerUid
-                && Objects.equals(iface, that.iface)
-                && Objects.equals(underlyingIfaces, that.underlyingIfaces);
+        return mOwnerUid == that.getOwnerUid()
+                && Objects.equals(mIface, that.getIface())
+                && Objects.equals(mUnderlyingIfaces, that.getUnderlyingIfaces());
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(ownerUid, iface, underlyingIfaces);
+        return Objects.hash(mOwnerUid, mIface, mUnderlyingIfaces);
     }
 }
diff --git a/services/core/java/com/android/server/net/NetworkStatsFactory.java b/services/core/java/com/android/server/net/NetworkStatsFactory.java
index d042b88..e7c0a50 100644
--- a/services/core/java/com/android/server/net/NetworkStatsFactory.java
+++ b/services/core/java/com/android/server/net/NetworkStatsFactory.java
@@ -382,8 +382,8 @@
 
         // Migrate data usage over a VPN to the TUN network.
         for (UnderlyingNetworkInfo info : vpnArray) {
-            delta.migrateTun(info.ownerUid, info.iface,
-                    info.underlyingIfaces.toArray(new String[0]));
+            delta.migrateTun(info.getOwnerUid(), info.getIface(),
+                    info.getUnderlyingIfaces());
             // Filter out debug entries as that may lead to over counting.
             delta.filterDebugEntries();
         }