blob: 33bd884931fc51b6584f438c82cf48efad452b9b [file] [log] [blame]
/*
* 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<String> setOf(@Nullable final String item) {
if (item == null) {
// Set.of will throw if item is null
final Set<String> 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.
* <p>
* Since the merge set is dynamic, it should <em>not</em> 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<String> getSubscriberIds() {
return new ArraySet<>(Arrays.asList(mMatchSubscriberIds));
}
/**
* Get the set of Wifi Network Keys of the template.
* See {@link WifiInfo#getNetworkKey()}.
*/
@NonNull
public Set<String> 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.
* <p>
* 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.
*
* <p>
* 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<String[]> 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.
*
* <p>
* 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<String[]> 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<NetworkTemplate> CREATOR = new Creator<NetworkTemplate>() {
@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<String> mMatchSubscriberIds =
new TreeSet<>(Comparator.nullsFirst(Comparator.naturalOrder()));
@NonNull
private final SortedSet<String> 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<String> 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<String> 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) || matchRule == MATCH_PROXY) {
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);
}
}
}