diff options
-rw-r--r-- | core/java/android/net/INetworkPolicyManager.aidl | 4 | ||||
-rw-r--r-- | core/java/android/net/INetworkStatsService.aidl | 2 | ||||
-rw-r--r-- | core/java/android/net/NetworkPolicy.aidl | 19 | ||||
-rw-r--r-- | core/java/android/net/NetworkPolicy.java | 81 | ||||
-rw-r--r-- | core/java/android/net/NetworkPolicyManager.java | 17 | ||||
-rw-r--r-- | core/res/AndroidManifest.xml | 13 | ||||
-rwxr-xr-x | core/res/res/values/strings.xml | 10 | ||||
-rw-r--r-- | services/java/com/android/server/SystemServer.java | 1 | ||||
-rw-r--r-- | services/java/com/android/server/net/NetworkPolicyManagerService.java | 491 | ||||
-rw-r--r-- | services/java/com/android/server/net/NetworkStatsService.java | 56 | ||||
-rw-r--r-- | services/tests/servicestests/AndroidManifest.xml | 2 | ||||
-rw-r--r-- | services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java | 143 |
12 files changed, 791 insertions, 48 deletions
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl index c1f3530..c9238eb 100644 --- a/core/java/android/net/INetworkPolicyManager.aidl +++ b/core/java/android/net/INetworkPolicyManager.aidl @@ -17,6 +17,7 @@ package android.net; import android.net.INetworkPolicyListener; +import android.net.NetworkPolicy; /** * Interface that creates and modifies network policy rules. @@ -33,6 +34,7 @@ interface INetworkPolicyManager { void registerListener(INetworkPolicyListener listener); void unregisterListener(INetworkPolicyListener listener); - // TODO: build API to surface stats details for settings UI + void setNetworkPolicy(int networkType, String subscriberId, in NetworkPolicy policy); + NetworkPolicy getNetworkPolicy(int networkType, String subscriberId); } diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl index d05c9d3..288112a 100644 --- a/core/java/android/net/INetworkStatsService.aidl +++ b/core/java/android/net/INetworkStatsService.aidl @@ -27,6 +27,8 @@ interface INetworkStatsService { /** Return historical stats for specific UID traffic that matches template. */ NetworkStatsHistory getHistoryForUid(int uid, int networkTemplate); + /** Return usage summary for traffic that matches template. */ + NetworkStats getSummaryForNetwork(long start, long end, int networkTemplate, String subscriberId); /** Return usage summary per UID for traffic that matches template. */ NetworkStats getSummaryForAllUid(long start, long end, int networkTemplate); diff --git a/core/java/android/net/NetworkPolicy.aidl b/core/java/android/net/NetworkPolicy.aidl new file mode 100644 index 0000000..dbabb06 --- /dev/null +++ b/core/java/android/net/NetworkPolicy.aidl @@ -0,0 +1,19 @@ +/** + * 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; + +parcelable NetworkPolicy; diff --git a/core/java/android/net/NetworkPolicy.java b/core/java/android/net/NetworkPolicy.java new file mode 100644 index 0000000..b9909c3 --- /dev/null +++ b/core/java/android/net/NetworkPolicy.java @@ -0,0 +1,81 @@ +/* + * 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 android.os.Parcel; +import android.os.Parcelable; + +/** + * Policy for a specific network, including usage cycle and limits to be + * enforced. + * + * @hide + */ +public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { + public final int cycleDay; + public final long warningBytes; + public final long limitBytes; + + public NetworkPolicy(int cycleDay, long warningBytes, long limitBytes) { + this.cycleDay = cycleDay; + this.warningBytes = warningBytes; + this.limitBytes = limitBytes; + } + + public NetworkPolicy(Parcel in) { + cycleDay = in.readInt(); + warningBytes = in.readLong(); + limitBytes = in.readLong(); + } + + /** {@inheritDoc} */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(cycleDay); + dest.writeLong(warningBytes); + dest.writeLong(limitBytes); + } + + /** {@inheritDoc} */ + public int describeContents() { + return 0; + } + + /** {@inheritDoc} */ + public int compareTo(NetworkPolicy another) { + if (another == null || limitBytes < another.limitBytes) { + return -1; + } else { + return 1; + } + } + + @Override + public String toString() { + return "NetworkPolicy: cycleDay=" + cycleDay + ", warningBytes=" + warningBytes + + ", limitBytes=" + limitBytes; + } + + public static final Creator<NetworkPolicy> CREATOR = new Creator<NetworkPolicy>() { + public NetworkPolicy createFromParcel(Parcel in) { + return new NetworkPolicy(in); + } + + public NetworkPolicy[] newArray(int size) { + return new NetworkPolicy[size]; + } + }; +} diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index dd7c1b0..0f4dc9a 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -51,6 +51,23 @@ public class NetworkPolicyManager { return (NetworkPolicyManager) context.getSystemService(Context.NETWORK_POLICY_SERVICE); } + /** {@hide} */ + public void setNetworkPolicy(int networkType, String subscriberId, NetworkPolicy policy) { + try { + mService.setNetworkPolicy(networkType, subscriberId, policy); + } catch (RemoteException e) { + } + } + + /** {@hide} */ + public NetworkPolicy getNetworkPolicy(int networkType, String subscriberId) { + try { + return mService.getNetworkPolicy(networkType, subscriberId); + } catch (RemoteException e) { + return null; + } + } + /** * Set policy flags for specific UID. * diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index b2606c1..a8aff37 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1377,6 +1377,19 @@ <permission android:name="android.permission.CRYPT_KEEPER" android:protectionLevel="signatureOrSystem" /> + <!-- Allows an application to read historical network usage for + specific networks and applications. @hide --> + <permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" + android:label="@string/permlab_readNetworkUsageHistory" + android:description="@string/permdesc_readNetworkUsageHistory" + android:protectionLevel="signature" /> + + <!-- Allows an application to manage network policies (such as warning and disable + limits) and to define application-specific rules. @hide --> + <permission android:name="android.permission.MANAGE_NETWORK_POLICY" + android:label="@string/permlab_manageNetworkPolicy" + android:description="@string/permdesc_manageNetworkPolicy" + android:protectionLevel="signature" /> <!-- C2DM permission. @hide Used internally. diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 3736157..b264b41 100755 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1431,6 +1431,16 @@ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_use_sip">Allows an application to use the SIP service to make/receive Internet calls.</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_readNetworkUsageHistory">read historical network usage</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_readNetworkUsageHistory">Allows an application to read historical network usage for specific networks and applications.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_manageNetworkPolicy">manage network policy</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_manageNetworkPolicy">Allows an application to manage network policies and define application-specific rules.</string> + <!-- Policy administration --> <!-- Title of policy access to limiting the user's password choices --> diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index fd03e5f..3484baf 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -306,6 +306,7 @@ class ServerThread extends Thread { connectivity = new ConnectivityService(context, networkManagement, networkPolicy); ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity); networkStats.bindConnectivityManager(connectivity); + networkPolicy.bindConnectivityManager(connectivity); } catch (Throwable e) { Slog.e(TAG, "Failure starting Connectivity Service", e); } 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; + } + + } + } diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java index 8db2839..161c393 100644 --- a/services/java/com/android/server/net/NetworkStatsService.java +++ b/services/java/com/android/server/net/NetworkStatsService.java @@ -18,8 +18,8 @@ package com.android.server.net; import static android.Manifest.permission.CONNECTIVITY_INTERNAL; import static android.Manifest.permission.DUMP; +import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY; import static android.Manifest.permission.SHUTDOWN; -import static android.Manifest.permission.UPDATE_DEVICE_STATS; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.NetworkStats.IFACE_ALL; import static android.net.NetworkStats.UID_ALL; @@ -86,7 +86,7 @@ import libcore.io.IoUtils; * other system services. */ public class NetworkStatsService extends INetworkStatsService.Stub { - private static final String TAG = "NetworkStatsService"; + private static final String TAG = "NetworkStats"; private static final boolean LOGD = true; /** File header magic number: "ANET" */ @@ -178,20 +178,25 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mSummaryFile = new AtomicFile(new File(systemDir, "netstats.bin")); } + public void bindConnectivityManager(IConnectivityManager connManager) { + mConnManager = checkNotNull(connManager, "missing IConnectivityManager"); + } + public void systemReady() { synchronized (mStatsLock) { // read historical stats from disk readStatsLocked(); } - // watch other system services that claim interfaces + // watch for network interfaces to be claimed final IntentFilter ifaceFilter = new IntentFilter(); ifaceFilter.addAction(CONNECTIVITY_ACTION); mContext.registerReceiver(mIfaceReceiver, ifaceFilter, CONNECTIVITY_INTERNAL, mHandler); // listen for periodic polling events + // TODO: switch to stronger internal permission final IntentFilter pollFilter = new IntentFilter(ACTION_NETWORK_STATS_POLL); - mContext.registerReceiver(mPollReceiver, pollFilter, UPDATE_DEVICE_STATS, mHandler); + mContext.registerReceiver(mPollReceiver, pollFilter, READ_NETWORK_USAGE_HISTORY, mHandler); // persist stats during clean shutdown final IntentFilter shutdownFilter = new IntentFilter(Intent.ACTION_SHUTDOWN); @@ -204,10 +209,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } } - public void bindConnectivityManager(IConnectivityManager connManager) { - mConnManager = checkNotNull(connManager, "missing IConnectivityManager"); - } - private void shutdownLocked() { mContext.unregisterReceiver(mIfaceReceiver); mContext.unregisterReceiver(mPollReceiver); @@ -237,8 +238,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public NetworkStatsHistory getHistoryForNetwork(int networkTemplate) { - // TODO: create relaxed permission for reading stats - mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG); + mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); synchronized (mStatsLock) { // combine all interfaces that match template @@ -257,17 +257,41 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public NetworkStatsHistory getHistoryForUid(int uid, int networkTemplate) { - // TODO: create relaxed permission for reading stats - mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG); + mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); // TODO: return history for requested uid return null; } @Override + public NetworkStats getSummaryForNetwork( + long start, long end, int networkTemplate, String subscriberId) { + mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); + + synchronized (mStatsLock) { + long rx = 0; + long tx = 0; + long[] networkTotal = new long[2]; + + // combine total from all interfaces that match template + for (InterfaceIdentity ident : mSummaryStats.keySet()) { + final NetworkStatsHistory history = mSummaryStats.get(ident); + if (ident.matchesTemplate(networkTemplate, subscriberId)) { + networkTotal = history.getTotalData(start, end, networkTotal); + rx += networkTotal[0]; + tx += networkTotal[1]; + } + } + + final NetworkStats.Builder stats = new NetworkStats.Builder(end - start, 1); + stats.addEntry(IFACE_ALL, UID_ALL, tx, tx); + return stats.build(); + } + } + + @Override public NetworkStats getSummaryForAllUid(long start, long end, int networkTemplate) { - // TODO: create relaxed permission for reading stats - mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG); + mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); // TODO: apply networktemplate once granular uid stats are stored. @@ -275,11 +299,11 @@ public class NetworkStatsService extends INetworkStatsService.Stub { final int size = mDetailStats.size(); final NetworkStats.Builder stats = new NetworkStats.Builder(end - start, size); - final long[] total = new long[2]; + long[] total = new long[2]; for (int i = 0; i < size; i++) { final int uid = mDetailStats.keyAt(i); final NetworkStatsHistory history = mDetailStats.valueAt(i); - history.getTotalData(start, end, total); + total = history.getTotalData(start, end, total); stats.addEntry(IFACE_ALL, uid, total[0], total[1]); } return stats.build(); diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 295d569..151fde7 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -26,6 +26,8 @@ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" /> <uses-permission android:name="android.permission.MANAGE_APP_TOKENS" /> + <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" /> + <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" /> <application> <uses-library android:name="android.test.runner" /> diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java index 6552cdf..c4ddb00 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java @@ -16,10 +16,15 @@ package com.android.server; +import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; +import static android.net.ConnectivityManager.TYPE_WIFI; 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.NetworkStats.UID_ALL; +import static android.net.TrafficStats.TEMPLATE_WIFI; +import static com.android.server.net.NetworkPolicyManagerService.computeLastCycleBoundary; import static org.easymock.EasyMock.capture; import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.eq; @@ -31,20 +36,30 @@ import android.app.IProcessObserver; import android.content.Intent; import android.content.pm.PackageManager; import android.net.ConnectivityManager; +import android.net.IConnectivityManager; import android.net.INetworkPolicyListener; import android.net.INetworkStatsService; +import android.net.LinkProperties; +import android.net.NetworkInfo; +import android.net.NetworkInfo.DetailedState; +import android.net.NetworkPolicy; +import android.net.NetworkState; +import android.net.NetworkStats; import android.os.Binder; import android.os.IPowerManager; import android.test.AndroidTestCase; import android.test.mock.MockPackageManager; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.Suppress; +import android.text.format.Time; +import android.util.TrustedTime; import com.android.server.net.NetworkPolicyManagerService; import org.easymock.Capture; import org.easymock.EasyMock; +import java.io.File; import java.util.concurrent.Future; /** @@ -54,12 +69,18 @@ import java.util.concurrent.Future; public class NetworkPolicyManagerServiceTest extends AndroidTestCase { private static final String TAG = "NetworkPolicyManagerServiceTest"; + private static final long TEST_START = 1194220800000L; + private static final String TEST_IFACE = "test0"; + private BroadcastInterceptingContext mServiceContext; + private File mPolicyDir; private IActivityManager mActivityManager; private IPowerManager mPowerManager; private INetworkStatsService mStatsService; private INetworkPolicyListener mPolicyListener; + private TrustedTime mTime; + private IConnectivityManager mConnManager; private NetworkPolicyManagerService mService; private IProcessObserver mProcessObserver; @@ -90,13 +111,18 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { } }; + mPolicyDir = getContext().getFilesDir(); + mActivityManager = createMock(IActivityManager.class); mPowerManager = createMock(IPowerManager.class); mStatsService = createMock(INetworkStatsService.class); mPolicyListener = createMock(INetworkPolicyListener.class); + mTime = createMock(TrustedTime.class); + mConnManager = createMock(IConnectivityManager.class); mService = new NetworkPolicyManagerService( - mServiceContext, mActivityManager, mPowerManager, mStatsService); + mServiceContext, mActivityManager, mPowerManager, mStatsService, mTime, mPolicyDir); + mService.bindConnectivityManager(mConnManager); // RemoteCallbackList needs a binder to use as key expect(mPolicyListener.asBinder()).andReturn(mStubBinder).atLeastOnce(); @@ -122,12 +148,18 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { @Override public void tearDown() throws Exception { + for (File file : mPolicyDir.listFiles()) { + file.delete(); + } + mServiceContext = null; + mPolicyDir = null; mActivityManager = null; mPowerManager = null; mStatsService = null; mPolicyListener = null; + mTime = null; mService = null; mProcessObserver = null; @@ -260,17 +292,120 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { verifyAndReset(); } + public void testLastCycleBoundaryThisMonth() throws Exception { + // assume cycle day of "5th", which should be in same month + final long currentTime = parseTime("2007-11-14T00:00:00.000Z"); + final long expectedCycle = parseTime("2007-11-05T00:00:00.000Z"); + + final NetworkPolicy policy = new NetworkPolicy(5, 1024L, 1024L); + final long actualCycle = computeLastCycleBoundary(currentTime, policy); + assertEquals(expectedCycle, actualCycle); + } + + public void testLastCycleBoundaryLastMonth() throws Exception { + // assume cycle day of "20th", which should be in last month + final long currentTime = parseTime("2007-11-14T00:00:00.000Z"); + final long expectedCycle = parseTime("2007-10-20T00:00:00.000Z"); + + final NetworkPolicy policy = new NetworkPolicy(20, 1024L, 1024L); + final long actualCycle = computeLastCycleBoundary(currentTime, policy); + assertEquals(expectedCycle, actualCycle); + } + + public void testLastCycleBoundaryThisMonthFebruary() throws Exception { + // assume cycle day of "30th" in february; should go to january + final long currentTime = parseTime("2007-02-14T00:00:00.000Z"); + final long expectedCycle = parseTime("2007-01-30T00:00:00.000Z"); + + final NetworkPolicy policy = new NetworkPolicy(30, 1024L, 1024L); + final long actualCycle = computeLastCycleBoundary(currentTime, policy); + assertEquals(expectedCycle, actualCycle); + } + + public void testLastCycleBoundaryLastMonthFebruary() throws Exception { + // assume cycle day of "30th" in february, which should clamp + final long currentTime = parseTime("2007-03-14T00:00:00.000Z"); + final long expectedCycle = parseTime("2007-03-01T00:00:00.000Z"); + + final NetworkPolicy policy = new NetworkPolicy(30, 1024L, 1024L); + final long actualCycle = computeLastCycleBoundary(currentTime, policy); + assertEquals(expectedCycle, actualCycle); + } + + public void testNetworkPolicyAppliedCycleLastMonth() throws Exception { + long elapsedRealtime = 0; + NetworkState[] state = null; + NetworkStats stats = null; + + final long TIME_FEB_15 = 1171497600000L; + final long TIME_MAR_10 = 1173484800000L; + final int CYCLE_DAY = 15; + + // first, pretend that wifi network comes online. no policy active, + // which means we shouldn't push limit to interface. + state = new NetworkState[] { buildWifi() }; + expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce(); + expectTime(TIME_MAR_10 + elapsedRealtime); + + replay(); + mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION)); + verifyAndReset(); + + // now change cycle to be on 15th, and test in early march, to verify we + // pick cycle day in previous month. + expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce(); + expectTime(TIME_MAR_10 + elapsedRealtime); + + // pretend that 512 bytes total have happened + stats = new NetworkStats.Builder(elapsedRealtime, 1).addEntry( + TEST_IFACE, UID_ALL, 256L, 256L).build(); + expect(mStatsService.getSummaryForNetwork(TIME_FEB_15, TIME_MAR_10, TEMPLATE_WIFI, null)) + .andReturn(stats).atLeastOnce(); + + // expect that quota remaining should be 1536 bytes + // TODO: write up NetworkManagementService mock + + replay(); + mService.setNetworkPolicy(TEMPLATE_WIFI, null, new NetworkPolicy(CYCLE_DAY, 1024L, 2048L)); + verifyAndReset(); + } + + private static long parseTime(String time) { + final Time result = new Time(); + result.parse3339(time); + return result.toMillis(true); + } + + private static NetworkState buildWifi() { + final NetworkInfo info = new NetworkInfo(TYPE_WIFI, 0, null, null); + info.setDetailedState(DetailedState.CONNECTED, null, null); + final LinkProperties prop = new LinkProperties(); + prop.setInterfaceName(TEST_IFACE); + return new NetworkState(info, prop, null); + } + + public void expectTime(long currentTime) throws Exception { + expect(mTime.forceRefresh()).andReturn(false).anyTimes(); + expect(mTime.hasCache()).andReturn(true).anyTimes(); + expect(mTime.currentTimeMillis()).andReturn(currentTime).anyTimes(); + expect(mTime.getCacheAge()).andReturn(0L).anyTimes(); + expect(mTime.getCacheCertainty()).andReturn(0L).anyTimes(); + } + private void expectRulesChanged(int uid, int policy) throws Exception { mPolicyListener.onRulesChanged(eq(uid), eq(policy)); expectLastCall().atLeastOnce(); } private void replay() { - EasyMock.replay(mActivityManager, mPowerManager, mStatsService, mPolicyListener); + EasyMock.replay(mActivityManager, mPowerManager, mStatsService, mPolicyListener, mTime, + mConnManager); } private void verifyAndReset() { - EasyMock.verify(mActivityManager, mPowerManager, mStatsService, mPolicyListener); - EasyMock.reset(mActivityManager, mPowerManager, mStatsService, mPolicyListener); + EasyMock.verify(mActivityManager, mPowerManager, mStatsService, mPolicyListener, mTime, + mConnManager); + EasyMock.reset(mActivityManager, mPowerManager, mStatsService, mPolicyListener, mTime, + mConnManager); } } |