/* * Copyright (C) 2011 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 android.net; import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; import static android.net.ConnectivityManager.TYPE_BLUETOOTH; import static android.net.ConnectivityManager.TYPE_ETHERNET; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_PROXY; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.ConnectivityManager.TYPE_WIFI_P2P; import static android.net.ConnectivityManager.TYPE_WIMAX; import static android.net.NetworkIdentity.OEM_NONE; import static android.net.NetworkIdentity.OEM_PAID; import static android.net.NetworkIdentity.OEM_PRIVATE; import static android.net.NetworkStats.DEFAULT_NETWORK_ALL; import static android.net.NetworkStats.DEFAULT_NETWORK_NO; import static android.net.NetworkStats.DEFAULT_NETWORK_YES; import static android.net.NetworkStats.METERED_ALL; import static android.net.NetworkStats.METERED_NO; import static android.net.NetworkStats.METERED_YES; import static android.net.NetworkStats.ROAMING_ALL; import static android.net.NetworkStats.ROAMING_NO; import static android.net.NetworkStats.ROAMING_YES; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.app.usage.NetworkStatsManager; import android.compat.annotation.UnsupportedAppUsage; import android.net.wifi.WifiInfo; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.NetworkIdentityUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; /** * Predicate used to match {@link NetworkIdentity}, usually when collecting * statistics. (It should probably have been named {@code NetworkPredicate}.) * * @hide */ @SystemApi(client = MODULE_LIBRARIES) public final class NetworkTemplate implements Parcelable { private static final String TAG = NetworkTemplate.class.getSimpleName(); /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "MATCH_" }, value = { MATCH_MOBILE, MATCH_WIFI, MATCH_ETHERNET, MATCH_BLUETOOTH, MATCH_PROXY, MATCH_CARRIER, }) public @interface TemplateMatchRule{} /** Match rule to match cellular networks with given Subscriber Ids. */ public static final int MATCH_MOBILE = 1; /** Match rule to match wifi networks. */ public static final int MATCH_WIFI = 4; /** Match rule to match ethernet networks. */ public static final int MATCH_ETHERNET = 5; /** Match rule to match bluetooth networks. */ public static final int MATCH_BLUETOOTH = 8; /** * Match rule to match networks with {@link ConnectivityManager#TYPE_PROXY} as the legacy * network type. */ public static final int MATCH_PROXY = 9; /** * Match rule to match all networks with subscriberId inside the template. Some carriers * may offer non-cellular networks like WiFi, which will be matched by this rule. */ public static final int MATCH_CARRIER = 10; /** * Match rule to match networks with {@link ConnectivityManager#TYPE_TEST} as the legacy * network type. * * @hide */ @VisibleForTesting public static final int MATCH_TEST = 11; // TODO: Remove this and replace all callers with WIFI_NETWORK_KEY_ALL. /** @hide */ public static final String WIFI_NETWORKID_ALL = null; /** * Wi-Fi Network Key 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 Wifi Network Key. * * @hide */ public static final String WIFI_NETWORK_KEY_ALL = WIFI_NETWORKID_ALL; /** * Include all network types when filtering. This is meant to merge in with the * {@code TelephonyManager.NETWORK_TYPE_*} constants, and thus needs to stay in sync. */ public static final int NETWORK_TYPE_ALL = -1; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "OEM_MANAGED_" }, value = { OEM_MANAGED_ALL, OEM_MANAGED_NO, OEM_MANAGED_YES, OEM_MANAGED_PAID, OEM_MANAGED_PRIVATE }) public @interface OemManaged{} /** * Value to match both OEM managed and unmanaged networks (all networks). */ public static final int OEM_MANAGED_ALL = -1; /** * Value to match networks which are not OEM managed. */ public static final int OEM_MANAGED_NO = OEM_NONE; /** * Value to match any OEM managed network. */ public static final int OEM_MANAGED_YES = -2; /** * Network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PAID}. */ public static final int OEM_MANAGED_PAID = OEM_PAID; /** * Network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PRIVATE}. */ public static final int OEM_MANAGED_PRIVATE = OEM_PRIVATE; private static boolean isKnownMatchRule(final int rule) { switch (rule) { case MATCH_MOBILE: case MATCH_WIFI: case MATCH_ETHERNET: case MATCH_BLUETOOTH: case MATCH_PROXY: case MATCH_CARRIER: case MATCH_TEST: return true; default: return false; } } private static Set setOf(@Nullable final String item) { if (item == null) { // Set.of will throw if item is null final Set set = new HashSet<>(); set.add(null); return Collections.unmodifiableSet(set); } else { return Set.of(item); } } private static void throwAtLeastU() { if (SdkLevel.isAtLeastU()) { throw new UnsupportedOperationException("Method not supported on Android U or above"); } } /** * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks with * the given IMSI. * * @deprecated Use {@link Builder} to build a template. * @hide */ @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU, publicAlternatives = "Use {@code Builder} instead.") public static NetworkTemplate buildTemplateMobileAll(@NonNull String subscriberId) { return new NetworkTemplate.Builder(MATCH_MOBILE).setMeteredness(METERED_YES) .setSubscriberIds(setOf(subscriberId)).build(); } /** * Template to match metered {@link ConnectivityManager#TYPE_MOBILE} networks, * regardless of IMSI. * * @deprecated Use {@link Builder} to build a template. * @hide */ @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public static NetworkTemplate buildTemplateMobileWildcard() { return new NetworkTemplate.Builder(MATCH_MOBILE).setMeteredness(METERED_YES).build(); } /** * Template to match all metered {@link ConnectivityManager#TYPE_WIFI} networks, * regardless of key of the wifi network. * * @deprecated Use {@link Builder} to build a template. * @hide */ @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU, publicAlternatives = "Use {@code Builder} instead.") public static NetworkTemplate buildTemplateWifiWildcard() { return new NetworkTemplate.Builder(MATCH_WIFI).build(); } /** * @deprecated Use {@link Builder} to build a template. * @hide */ @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU, publicAlternatives = "Use {@code Builder} instead.") public static NetworkTemplate buildTemplateWifi() { return buildTemplateWifiWildcard(); } /** * Template to combine all {@link ConnectivityManager#TYPE_ETHERNET} style * networks together. * * @deprecated Use {@link Builder} to build a template. * @hide */ @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU, publicAlternatives = "Use {@code Builder} instead.") public static NetworkTemplate buildTemplateEthernet() { return new NetworkTemplate.Builder(MATCH_ETHERNET).build(); } /** * Template to combine all {@link ConnectivityManager#TYPE_BLUETOOTH} style * networks together. * * @hide */ // TODO(b/270089918): Remove this method. This can only be done after there are no more callers, // including in OEM code which can access this by linking against the framework. public static NetworkTemplate buildTemplateBluetooth() { // TODO : this is part of hidden-o txt, does that mean it should be annotated with // @UnsupportedAppUsage(maxTargetSdk = O) ? If yes, can't throwAtLeastU() lest apps // targeting O- crash on those devices. return new NetworkTemplate.Builder(MATCH_BLUETOOTH).build(); } /** * Template to combine all {@link ConnectivityManager#TYPE_PROXY} style * networks together. * * @hide */ // TODO(b/270089918): Remove this method. This can only be done after there are no more callers, // including in OEM code which can access this by linking against the framework. public static NetworkTemplate buildTemplateProxy() { // TODO : this is part of hidden-o txt, does that mean it should be annotated with // @UnsupportedAppUsage(maxTargetSdk = O) ? If yes, can't throwAtLeastU() lest apps // targeting O- crash on those devices. return new NetworkTemplate(MATCH_PROXY, null, null); } /** * Template to match all metered carrier networks with the given IMSI. * * @hide */ // TODO(b/273963543): Remove this method. This can only be done after there are no more callers, // including in OEM code which can access this by linking against the framework. public static NetworkTemplate buildTemplateCarrierMetered(@NonNull String subscriberId) { throwAtLeastU(); return new NetworkTemplate.Builder(MATCH_CARRIER) // Set.of will throw if subscriberId is null, which is the historical // behavior and should be preserved. .setSubscriberIds(Set.of(subscriberId)) .setMeteredness(METERED_YES) .build(); } /** * Template to match cellular networks with the given IMSI, {@code ratType} and * {@code metered}. Use {@link #NETWORK_TYPE_ALL} to include all network types when * filtering. See {@code TelephonyManager.NETWORK_TYPE_*}. * * @hide */ // TODO(b/273963543): Remove this method. This can only be done after there are no more callers, // including in OEM code which can access this by linking against the framework. public static NetworkTemplate buildTemplateMobileWithRatType(@Nullable String subscriberId, int ratType, int metered) { throwAtLeastU(); return new NetworkTemplate.Builder(MATCH_MOBILE) .setSubscriberIds(TextUtils.isEmpty(subscriberId) ? Collections.emptySet() : Set.of(subscriberId)) .setMeteredness(metered) .setRatType(ratType) .build(); } /** * Template to match {@link ConnectivityManager#TYPE_WIFI} networks with the * given key of the wifi network. * * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getNetworkKey()} * to know details about the key. * @hide */ // TODO(b/273963543): Remove this method. This can only be done after there are no more callers, // including in OEM code which can access this by linking against the framework. public static NetworkTemplate buildTemplateWifi(@NonNull String wifiNetworkKey) { // TODO : this is part of hidden-o txt, does that mean it should be annotated with // @UnsupportedAppUsage(maxTargetSdk = O) ? If yes, can't throwAtLeastU() lest apps // targeting O- crash on those devices. return new NetworkTemplate.Builder(MATCH_WIFI) // Set.of will throw if wifiNetworkKey is null, which is the historical // behavior and should be preserved. .setWifiNetworkKeys(Set.of(wifiNetworkKey)) .build(); } /** * Template to match all {@link ConnectivityManager#TYPE_WIFI} networks with the given * key of the wifi network and IMSI. * * Call with {@link #WIFI_NETWORK_KEY_ALL} for {@code wifiNetworkKey} to get result regardless * of key of the wifi network. * * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getNetworkKey()} * to know details about the key. * @param subscriberId the IMSI associated to this wifi network. * * @hide */ // TODO(b/273963543): Remove this method. This can only be done after there are no more callers, // including in OEM code which can access this by linking against the framework. public static NetworkTemplate buildTemplateWifi(@Nullable String wifiNetworkKey, @Nullable String subscriberId) { throwAtLeastU(); return new NetworkTemplate.Builder(MATCH_WIFI) .setSubscriberIds(setOf(subscriberId)) .setWifiNetworkKeys(wifiNetworkKey == null ? Collections.emptySet() : Set.of(wifiNetworkKey)) .build(); } private final int mMatchRule; /** * 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. *

* Since the merge set is dynamic, it should not be persisted or * used for determining equality. */ @NonNull private final String[] mMatchSubscriberIds; @NonNull private final String[] mMatchWifiNetworkKeys; // Matches for the NetworkStats constants METERED_*, ROAMING_* and DEFAULT_NETWORK_*. private final int mMetered; private final int mRoaming; private final int mDefaultNetwork; private final int mRatType; // Bitfield containing OEM network properties{@code NetworkIdentity#OEM_*}. private final int mOemManaged; private static void checkValidMatchSubscriberIds(int matchRule, String[] matchSubscriberIds) { switch (matchRule) { // CARRIER templates must always specify a valid subscriber ID. // MOBILE templates can have empty matchSubscriberIds but it must not contain a null // subscriber ID. case MATCH_CARRIER: if (matchSubscriberIds.length == 0) { throw new IllegalArgumentException("matchSubscriberIds may not contain" + " null for rule " + getMatchRuleName(matchRule)); } if (CollectionUtils.contains(matchSubscriberIds, null)) { throw new IllegalArgumentException("matchSubscriberIds may not contain" + " null for rule " + getMatchRuleName(matchRule)); } break; case MATCH_MOBILE: // Prevent from crash for b/273963543, where the OEMs still call into unsupported // buildTemplateMobileAll with null subscriberId and get crashed. final int firstSdk = Build.VERSION.DEVICE_INITIAL_SDK_INT; if (firstSdk > Build.VERSION_CODES.TIRAMISU && CollectionUtils.contains(matchSubscriberIds, null)) { throw new IllegalArgumentException("checkValidMatchSubscriberIds list of ids" + " may not contain null for rule " + getMatchRuleName(matchRule)); } return; default: return; } } /** * @deprecated Use {@link Builder} to build a template. * @hide */ @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU, publicAlternatives = "Use {@code Builder} instead.") public NetworkTemplate(int matchRule, String subscriberId, String wifiNetworkKey) { // Older versions used to only match MATCH_MOBILE and MATCH_MOBILE_WILDCARD templates // to metered networks. It is now possible to match mobile with any meteredness, but // in order to preserve backward compatibility of @UnsupportedAppUsage methods, this // constructor passes METERED_YES for these types. // For backwards compatibility, still accept old wildcard match rules (6 and 7 for // MATCH_{MOBILE,WIFI}_WILDCARD) but convert into functionally equivalent non-wildcard // ones. this(getBackwardsCompatibleMatchRule(matchRule), subscriberId != null ? new String[] { subscriberId } : new String[0], wifiNetworkKey != null ? new String[] { wifiNetworkKey } : new String[0], getMeterednessForBackwardsCompatibility(matchRule), ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL); if (matchRule == 6 || matchRule == 7) { Log.e(TAG, "Use MATCH_MOBILE with empty subscriberIds or MATCH_WIFI with empty " + "wifiNetworkKeys instead of template with matchRule=" + matchRule); } } private static int getBackwardsCompatibleMatchRule(int matchRule) { // Backwards compatibility old constants // Old MATCH_MOBILE_WILDCARD if (6 == matchRule) return MATCH_MOBILE; // Old MATCH_WIFI_WILDCARD if (7 == matchRule) return MATCH_WIFI; return matchRule; } private static int getMeterednessForBackwardsCompatibility(int matchRule) { if (getBackwardsCompatibleMatchRule(matchRule) == MATCH_MOBILE || matchRule == MATCH_CARRIER) { return METERED_YES; } return METERED_ALL; } /** @hide */ // TODO(b/270089918): Remove this method after no callers. public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds, String wifiNetworkKey) { // Older versions used to only match MATCH_MOBILE and MATCH_MOBILE_WILDCARD templates // to metered networks. It is now possible to match mobile with any meteredness, but // in order to preserve backward compatibility of @UnsupportedAppUsage methods, this // constructor passes METERED_YES for these types. this(getBackwardsCompatibleMatchRule(matchRule), matchSubscriberIds, wifiNetworkKey != null ? new String[] { wifiNetworkKey } : new String[0], getMeterednessForBackwardsCompatibility(matchRule), ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL); // TODO : this is part of hidden-o txt, does that mean it should be annotated with // @UnsupportedAppUsage(maxTargetSdk = O) ? If yes, can't throwAtLeastU() lest apps // targeting O- crash on those devices. } /** @hide */ // TODO(b/269974916): Remove this method after Android U is released. // This is only used by CTS of Android T. public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds, String[] matchWifiNetworkKeys, int metered, int roaming, int defaultNetwork, int ratType, int oemManaged, int subscriberIdMatchRule) { // subscriberId and subscriberIdMatchRule aren't used since they are replaced by // matchSubscriberIds, which could be null to indicate the intention of matching any // subscriberIds. this(getBackwardsCompatibleMatchRule(matchRule), matchSubscriberIds == null ? new String[]{} : matchSubscriberIds, matchWifiNetworkKeys, metered, roaming, defaultNetwork, ratType, oemManaged); throwAtLeastU(); } /** @hide */ public NetworkTemplate(int matchRule, String[] matchSubscriberIds, String[] matchWifiNetworkKeys, int metered, int roaming, int defaultNetwork, int ratType, int oemManaged) { Objects.requireNonNull(matchWifiNetworkKeys); Objects.requireNonNull(matchSubscriberIds); mMatchRule = matchRule; mMatchSubscriberIds = matchSubscriberIds; mMatchWifiNetworkKeys = matchWifiNetworkKeys; mMetered = metered; mRoaming = roaming; mDefaultNetwork = defaultNetwork; mRatType = ratType; mOemManaged = oemManaged; checkValidMatchSubscriberIds(matchRule, matchSubscriberIds); if (!isKnownMatchRule(matchRule)) { throw new IllegalArgumentException("Unknown network template rule " + matchRule + " will not match any identity."); } } private NetworkTemplate(Parcel in) { mMatchRule = in.readInt(); mMatchSubscriberIds = in.createStringArray(); mMatchWifiNetworkKeys = in.createStringArray(); mMetered = in.readInt(); mRoaming = in.readInt(); mDefaultNetwork = in.readInt(); mRatType = in.readInt(); mOemManaged = in.readInt(); } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mMatchRule); dest.writeStringArray(mMatchSubscriberIds); dest.writeStringArray(mMatchWifiNetworkKeys); dest.writeInt(mMetered); dest.writeInt(mRoaming); dest.writeInt(mDefaultNetwork); dest.writeInt(mRatType); dest.writeInt(mOemManaged); } @Override public int describeContents() { return 0; } @Override public String toString() { final StringBuilder builder = new StringBuilder("NetworkTemplate: "); builder.append("matchRule=").append(getMatchRuleName(mMatchRule)); if (mMatchSubscriberIds != null) { builder.append(", matchSubscriberIds=").append( Arrays.toString(NetworkIdentityUtils.scrubSubscriberIds(mMatchSubscriberIds))); } builder.append(", matchWifiNetworkKeys=").append(Arrays.toString(mMatchWifiNetworkKeys)); if (mMetered != METERED_ALL) { builder.append(", metered=").append(NetworkStats.meteredToString(mMetered)); } if (mRoaming != ROAMING_ALL) { builder.append(", roaming=").append(NetworkStats.roamingToString(mRoaming)); } if (mDefaultNetwork != DEFAULT_NETWORK_ALL) { builder.append(", defaultNetwork=").append(NetworkStats.defaultNetworkToString( mDefaultNetwork)); } if (mRatType != NETWORK_TYPE_ALL) { builder.append(", ratType=").append(mRatType); } if (mOemManaged != OEM_MANAGED_ALL) { builder.append(", oemManaged=").append(getOemManagedNames(mOemManaged)); } return builder.toString(); } @Override public int hashCode() { return Objects.hash(mMatchRule, Arrays.hashCode(mMatchSubscriberIds), Arrays.hashCode(mMatchWifiNetworkKeys), mMetered, mRoaming, mDefaultNetwork, mRatType, mOemManaged); } @Override public boolean equals(@Nullable Object obj) { if (obj instanceof NetworkTemplate) { final NetworkTemplate other = (NetworkTemplate) obj; return mMatchRule == other.mMatchRule && mMetered == other.mMetered && mRoaming == other.mRoaming && mDefaultNetwork == other.mDefaultNetwork && mRatType == other.mRatType && mOemManaged == other.mOemManaged && Arrays.equals(mMatchSubscriberIds, other.mMatchSubscriberIds) && Arrays.equals(mMatchWifiNetworkKeys, other.mMatchWifiNetworkKeys); } return false; } // TODO(b/270089918): Remove this method. This can only be done after there are no more callers, // including in OEM code which can access this by linking against the framework. /** @hide */ public boolean isMatchRuleMobile() { // TODO : this is part of hidden-o txt, does that mean it should be annotated with // @UnsupportedAppUsage(maxTargetSdk = O) ? If yes, can't throwAtLeastU() lest apps // targeting O- crash on those devices. switch (mMatchRule) { case MATCH_MOBILE: // Old MATCH_MOBILE_WILDCARD case 6: return true; default: return false; } } /** * Get match rule of the template. See {@code MATCH_*}. */ public int getMatchRule() { return mMatchRule; } /** * Get subscriber Id of the template. * * @deprecated User should use {@link #getSubscriberIds} instead. * @hide */ @Deprecated @Nullable @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU, publicAlternatives = "Caller should use {@code getSubscriberIds} instead.") public String getSubscriberId() { return CollectionUtils.isEmpty(mMatchSubscriberIds) ? null : mMatchSubscriberIds[0]; } /** * Get set of subscriber Ids of the template. */ @NonNull public Set getSubscriberIds() { return new ArraySet<>(Arrays.asList(mMatchSubscriberIds)); } /** * Get the set of Wifi Network Keys of the template. * See {@link WifiInfo#getNetworkKey()}. */ @NonNull public Set getWifiNetworkKeys() { return new ArraySet<>(Arrays.asList(mMatchWifiNetworkKeys)); } /** @hide */ // TODO: Remove this and replace all callers with {@link #getWifiNetworkKeys()}. @Nullable public String getNetworkId() { return getWifiNetworkKeys().isEmpty() ? null : getWifiNetworkKeys().iterator().next(); } /** * Get meteredness filter of the template. */ @NetworkStats.Meteredness public int getMeteredness() { return mMetered; } /** * Get roaming filter of the template. */ @NetworkStats.Roaming public int getRoaming() { return mRoaming; } /** * Get the default network status filter of the template. */ @NetworkStats.DefaultNetwork public int getDefaultNetworkStatus() { return mDefaultNetwork; } /** * Get the Radio Access Technology(RAT) type filter of the template. */ public int getRatType() { return mRatType; } /** * Get the OEM managed filter of the template. See {@code OEM_MANAGED_*} or * {@code android.net.NetworkIdentity#OEM_*}. */ @OemManaged public int getOemManaged() { return mOemManaged; } /** * Test if given {@link NetworkIdentity} matches this template. * * @hide */ @SystemApi(client = MODULE_LIBRARIES) public boolean matches(@NonNull NetworkIdentity ident) { Objects.requireNonNull(ident); if (!matchesMetered(ident)) return false; if (!matchesRoaming(ident)) return false; if (!matchesDefaultNetwork(ident)) return false; if (!matchesOemNetwork(ident)) return false; switch (mMatchRule) { case MATCH_MOBILE: return matchesMobile(ident); case MATCH_WIFI: return matchesWifi(ident); case MATCH_ETHERNET: return matchesEthernet(ident); case MATCH_BLUETOOTH: return matchesBluetooth(ident); case MATCH_PROXY: return matchesProxy(ident); case MATCH_CARRIER: return matchesCarrier(ident); case MATCH_TEST: return matchesTest(ident); default: // We have no idea what kind of network template we are, so we // just claim not to match anything. return false; } } private boolean matchesMetered(NetworkIdentity ident) { return (mMetered == METERED_ALL) || (mMetered == METERED_YES && ident.mMetered) || (mMetered == METERED_NO && !ident.mMetered); } private boolean matchesRoaming(NetworkIdentity ident) { return (mRoaming == ROAMING_ALL) || (mRoaming == ROAMING_YES && ident.mRoaming) || (mRoaming == ROAMING_NO && !ident.mRoaming); } private boolean matchesDefaultNetwork(NetworkIdentity ident) { return (mDefaultNetwork == DEFAULT_NETWORK_ALL) || (mDefaultNetwork == DEFAULT_NETWORK_YES && ident.mDefaultNetwork) || (mDefaultNetwork == DEFAULT_NETWORK_NO && !ident.mDefaultNetwork); } private boolean matchesOemNetwork(NetworkIdentity ident) { return (mOemManaged == OEM_MANAGED_ALL) || (mOemManaged == OEM_MANAGED_YES && ident.mOemManaged != OEM_NONE) || (mOemManaged == ident.mOemManaged); } private boolean matchesCollapsedRatType(NetworkIdentity ident) { return mRatType == NETWORK_TYPE_ALL || NetworkStatsManager.getCollapsedRatType(mRatType) == NetworkStatsManager.getCollapsedRatType(ident.mRatType); } /** * Check if this template matches {@code subscriberId}. Returns true if this * template was created with a {@code mMatchSubscriberIds} array that contains * {@code subscriberId} or if {@code mMatchSubscriberIds} is empty. * * @hide */ public boolean matchesSubscriberId(@Nullable String subscriberId) { return mMatchSubscriberIds.length == 0 || CollectionUtils.contains(mMatchSubscriberIds, subscriberId); } /** * Check if network matches key of the wifi network. * Returns true when the key matches, or when {@code mMatchWifiNetworkKeys} is * empty. * * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getNetworkKey()} * to know details about the key. */ private boolean matchesWifiNetworkKey(@NonNull String wifiNetworkKey) { // Note that this code accepts null wifi network keys because of a past bug where wifi // code was sending a null network key for some connected networks, which isn't expected // and ended up stored in the data on many devices. // A null network key in the data matches a wildcard template (one where // {@code mMatchWifiNetworkKeys} is empty), but not one where {@code MatchWifiNetworkKeys} // contains null. See b/266598304. if (wifiNetworkKey == null) { return CollectionUtils.isEmpty(mMatchWifiNetworkKeys); } return CollectionUtils.isEmpty(mMatchWifiNetworkKeys) || CollectionUtils.contains(mMatchWifiNetworkKeys, wifiNetworkKey); } /** * Check if mobile network matches IMSI. */ private boolean matchesMobile(NetworkIdentity ident) { if (ident.mType == TYPE_WIMAX) { // TODO: consider matching against WiMAX subscriber identity return true; } else { return (CollectionUtils.isEmpty(mMatchSubscriberIds) || CollectionUtils.contains(mMatchSubscriberIds, ident.mSubscriberId)) && (ident.mType == TYPE_MOBILE && matchesCollapsedRatType(ident)); } } /** * Check if matches Wi-Fi network template. */ private boolean matchesWifi(NetworkIdentity ident) { switch (ident.mType) { case TYPE_WIFI: return matchesSubscriberId(ident.mSubscriberId) && matchesWifiNetworkKey(ident.mWifiNetworkKey); case TYPE_WIFI_P2P: return CollectionUtils.isEmpty(mMatchWifiNetworkKeys); default: return false; } } /** * Check if matches Ethernet network template. */ private boolean matchesEthernet(NetworkIdentity ident) { if (ident.mType == TYPE_ETHERNET) { return true; } return false; } /** * Check if matches carrier network. The carrier networks means it includes the subscriberId. */ private boolean matchesCarrier(NetworkIdentity ident) { return ident.mSubscriberId != null && !CollectionUtils.isEmpty(mMatchSubscriberIds) && CollectionUtils.contains(mMatchSubscriberIds, ident.mSubscriberId); } /** * Check if matches test network. If the wifiNetworkKeys in the template is specified, Then it * will only match a network containing any of the specified the wifi network key. Otherwise, * all test networks would be matched. */ private boolean matchesTest(NetworkIdentity ident) { return ident.mType == NetworkIdentity.TYPE_TEST && ((CollectionUtils.isEmpty(mMatchWifiNetworkKeys) || CollectionUtils.contains(mMatchWifiNetworkKeys, ident.mWifiNetworkKey))); } /** * Check if matches Bluetooth network template. */ private boolean matchesBluetooth(NetworkIdentity ident) { if (ident.mType == TYPE_BLUETOOTH) { return true; } return false; } /** * Check if matches Proxy network template. */ private boolean matchesProxy(NetworkIdentity ident) { return ident.mType == TYPE_PROXY; } private static String getMatchRuleName(int matchRule) { switch (matchRule) { case MATCH_MOBILE: return "MOBILE"; case MATCH_WIFI: return "WIFI"; case MATCH_ETHERNET: return "ETHERNET"; case MATCH_BLUETOOTH: return "BLUETOOTH"; case MATCH_PROXY: return "PROXY"; case MATCH_CARRIER: return "CARRIER"; case MATCH_TEST: return "TEST"; default: return "UNKNOWN(" + matchRule + ")"; } } private static String getOemManagedNames(int oemManaged) { switch (oemManaged) { case OEM_MANAGED_ALL: return "OEM_MANAGED_ALL"; case OEM_MANAGED_NO: return "OEM_MANAGED_NO"; case OEM_MANAGED_YES: return "OEM_MANAGED_YES"; default: return NetworkIdentity.getOemManagedNames(oemManaged); } } /** * Examine the given template and normalize it. * We pick the "lowest" merged subscriber as the primary * for key purposes, and expand the template to match all other merged * subscribers. *

* For example, given an incoming template matching B, and the currently * active merge set [A,B], we'd return a new template that matches both A and B. * * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU, publicAlternatives = "There is no alternative for {@code NetworkTemplate.normalize}." + "Callers should have their own logic to merge template for" + " different IMSIs and stop calling this function.") public static NetworkTemplate normalize(NetworkTemplate template, String[] merged) { return normalizeImpl(template, Collections.singletonList(merged)); } /** * Examine the given template and normalize it. * We pick the "lowest" merged subscriber as the primary * for key purposes, and expand the template to match all other merged * subscribers. * * There can be multiple merged subscriberIds for multi-SIM devices. * *

* For example, given an incoming template matching B, and the currently * active merge set [A,B], we'd return a new template that matches both A and B. * * @hide */ // TODO(b/273963543): Remove this method. This can only be done after there are no more callers, // including in OEM code which can access this by linking against the framework. public static NetworkTemplate normalize(NetworkTemplate template, List mergedList) { throwAtLeastU(); return normalizeImpl(template, mergedList); } /** * Examine the given template and normalize it. * We pick the "lowest" merged subscriber as the primary * for key purposes, and expand the template to match all other merged * subscribers. * * There can be multiple merged subscriberIds for multi-SIM devices. * *

* For example, given an incoming template matching B, and the currently * active merge set [A,B], we'd return a new template that matches both A and B. * * @hide */ private static NetworkTemplate normalizeImpl(NetworkTemplate template, List mergedList) { // Now there are several types of network which uses SubscriberId to store network // information. For instances: // The TYPE_WIFI with subscriberId means that it is a merged carrier wifi network. // The TYPE_CARRIER means that the network associate to specific carrier network. if (CollectionUtils.isEmpty(template.mMatchSubscriberIds)) return template; for (String[] merged : mergedList) { if (CollectionUtils.contains(merged, template.mMatchSubscriberIds[0])) { // Requested template subscriber is part of the merge group; return // a template that matches all merged subscribers. final String[] matchWifiNetworkKeys = template.mMatchWifiNetworkKeys; // TODO: Use NetworkTemplate.Builder to build a template after NetworkTemplate // could handle incompatible subscriberIds. See b/217805241. return new NetworkTemplate(template.mMatchRule, merged, CollectionUtils.isEmpty(matchWifiNetworkKeys) ? new String[0] : new String[] { matchWifiNetworkKeys[0] }, (template.mMatchRule == MATCH_MOBILE || template.mMatchRule == MATCH_CARRIER) ? METERED_YES : METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL); } } return template; } @UnsupportedAppUsage public static final @android.annotation.NonNull Creator CREATOR = new Creator() { @Override public NetworkTemplate createFromParcel(Parcel in) { return new NetworkTemplate(in); } @Override public NetworkTemplate[] newArray(int size) { return new NetworkTemplate[size]; } }; /** * Builder class for NetworkTemplate. */ public static final class Builder { private final int mMatchRule; // Use a SortedSet to provide a deterministic order when fetching the first one. @NonNull private final SortedSet mMatchSubscriberIds = new TreeSet<>(Comparator.nullsFirst(Comparator.naturalOrder())); @NonNull private final SortedSet mMatchWifiNetworkKeys = new TreeSet<>(); // Matches for the NetworkStats constants METERED_*, ROAMING_* and DEFAULT_NETWORK_*. private int mMetered; private int mRoaming; private int mDefaultNetwork; private int mRatType; // Bitfield containing OEM network properties {@code NetworkIdentity#OEM_*}. private int mOemManaged; /** * Creates a new Builder with given match rule to construct NetworkTemplate objects. * * @param matchRule the match rule of the template, see {@code MATCH_*}. */ public Builder(@TemplateMatchRule final int matchRule) { assertRequestableMatchRule(matchRule); // Initialize members with default values. mMatchRule = matchRule; mMetered = METERED_ALL; mRoaming = ROAMING_ALL; mDefaultNetwork = DEFAULT_NETWORK_ALL; mRatType = NETWORK_TYPE_ALL; mOemManaged = OEM_MANAGED_ALL; } /** * Set the Subscriber Ids. Calling this function with an empty set represents * the intention of matching any Subscriber Ids. * * @param subscriberIds the list of Subscriber Ids. * @return this builder. */ @NonNull public Builder setSubscriberIds(@NonNull Set subscriberIds) { Objects.requireNonNull(subscriberIds); mMatchSubscriberIds.clear(); mMatchSubscriberIds.addAll(subscriberIds); return this; } /** * Set the Wifi Network Keys. Calling this function with an empty set represents * the intention of matching any Wifi Network Key. * * @param wifiNetworkKeys the list of Wifi Network Key, * see {@link WifiInfo#getNetworkKey()}. * Or an empty list to match all networks. * Note that {@code getNetworkKey()} might get null key * when wifi disconnects. However, the caller should never invoke * this function with a null Wifi Network Key since such statistics * never exists. * @return this builder. */ @NonNull public Builder setWifiNetworkKeys(@NonNull Set wifiNetworkKeys) { Objects.requireNonNull(wifiNetworkKeys); for (String key : wifiNetworkKeys) { if (key == null) { throw new IllegalArgumentException("Null is not a valid key"); } } mMatchWifiNetworkKeys.clear(); mMatchWifiNetworkKeys.addAll(wifiNetworkKeys); return this; } /** * Set the meteredness filter. * * @param metered the meteredness filter. * @return this builder. */ @NonNull public Builder setMeteredness(@NetworkStats.Meteredness int metered) { mMetered = metered; return this; } /** * Set the roaming filter. * * @param roaming the roaming filter. * @return this builder. */ @NonNull public Builder setRoaming(@NetworkStats.Roaming int roaming) { mRoaming = roaming; return this; } /** * Set the default network status filter. * * @param defaultNetwork the default network status filter. * @return this builder. */ @NonNull public Builder setDefaultNetworkStatus(@NetworkStats.DefaultNetwork int defaultNetwork) { mDefaultNetwork = defaultNetwork; return this; } /** * Set the Radio Access Technology(RAT) type filter. * * @param ratType the Radio Access Technology(RAT) type filter. Use * {@link #NETWORK_TYPE_ALL} to include all network types when filtering. * See {@code TelephonyManager.NETWORK_TYPE_*}. * @return this builder. */ @NonNull public Builder setRatType(int ratType) { // Input will be validated with the match rule when building the template. mRatType = ratType; return this; } /** * Set the OEM managed filter. * * @param oemManaged the match rule to match different type of OEM managed network or * unmanaged networks. See {@code OEM_MANAGED_*}. * @return this builder. */ @NonNull public Builder setOemManaged(@OemManaged int oemManaged) { mOemManaged = oemManaged; return this; } /** * Check whether the match rule is requestable. * * @param matchRule the target match rule to be checked. */ private static void assertRequestableMatchRule(final int matchRule) { if (!isKnownMatchRule(matchRule)) { throw new IllegalArgumentException("Invalid match rule: " + getMatchRuleName(matchRule)); } } private void assertRequestableParameters() { validateWifiNetworkKeys(); // TODO: Check all the input are legitimate. } private void validateWifiNetworkKeys() { // Also allow querying test networks which use wifi network key as identifier. if (mMatchRule != MATCH_WIFI && mMatchRule != MATCH_TEST && !mMatchWifiNetworkKeys.isEmpty()) { throw new IllegalArgumentException("Trying to build non wifi match rule: " + mMatchRule + " with wifi network keys"); } } /** * Builds the instance of the NetworkTemplate. * * @return the built instance of NetworkTemplate. */ @NonNull public NetworkTemplate build() { assertRequestableParameters(); return new NetworkTemplate(mMatchRule, mMatchSubscriberIds.toArray(new String[0]), mMatchWifiNetworkKeys.toArray(new String[0]), mMetered, mRoaming, mDefaultNetwork, mRatType, mOemManaged); } } }