summaryrefslogtreecommitdiffstats
path: root/services/java/com/android/server/net/NetworkPolicyManagerService.java
diff options
context:
space:
mode:
Diffstat (limited to 'services/java/com/android/server/net/NetworkPolicyManagerService.java')
-rw-r--r--services/java/com/android/server/net/NetworkPolicyManagerService.java491
1 files changed, 464 insertions, 27 deletions
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index 1a90a92..c8f617e 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -16,15 +16,22 @@
package com.android.server.net;
+import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
import static android.Manifest.permission.DUMP;
import static android.Manifest.permission.MANAGE_APP_TOKENS;
-import static android.Manifest.permission.UPDATE_DEVICE_STATS;
+import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
+import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.NetworkPolicyManager.POLICY_NONE;
import static android.net.NetworkPolicyManager.POLICY_REJECT_PAID_BACKGROUND;
import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_PAID;
import static android.net.NetworkPolicyManager.dumpPolicy;
import static android.net.NetworkPolicyManager.dumpRules;
+import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static android.text.format.Time.MONTH_DAY;
+import static com.android.internal.util.Preconditions.checkNotNull;
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
import android.app.IActivityManager;
import android.app.IProcessObserver;
@@ -33,19 +40,51 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
+import android.net.IConnectivityManager;
import android.net.INetworkPolicyListener;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
+import android.net.NetworkPolicy;
+import android.net.NetworkState;
+import android.net.NetworkStats;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IPowerManager;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.text.format.Time;
+import android.util.NtpTrustedTime;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import android.util.TrustedTime;
+import android.util.Xml;
+import com.android.internal.os.AtomicFile;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.Objects;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.io.PrintWriter;
+import java.net.ProtocolException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
+import libcore.io.IoUtils;
/**
* Service that maintains low-level network policy rules and collects usage
@@ -59,10 +98,30 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private static final String TAG = "NetworkPolicy";
private static final boolean LOGD = true;
+ private static final int VERSION_CURRENT = 1;
+
+ private static final String TAG_POLICY_LIST = "policy-list";
+ private static final String TAG_NETWORK_POLICY = "network-policy";
+ private static final String TAG_UID_POLICY = "uid-policy";
+
+ private static final String ATTR_VERSION = "version";
+ private static final String ATTR_NETWORK_TEMPLATE = "networkTemplate";
+ private static final String ATTR_SUBSCRIBER_ID = "subscriberId";
+ private static final String ATTR_CYCLE_DAY = "cycleDay";
+ private static final String ATTR_WARNING_BYTES = "warningBytes";
+ private static final String ATTR_LIMIT_BYTES = "limitBytes";
+ private static final String ATTR_UID = "uid";
+ private static final String ATTR_POLICY = "policy";
+
+ private static final long TIME_CACHE_MAX_AGE = DAY_IN_MILLIS;
+
private final Context mContext;
private final IActivityManager mActivityManager;
private final IPowerManager mPowerManager;
private final INetworkStatsService mNetworkStats;
+ private final TrustedTime mTime;
+
+ private IConnectivityManager mConnManager;
private final Object mRulesLock = new Object();
@@ -73,6 +132,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
/** Current derived network rules for each UID. */
private SparseIntArray mUidRules = new SparseIntArray();
+ /** Set of policies for strong network templates. */
+ private HashMap<StrongTemplate, NetworkPolicy> mTemplatePolicy = Maps.newHashMap();
+
/** Foreground at both UID and PID granularity. */
private SparseBooleanArray mUidForeground = new SparseBooleanArray();
private SparseArray<SparseBooleanArray> mUidPidForeground = new SparseArray<
@@ -81,26 +143,52 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private final RemoteCallbackList<INetworkPolicyListener> mListeners = new RemoteCallbackList<
INetworkPolicyListener>();
- // TODO: save/restore policy information from disk
+ private final HandlerThread mHandlerThread;
+ private final Handler mHandler;
+
+ private final AtomicFile mPolicyFile;
// TODO: keep whitelist of system-critical services that should never have
// rules enforced, such as system, phone, and radio UIDs.
- // TODO: keep record of billing cycle details, and limit rules
- // TODO: keep map of interfaces-to-billing-relationship
-
// TODO: dispatch callbacks through handler when locked
public NetworkPolicyManagerService(Context context, IActivityManager activityManager,
IPowerManager powerManager, INetworkStatsService networkStats) {
+ // TODO: move to using cached NtpTrustedTime
+ this(context, activityManager, powerManager, networkStats, new NtpTrustedTime(),
+ getSystemDir());
+ }
+
+ private static File getSystemDir() {
+ return new File(Environment.getDataDirectory(), "system");
+ }
+
+ public NetworkPolicyManagerService(Context context, IActivityManager activityManager,
+ IPowerManager powerManager, INetworkStatsService networkStats, TrustedTime time,
+ File systemDir) {
mContext = checkNotNull(context, "missing context");
mActivityManager = checkNotNull(activityManager, "missing activityManager");
mPowerManager = checkNotNull(powerManager, "missing powerManager");
mNetworkStats = checkNotNull(networkStats, "missing networkStats");
+ mTime = checkNotNull(time, "missing TrustedTime");
+
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+
+ mPolicyFile = new AtomicFile(new File(systemDir, "netpolicy.xml"));
+ }
+
+ public void bindConnectivityManager(IConnectivityManager connManager) {
+ mConnManager = checkNotNull(connManager, "missing IConnectivityManager");
}
public void systemReady() {
- // TODO: read current policy from disk
+ synchronized (mRulesLock) {
+ // read policy from disk
+ readPolicyLocked();
+ }
updateScreenOn();
@@ -120,6 +208,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
screenFilter.addAction(Intent.ACTION_SCREEN_OFF);
mContext.registerReceiver(mScreenReceiver, screenFilter);
+ // watch for network interfaces to be claimed
+ final IntentFilter ifaceFilter = new IntentFilter();
+ ifaceFilter.addAction(CONNECTIVITY_ACTION);
+ mContext.registerReceiver(mIfaceReceiver, ifaceFilter, CONNECTIVITY_INTERNAL, mHandler);
+
}
private IProcessObserver mProcessObserver = new IProcessObserver.Stub() {
@@ -139,7 +232,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
mUidPidForeground.put(uid, pidForeground);
}
pidForeground.put(pid, foregroundActivities);
- computeUidForegroundL(uid);
+ computeUidForegroundLocked(uid);
}
}
@@ -153,7 +246,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
final SparseBooleanArray pidForeground = mUidPidForeground.get(uid);
if (pidForeground != null) {
pidForeground.delete(pid);
- computeUidForegroundL(uid);
+ computeUidForegroundLocked(uid);
}
}
}
@@ -170,23 +263,279 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
};
+ /**
+ * Receiver that watches for {@link IConnectivityManager} to claim network
+ * interfaces. Used to apply {@link NetworkPolicy} when networks match
+ * {@link StrongTemplate}.
+ */
+ private BroadcastReceiver mIfaceReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // on background handler thread, and verified CONNECTIVITY_INTERNAL
+ // permission above.
+ synchronized (mRulesLock) {
+ updateIfacesLocked();
+ }
+ }
+ };
+
+ /**
+ * Examine all connected {@link NetworkState}, looking for
+ * {@link NetworkPolicy} that need to be enforced. When matches found, set
+ * remaining quota based on usage cycle and historical stats.
+ */
+ private void updateIfacesLocked() {
+ if (LOGD) Slog.v(TAG, "updateIfacesLocked()");
+
+ final NetworkState[] states;
+ try {
+ states = mConnManager.getAllNetworkState();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "problem reading network state");
+ return;
+ }
+
+ // first, derive identity for all connected networks, which can be used
+ // to match against templates.
+ final HashMap<NetworkIdentity, String> networks = Maps.newHashMap();
+ for (NetworkState state : states) {
+ // stash identity and iface away for later use
+ if (state.networkInfo.isConnected()) {
+ final String iface = state.linkProperties.getInterfaceName();
+ final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state);
+ networks.put(ident, iface);
+ }
+ }
+
+ // build list of rules and ifaces to enforce them against
+ final HashMap<StrongTemplate, String[]> rules = Maps.newHashMap();
+ final ArrayList<String> ifaceList = Lists.newArrayList();
+ for (StrongTemplate template : mTemplatePolicy.keySet()) {
+
+ // collect all active ifaces that match this template
+ ifaceList.clear();
+ for (NetworkIdentity ident : networks.keySet()) {
+ if (ident.matchesTemplate(template.networkTemplate, template.subscriberId)) {
+ final String iface = networks.get(ident);
+ ifaceList.add(iface);
+ }
+ }
+
+ if (ifaceList.size() > 0) {
+ final String[] ifaces = ifaceList.toArray(new String[ifaceList.size()]);
+ rules.put(template, ifaces);
+ }
+ }
+
+ // try refreshing time source when stale
+ if (mTime.getCacheAge() > TIME_CACHE_MAX_AGE) {
+ mTime.forceRefresh();
+ }
+
+ final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
+ : System.currentTimeMillis();
+
+ // apply each policy that we found ifaces for; compute remaining data
+ // based on current cycle and historical stats, and push to kernel.
+ for (StrongTemplate template : rules.keySet()) {
+ final NetworkPolicy policy = mTemplatePolicy.get(template);
+ final String[] ifaces = rules.get(policy);
+
+ final long start = computeLastCycleBoundary(currentTime, policy);
+ final long end = currentTime;
+
+ final NetworkStats stats;
+ try {
+ stats = mNetworkStats.getSummaryForNetwork(
+ start, end, template.networkTemplate, template.subscriberId);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "problem reading summary for template " + template.networkTemplate);
+ continue;
+ }
+
+ // remaining "quota" is based on usage in current cycle
+ final long total = stats.rx[0] + stats.tx[0];
+ final long quota = Math.max(0, policy.limitBytes - total);
+
+ if (LOGD) {
+ Slog.d(TAG, "applying policy " + policy.toString() + " to ifaces "
+ + Arrays.toString(ifaces) + " with quota " + quota);
+ }
+
+ // TODO: push rule down through NetworkManagementService.setInterfaceQuota()
+
+ }
+ }
+
+ /**
+ * Compute the last cycle boundary for the given {@link NetworkPolicy}. For
+ * example, if cycle day is 20th, and today is June 15th, it will return May
+ * 20th. When cycle day doesn't exist in current month, it snaps to the 1st
+ * of following month.
+ */
+ // @VisibleForTesting
+ public static long computeLastCycleBoundary(long currentTime, NetworkPolicy policy) {
+ final Time now = new Time(Time.TIMEZONE_UTC);
+ now.set(currentTime);
+
+ // first, find cycle boundary for current month
+ final Time cycle = new Time(now);
+ cycle.hour = cycle.minute = cycle.second = 0;
+ snapToCycleDay(cycle, policy.cycleDay);
+
+ if (cycle.after(now)) {
+ // cycle boundary is beyond now, use last cycle boundary; start by
+ // pushing ourselves squarely into last month.
+ final Time lastMonth = new Time(now);
+ lastMonth.hour = lastMonth.minute = lastMonth.second = 0;
+ lastMonth.monthDay = 1;
+ lastMonth.month -= 1;
+ lastMonth.normalize(true);
+
+ cycle.set(lastMonth);
+ snapToCycleDay(cycle, policy.cycleDay);
+ }
+
+ return cycle.toMillis(true);
+ }
+
+ /**
+ * Snap to the cycle day for the current month given; when cycle day doesn't
+ * exist, it snaps to 1st of following month.
+ */
+ private static void snapToCycleDay(Time time, int cycleDay) {
+ if (cycleDay > time.getActualMaximum(MONTH_DAY)) {
+ // cycle day isn't valid this month; snap to 1st of next month
+ time.month += 1;
+ time.monthDay = 1;
+ } else {
+ time.monthDay = cycleDay;
+ }
+ time.normalize(true);
+ }
+
+ private void readPolicyLocked() {
+ if (LOGD) Slog.v(TAG, "readPolicyLocked()");
+
+ // clear any existing policy and read from disk
+ mTemplatePolicy.clear();
+ mUidPolicy.clear();
+
+ FileInputStream fis = null;
+ try {
+ fis = mPolicyFile.openRead();
+ final XmlPullParser in = Xml.newPullParser();
+ in.setInput(fis, null);
+
+ int type;
+ int version = VERSION_CURRENT;
+ while ((type = in.next()) != END_DOCUMENT) {
+ final String tag = in.getName();
+ if (type == START_TAG) {
+ if (TAG_POLICY_LIST.equals(tag)) {
+ version = readIntAttribute(in, ATTR_VERSION);
+
+ } else if (TAG_NETWORK_POLICY.equals(tag)) {
+ final int networkTemplate = readIntAttribute(in, ATTR_NETWORK_TEMPLATE);
+ final String subscriberId = in.getAttributeValue(null, ATTR_SUBSCRIBER_ID);
+
+ final int cycleDay = readIntAttribute(in, ATTR_CYCLE_DAY);
+ final long warningBytes = readLongAttribute(in, ATTR_WARNING_BYTES);
+ final long limitBytes = readLongAttribute(in, ATTR_LIMIT_BYTES);
+
+ mTemplatePolicy.put(new StrongTemplate(networkTemplate, subscriberId),
+ new NetworkPolicy(cycleDay, warningBytes, limitBytes));
+
+ } else if (TAG_UID_POLICY.equals(tag)) {
+ final int uid = readIntAttribute(in, ATTR_UID);
+ final int policy = readIntAttribute(in, ATTR_POLICY);
+
+ mUidPolicy.put(uid, policy);
+ }
+ }
+ }
+
+ } catch (FileNotFoundException e) {
+ // missing policy is okay, probably first boot
+ } catch (IOException e) {
+ Slog.e(TAG, "problem reading network stats", e);
+ } catch (XmlPullParserException e) {
+ Slog.e(TAG, "problem reading network stats", e);
+ } finally {
+ IoUtils.closeQuietly(fis);
+ }
+ }
+
+ private void writePolicyLocked() {
+ if (LOGD) Slog.v(TAG, "writePolicyLocked()");
+
+ FileOutputStream fos = null;
+ try {
+ fos = mPolicyFile.startWrite();
+
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(fos, "utf-8");
+ out.startDocument(null, true);
+
+ out.startTag(null, TAG_POLICY_LIST);
+ writeIntAttribute(out, ATTR_VERSION, VERSION_CURRENT);
+
+ // write all known network policies
+ for (StrongTemplate template : mTemplatePolicy.keySet()) {
+ final NetworkPolicy policy = mTemplatePolicy.get(template);
+
+ out.startTag(null, TAG_NETWORK_POLICY);
+ writeIntAttribute(out, ATTR_NETWORK_TEMPLATE, template.networkTemplate);
+ if (template.subscriberId != null) {
+ out.attribute(null, ATTR_SUBSCRIBER_ID, template.subscriberId);
+ }
+ writeIntAttribute(out, ATTR_CYCLE_DAY, policy.cycleDay);
+ writeLongAttribute(out, ATTR_WARNING_BYTES, policy.warningBytes);
+ writeLongAttribute(out, ATTR_LIMIT_BYTES, policy.limitBytes);
+ out.endTag(null, TAG_NETWORK_POLICY);
+ }
+
+ // write all known uid policies
+ for (int i = 0; i < mUidPolicy.size(); i++) {
+ final int uid = mUidPolicy.keyAt(i);
+ final int policy = mUidPolicy.valueAt(i);
+
+ out.startTag(null, TAG_UID_POLICY);
+ writeIntAttribute(out, ATTR_UID, uid);
+ writeIntAttribute(out, ATTR_POLICY, policy);
+ out.endTag(null, TAG_UID_POLICY);
+ }
+
+ out.endTag(null, TAG_POLICY_LIST);
+ out.endDocument();
+
+ mPolicyFile.finishWrite(fos);
+ } catch (IOException e) {
+ if (fos != null) {
+ mPolicyFile.failWrite(fos);
+ }
+ }
+ }
+
@Override
public void setUidPolicy(int uid, int policy) {
- // TODO: create permission for modifying data policy
- mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG);
+ mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
final int oldPolicy;
synchronized (mRulesLock) {
oldPolicy = getUidPolicy(uid);
mUidPolicy.put(uid, policy);
- updateRulesForUidL(uid);
- }
- // TODO: consider dispatching BACKGROUND_DATA_SETTING broadcast
+ // uid policy changed, recompute rules and persist policy.
+ updateRulesForUidLocked(uid);
+ writePolicyLocked();
+ }
}
@Override
public int getUidPolicy(int uid) {
+ mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
synchronized (mRulesLock) {
return mUidPolicy.get(uid, POLICY_NONE);
}
@@ -218,10 +567,40 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
@Override
+ public void setNetworkPolicy(int networkType, String subscriberId, NetworkPolicy policy) {
+ mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+ synchronized (mRulesLock) {
+ mTemplatePolicy.put(new StrongTemplate(networkType, subscriberId), policy);
+
+ // network policy changed, recompute template rules based on active
+ // interfaces and persist policy.
+ updateIfacesLocked();
+ writePolicyLocked();
+ }
+ }
+
+ @Override
+ public NetworkPolicy getNetworkPolicy(int networkType, String subscriberId) {
+ mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+ synchronized (mRulesLock) {
+ return mTemplatePolicy.get(new StrongTemplate(networkType, subscriberId));
+ }
+ }
+
+ @Override
protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
mContext.enforceCallingOrSelfPermission(DUMP, TAG);
synchronized (mRulesLock) {
+ fout.println("Network policies:");
+ for (StrongTemplate template : mTemplatePolicy.keySet()) {
+ final NetworkPolicy policy = mTemplatePolicy.get(template);
+ fout.print(" "); fout.println(template.toString());
+ fout.print(" "); fout.println(policy.toString());
+ }
+
fout.println("Policy status for known UIDs:");
final SparseBooleanArray knownUids = new SparseBooleanArray();
@@ -274,9 +653,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
/**
* Foreground for PID changed; recompute foreground at UID level. If
- * changed, will trigger {@link #updateRulesForUidL(int)}.
+ * changed, will trigger {@link #updateRulesForUidLocked(int)}.
*/
- private void computeUidForegroundL(int uid) {
+ private void computeUidForegroundLocked(int uid) {
final SparseBooleanArray pidForeground = mUidPidForeground.get(uid);
// current pid is dropping foreground; examine other pids
@@ -293,7 +672,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
if (oldUidForeground != uidForeground) {
// foreground changed, push updated rules
mUidForeground.put(uid, uidForeground);
- updateRulesForUidL(uid);
+ updateRulesForUidLocked(uid);
}
}
@@ -303,25 +682,25 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
mScreenOn = mPowerManager.isScreenOn();
} catch (RemoteException e) {
}
- updateRulesForScreenL();
+ updateRulesForScreenLocked();
}
}
/**
* Update rules that might be changed by {@link #mScreenOn} value.
*/
- private void updateRulesForScreenL() {
+ private void updateRulesForScreenLocked() {
// only update rules for anyone with foreground activities
final int size = mUidForeground.size();
for (int i = 0; i < size; i++) {
if (mUidForeground.valueAt(i)) {
final int uid = mUidForeground.keyAt(i);
- updateRulesForUidL(uid);
+ updateRulesForUidLocked(uid);
}
}
}
- private void updateRulesForUidL(int uid) {
+ private void updateRulesForUidLocked(int uid) {
final int uidPolicy = getUidPolicy(uid);
final boolean uidForeground = isUidForeground(uid);
@@ -351,13 +730,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
mListeners.finishBroadcast();
}
- private static <T> T checkNotNull(T value, String message) {
- if (value == null) {
- throw new NullPointerException(message);
- }
- return value;
- }
-
private static void collectKeys(SparseIntArray source, SparseBooleanArray target) {
final int size = source.size();
for (int i = 0; i < size; i++) {
@@ -381,4 +753,69 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
fout.print("]");
}
+
+ private static int readIntAttribute(XmlPullParser in, String name) throws IOException {
+ final String value = in.getAttributeValue(null, name);
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ throw new ProtocolException("problem parsing " + name + "=" + value + " as int");
+ }
+ }
+
+ private static long readLongAttribute(XmlPullParser in, String name) throws IOException {
+ final String value = in.getAttributeValue(null, name);
+ try {
+ return Long.parseLong(value);
+ } catch (NumberFormatException e) {
+ throw new ProtocolException("problem parsing " + name + "=" + value + " as int");
+ }
+ }
+
+ private static void writeIntAttribute(XmlSerializer out, String name, int value)
+ throws IOException {
+ out.attribute(null, name, Integer.toString(value));
+ }
+
+ private static void writeLongAttribute(XmlSerializer out, String name, long value)
+ throws IOException {
+ out.attribute(null, name, Long.toString(value));
+ }
+
+ /**
+ * Network template with strong subscriber ID, used as key when defining
+ * {@link NetworkPolicy}.
+ */
+ private static class StrongTemplate {
+ public final int networkTemplate;
+ public final String subscriberId;
+
+ public StrongTemplate(int networkTemplate, String subscriberId) {
+ this.networkTemplate = networkTemplate;
+ this.subscriberId = subscriberId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(networkTemplate, subscriberId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof StrongTemplate) {
+ final StrongTemplate template = (StrongTemplate) obj;
+ return template.networkTemplate == networkTemplate
+ && Objects.equal(template.subscriberId, subscriberId);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "TemplateIdentity: networkTemplate=" + networkTemplate + ", subscriberId="
+ + subscriberId;
+ }
+
+ }
+
}