Merge "Relax permissions around NetworkStatsManager APIs."
diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java
index 2886cda..0fce7a9 100644
--- a/core/java/android/app/usage/NetworkStatsManager.java
+++ b/core/java/android/app/usage/NetworkStatsManager.java
@@ -21,6 +21,7 @@
import android.net.ConnectivityManager;
import android.net.NetworkIdentity;
import android.net.NetworkTemplate;
+import android.os.Build;
import android.os.RemoteException;
import android.util.Log;
@@ -29,10 +30,9 @@
* discrete bins of time called 'Buckets'. See {@link NetworkStats.Bucket} for details.
* <p />
* Queries can define a time interval in the form of start and end timestamps (Long.MIN_VALUE and
- * Long.MAX_VALUE can be used to simulate open ended intervals). All queries (except
- * {@link #querySummaryForDevice}) collect only network usage of apps belonging to the same user
- * as the client. In addition tethering usage, usage by removed users and apps, and usage by system
- * is also included in the results.
+ * Long.MAX_VALUE can be used to simulate open ended intervals). By default, apps can only obtain
+ * data about themselves. See the below note for special cases in which apps can obtain data about
+ * other applications.
* <h3>
* Summary queries
* </h3>
@@ -51,13 +51,20 @@
* multiple buckets for a particular key but all Bucket's state is going to be
* {@link NetworkStats.Bucket#STATE_ALL}.
* <p />
- * <b>NOTE:</b> This API requires the permission
+ * <b>NOTE:</b> Accessing stats for apps other than the calling app requires the permission
* {@link android.Manifest.permission#PACKAGE_USAGE_STATS}, which is a system-level permission and
* will not be granted to third-party apps. However, declaring the permission implies intention to
* use the API and the user of the device can grant permission through the Settings application.
* Profile owner apps are automatically granted permission to query data on the profile they manage
- * (that is, for any query except {@link #querySummaryForDevice}). Device owner apps likewise get
- * access to usage data of the primary user.
+ * (that is, for any query except {@link #querySummaryForDevice}). Device owner apps and carrier-
+ * privileged apps likewise get access to usage data for all users on the device.
+ * <p />
+ * In addition to tethering usage, usage by removed users and apps, and usage by the system
+ * is also included in the results for callers with one of these higher levels of access.
+ * <p />
+ * <b>NOTE:</b> Prior to API level {@value Build.VERSION_CODES#N}, all calls to these APIs required
+ * the above permission, even to access an app's own data usage, and carrier-privileged apps were
+ * not included.
*/
public class NetworkStatsManager {
private final static String TAG = "NetworkStatsManager";
diff --git a/services/core/java/com/android/server/net/NetworkStatsAccess.java b/services/core/java/com/android/server/net/NetworkStatsAccess.java
new file mode 100644
index 0000000..53ba718
--- /dev/null
+++ b/services/core/java/com/android/server/net/NetworkStatsAccess.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2015 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 com.android.server.net;
+
+import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
+import static android.net.TrafficStats.UID_REMOVED;
+import static android.net.TrafficStats.UID_TETHERING;
+
+import android.Manifest;
+import android.annotation.IntDef;
+import android.app.AppOpsManager;
+import android.app.admin.DeviceAdminInfo;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.telephony.TelephonyManager;
+
+import com.android.server.LocalServices;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** Utility methods for controlling access to network stats APIs. */
+public final class NetworkStatsAccess {
+ private NetworkStatsAccess() {}
+
+ /**
+ * Represents an access level for the network usage history and statistics APIs.
+ *
+ * <p>Access levels are in increasing order; that is, it is reasonable to check access by
+ * verifying that the caller's access level is at least the minimum required level.
+ */
+ @IntDef({
+ Level.DEFAULT,
+ Level.USER,
+ Level.DEVICE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Level {
+ /**
+ * Default, unprivileged access level.
+ *
+ * <p>Can only access usage for one's own UID.
+ *
+ * <p>Every app will have at least this access level.
+ */
+ int DEFAULT = 0;
+
+ /**
+ * Access level for apps which can access usage for any app running in the same user.
+ *
+ * <p>Granted to:
+ * <ul>
+ * <li>Apps with the PACKAGE_USAGE_STATS permission granted. Note that this is an AppOps bit
+ * so it is not necessarily sufficient to declare this in the manifest.
+ * <li>Apps with the (signature/privileged) READ_NETWORK_USAGE_HISTORY permission.
+ * <li>Profile owners.
+ * </ul>
+ */
+ int USER = 1;
+
+ /**
+ * Access level for apps which can access usage for any app on the device, including apps
+ * running on other users/profiles.
+ *
+ * <p>Granted to:
+ * <ul>
+ * <li>Device owners.
+ * <li>Carrier-privileged applications.
+ * <li>The system UID.
+ * </ul>
+ */
+ int DEVICE = 2;
+ }
+
+ /** Returns the {@link NetworkStatsAccess.Level} for the given caller. */
+ public static @NetworkStatsAccess.Level int checkAccessLevel(
+ Context context, int callingUid, String callingPackage) {
+ final DevicePolicyManagerInternal dpmi = LocalServices.getService(
+ DevicePolicyManagerInternal.class);
+ final TelephonyManager tm = (TelephonyManager)
+ context.getSystemService(Context.TELEPHONY_SERVICE);
+ boolean hasCarrierPrivileges = tm != null &&
+ tm.checkCarrierPrivilegesForPackage(callingPackage) ==
+ TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+ boolean isDeviceOwner = dpmi != null && dpmi.isActiveAdminWithPolicy(callingUid,
+ DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ if (hasCarrierPrivileges || isDeviceOwner
+ || UserHandle.getAppId(callingUid) == android.os.Process.SYSTEM_UID) {
+ // Carrier-privileged apps and device owners, and the system can access data usage for
+ // all apps on the device.
+ return NetworkStatsAccess.Level.DEVICE;
+ }
+
+ boolean isProfileOwner = dpmi != null && dpmi.isActiveAdminWithPolicy(callingUid,
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ if (hasAppOpsPermission(context, callingUid, callingPackage) || isProfileOwner
+ || context.checkCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY) ==
+ PackageManager.PERMISSION_GRANTED) {
+ // Apps with the AppOps permission, profile owners, and apps with the privileged
+ // permission can access data usage for all apps in this user/profile.
+ return NetworkStatsAccess.Level.USER;
+ }
+
+ // Everyone else gets default access (only to their own UID).
+ return NetworkStatsAccess.Level.DEFAULT;
+ }
+
+ /**
+ * Returns whether the given caller should be able to access the given UID when the caller has
+ * the given {@link NetworkStatsAccess.Level}.
+ */
+ public static boolean isAccessibleToUser(int uid, int callerUid,
+ @NetworkStatsAccess.Level int accessLevel) {
+ switch (accessLevel) {
+ case NetworkStatsAccess.Level.DEVICE:
+ // Device-level access - can access usage for any uid.
+ return true;
+ case NetworkStatsAccess.Level.USER:
+ // User-level access - can access usage for any app running in the same user, along
+ // with some special uids (system, removed, or tethering).
+ return uid == android.os.Process.SYSTEM_UID || uid == UID_REMOVED
+ || uid == UID_TETHERING
+ || UserHandle.getUserId(uid) == UserHandle.getUserId(callerUid);
+ case NetworkStatsAccess.Level.DEFAULT:
+ default:
+ // Default access level - can only access one's own usage.
+ return uid == callerUid;
+ }
+ }
+
+ private static boolean hasAppOpsPermission(
+ Context context, int callingUid, String callingPackage) {
+ if (callingPackage != null) {
+ AppOpsManager appOps = (AppOpsManager) context.getSystemService(
+ Context.APP_OPS_SERVICE);
+
+ final int mode = appOps.checkOp(AppOpsManager.OP_GET_USAGE_STATS,
+ callingUid, callingPackage);
+ if (mode == AppOpsManager.MODE_DEFAULT) {
+ // The default behavior here is to check if PackageManager has given the app
+ // permission.
+ final int permissionCheck = context.checkCallingPermission(
+ Manifest.permission.PACKAGE_USAGE_STATS);
+ return permissionCheck == PackageManager.PERMISSION_GRANTED;
+ }
+ return (mode == AppOpsManager.MODE_ALLOWED);
+ }
+ return false;
+ }
+}
diff --git a/services/core/java/com/android/server/net/NetworkStatsCollection.java b/services/core/java/com/android/server/net/NetworkStatsCollection.java
index 15b68c7..102695e 100644
--- a/services/core/java/com/android/server/net/NetworkStatsCollection.java
+++ b/services/core/java/com/android/server/net/NetworkStatsCollection.java
@@ -22,24 +22,18 @@
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
import static android.net.TrafficStats.UID_REMOVED;
-import static android.net.TrafficStats.UID_TETHERING;
-import static android.text.format.DateUtils.SECOND_IN_MILLIS;
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
-import android.net.ConnectivityManager;
import android.net.NetworkIdentity;
import android.net.NetworkStats;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.net.TrafficStats;
import android.os.Binder;
-import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.IntArray;
-import libcore.io.IoUtils;
-
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FileRotator;
import com.android.internal.util.IndentingPrintWriter;
@@ -47,6 +41,8 @@
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
+import libcore.io.IoUtils;
+
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -136,12 +132,12 @@
return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE;
}
- public int[] getRelevantUids() {
+ public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel) {
final int callerUid = Binder.getCallingUid();
IntArray uids = new IntArray();
for (int i = 0; i < mStats.size(); i++) {
final Key key = mStats.keyAt(i);
- if (isAccessibleToUser(key.uid, callerUid)) {
+ if (NetworkStatsAccess.isAccessibleToUser(key.uid, callerUid, accessLevel)) {
int j = uids.binarySearch(key.uid);
if (j < 0) {
@@ -158,8 +154,10 @@
* the requested parameters.
*/
public NetworkStatsHistory getHistory(
- NetworkTemplate template, int uid, int set, int tag, int fields) {
- return getHistory(template, uid, set, tag, fields, Long.MIN_VALUE, Long.MAX_VALUE);
+ NetworkTemplate template, int uid, int set, int tag, int fields,
+ @NetworkStatsAccess.Level int accessLevel) {
+ return getHistory(template, uid, set, tag, fields, Long.MIN_VALUE, Long.MAX_VALUE,
+ accessLevel);
}
/**
@@ -167,9 +165,10 @@
* the requested parameters.
*/
public NetworkStatsHistory getHistory(
- NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end) {
+ NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end,
+ @NetworkStatsAccess.Level int accessLevel) {
final int callerUid = Binder.getCallingUid();
- if (!isAccessibleToUser(uid, callerUid)) {
+ if (!NetworkStatsAccess.isAccessibleToUser(uid, callerUid, accessLevel)) {
throw new SecurityException("Network stats history of uid " + uid
+ " is forbidden for caller " + callerUid);
}
@@ -195,7 +194,8 @@
* Summarize all {@link NetworkStatsHistory} in this collection which match
* the requested parameters.
*/
- public NetworkStats getSummary(NetworkTemplate template, long start, long end) {
+ public NetworkStats getSummary(NetworkTemplate template, long start, long end,
+ @NetworkStatsAccess.Level int accessLevel) {
final long now = System.currentTimeMillis();
final NetworkStats stats = new NetworkStats(end - start, 24);
@@ -208,7 +208,8 @@
final int callerUid = Binder.getCallingUid();
for (int i = 0; i < mStats.size(); i++) {
final Key key = mStats.keyAt(i);
- if (templateMatches(template, key.ident) && isAccessibleToUser(key.uid, callerUid)
+ if (templateMatches(template, key.ident)
+ && NetworkStatsAccess.isAccessibleToUser(key.uid, callerUid, accessLevel)
&& key.set < NetworkStats.SET_DEBUG_START) {
final NetworkStatsHistory value = mStats.valueAt(i);
historyEntry = value.getValues(start, end, now, historyEntry);
@@ -570,12 +571,6 @@
}
}
- private static boolean isAccessibleToUser(int uid, int callerUid) {
- return UserHandle.getAppId(callerUid) == android.os.Process.SYSTEM_UID ||
- uid == android.os.Process.SYSTEM_UID || uid == UID_REMOVED || uid == UID_TETHERING
- || UserHandle.getUserId(uid) == UserHandle.getUserId(callerUid);
- }
-
/**
* Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity}
* in the given {@link NetworkIdentitySet}.
diff --git a/services/core/java/com/android/server/net/NetworkStatsRecorder.java b/services/core/java/com/android/server/net/NetworkStatsRecorder.java
index 6490865..c091960 100644
--- a/services/core/java/com/android/server/net/NetworkStatsRecorder.java
+++ b/services/core/java/com/android/server/net/NetworkStatsRecorder.java
@@ -116,7 +116,8 @@
}
public NetworkStats.Entry getTotalSinceBootLocked(NetworkTemplate template) {
- return mSinceBoot.getSummary(template, Long.MIN_VALUE, Long.MAX_VALUE).getTotal(null);
+ return mSinceBoot.getSummary(template, Long.MIN_VALUE, Long.MAX_VALUE,
+ NetworkStatsAccess.Level.DEVICE).getTotal(null);
}
/**
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 8449348..b1d6f89 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -62,13 +62,9 @@
import static com.android.server.NetworkManagementSocketTagger.resetKernelUidStats;
import static com.android.server.NetworkManagementSocketTagger.setKernelCounterSet;
-import android.Manifest;
import android.app.AlarmManager;
-import android.app.AppOpsManager;
import android.app.IAlarmManager;
import android.app.PendingIntent;
-import android.app.admin.DeviceAdminInfo;
-import android.app.admin.DevicePolicyManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -97,9 +93,7 @@
import android.os.INetworkManagementService;
import android.os.Message;
import android.os.PowerManager;
-import android.os.Process;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
@@ -122,7 +116,6 @@
import com.android.internal.util.FileRotator;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.EventLogTags;
-import com.android.server.LocalServices;
import com.android.server.connectivity.Tethering;
import java.io.File;
@@ -484,18 +477,22 @@
@Override
public int[] getRelevantUids() {
- enforcePermissionForManagedAdmin(mCallingPackage);
- return getUidComplete().getRelevantUids();
+ return getUidComplete().getRelevantUids(checkAccessLevel(mCallingPackage));
}
@Override
public NetworkStats getDeviceSummaryForNetwork(NetworkTemplate template, long start,
long end) {
- enforcePermission(mCallingPackage);
+ @NetworkStatsAccess.Level int accessLevel = checkAccessLevel(mCallingPackage);
+ if (accessLevel < NetworkStatsAccess.Level.DEVICE) {
+ throw new SecurityException("Calling package " + mCallingPackage
+ + " cannot access device-level network stats");
+ }
NetworkStats result = new NetworkStats(end - start, 1);
final long ident = Binder.clearCallingIdentity();
try {
- result.combineAllValues(internalGetSummaryForNetwork(template, start, end));
+ result.combineAllValues(
+ internalGetSummaryForNetwork(template, start, end, accessLevel));
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -505,23 +502,25 @@
@Override
public NetworkStats getSummaryForNetwork(
NetworkTemplate template, long start, long end) {
- enforcePermission(mCallingPackage);
- return internalGetSummaryForNetwork(template, start, end);
+ @NetworkStatsAccess.Level int accessLevel = checkAccessLevel(mCallingPackage);
+ return internalGetSummaryForNetwork(template, start, end, accessLevel);
}
@Override
public NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields) {
- return internalGetHistoryForNetwork(template, fields);
+ @NetworkStatsAccess.Level int accessLevel = checkAccessLevel(mCallingPackage);
+ return internalGetHistoryForNetwork(template, fields, accessLevel);
}
@Override
public NetworkStats getSummaryForAllUid(
NetworkTemplate template, long start, long end, boolean includeTags) {
- enforcePermissionForManagedAdmin(mCallingPackage);
- final NetworkStats stats = getUidComplete().getSummary(template, start, end);
+ @NetworkStatsAccess.Level int accessLevel = checkAccessLevel(mCallingPackage);
+ final NetworkStats stats =
+ getUidComplete().getSummary(template, start, end, accessLevel);
if (includeTags) {
final NetworkStats tagStats = getUidTagComplete()
- .getSummary(template, start, end);
+ .getSummary(template, start, end, accessLevel);
stats.combineAllValues(tagStats);
}
return stats;
@@ -530,11 +529,13 @@
@Override
public NetworkStatsHistory getHistoryForUid(
NetworkTemplate template, int uid, int set, int tag, int fields) {
- enforcePermissionForManagedAdmin(mCallingPackage);
+ @NetworkStatsAccess.Level int accessLevel = checkAccessLevel(mCallingPackage);
if (tag == TAG_NONE) {
- return getUidComplete().getHistory(template, uid, set, tag, fields);
+ return getUidComplete().getHistory(template, uid, set, tag, fields,
+ accessLevel);
} else {
- return getUidTagComplete().getHistory(template, uid, set, tag, fields);
+ return getUidTagComplete().getHistory(template, uid, set, tag, fields,
+ accessLevel);
}
}
@@ -542,12 +543,13 @@
public NetworkStatsHistory getHistoryIntervalForUid(
NetworkTemplate template, int uid, int set, int tag, int fields,
long start, long end) {
- enforcePermissionForManagedAdmin(mCallingPackage);
+ @NetworkStatsAccess.Level int accessLevel = checkAccessLevel(mCallingPackage);
if (tag == TAG_NONE) {
- return getUidComplete().getHistory(template, uid, set, tag, fields, start, end);
+ return getUidComplete().getHistory(template, uid, set, tag, fields, start, end,
+ accessLevel);
} else {
return getUidTagComplete().getHistory(template, uid, set, tag, fields,
- start, end);
+ start, end, accessLevel);
}
}
@@ -559,80 +561,42 @@
};
}
- private boolean hasAppOpsPermission(String callingPackage) {
- final int callingUid = Binder.getCallingUid();
- boolean appOpsAllow = false;
- if (callingPackage != null) {
- AppOpsManager appOps = (AppOpsManager) mContext.getSystemService(
- Context.APP_OPS_SERVICE);
-
- final int mode = appOps.checkOp(AppOpsManager.OP_GET_USAGE_STATS,
- callingUid, callingPackage);
- if (mode == AppOpsManager.MODE_DEFAULT) {
- // The default behavior here is to check if PackageManager has given the app
- // permission.
- final int permissionCheck = mContext.checkCallingPermission(
- Manifest.permission.PACKAGE_USAGE_STATS);
- appOpsAllow = permissionCheck == PackageManager.PERMISSION_GRANTED;
- }
- appOpsAllow = (mode == AppOpsManager.MODE_ALLOWED);
- }
- return appOpsAllow;
+ private @NetworkStatsAccess.Level int checkAccessLevel(String callingPackage) {
+ return NetworkStatsAccess.checkAccessLevel(
+ mContext, Binder.getCallingUid(), callingPackage);
}
- private void enforcePermissionForManagedAdmin(String callingPackage) {
- boolean hasPermission = hasAppOpsPermission(callingPackage);
- if (!hasPermission) {
- // Profile and device owners are exempt from permission checking.
- final int callingUid = Binder.getCallingUid();
- final DevicePolicyManagerInternal dpmi = LocalServices.getService(
- DevicePolicyManagerInternal.class);
-
- // Device owners are also profile owners so it is enough to check for that.
- if (dpmi != null && dpmi.isActiveAdminWithPolicy(callingUid,
- DeviceAdminInfo.USES_POLICY_PROFILE_OWNER)) {
- return;
- }
- }
- if (!hasPermission) {
- mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
- }
- }
-
- private void enforcePermission(String callingPackage) {
- boolean appOpsAllow = hasAppOpsPermission(callingPackage);
- if (!appOpsAllow) {
- mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
- }
- }
-
-
/**
* Return network summary, splicing between DEV and XT stats when
* appropriate.
*/
private NetworkStats internalGetSummaryForNetwork(
- NetworkTemplate template, long start, long end) {
+ NetworkTemplate template, long start, long end,
+ @NetworkStatsAccess.Level int accessLevel) {
// We've been using pure XT stats long enough that we no longer need to
// splice DEV and XT together.
- return mXtStatsCached.getSummary(template, start, end);
+ return mXtStatsCached.getSummary(template, start, end, accessLevel);
}
/**
* Return network history, splicing between DEV and XT stats when
* appropriate.
*/
- private NetworkStatsHistory internalGetHistoryForNetwork(NetworkTemplate template, int fields) {
+ private NetworkStatsHistory internalGetHistoryForNetwork(NetworkTemplate template, int fields,
+ @NetworkStatsAccess.Level int accessLevel) {
// We've been using pure XT stats long enough that we no longer need to
// splice DEV and XT together.
- return mXtStatsCached.getHistory(template, UID_ALL, SET_ALL, TAG_NONE, fields);
+ return mXtStatsCached.getHistory(template, UID_ALL, SET_ALL, TAG_NONE, fields, accessLevel);
}
@Override
public long getNetworkTotalBytes(NetworkTemplate template, long start, long end) {
+ // Special case - since this is for internal use only, don't worry about a full access level
+ // check and just require the signature/privileged permission.
mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
assertBandwidthControlEnabled();
- return internalGetSummaryForNetwork(template, start, end).getTotalBytes();
+ return internalGetSummaryForNetwork(template, start, end, NetworkStatsAccess.Level.DEVICE)
+ .getTotalBytes();
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsAccessTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkStatsAccessTest.java
new file mode 100644
index 0000000..bb8f9d1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkStatsAccessTest.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2015 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 com.android.server.net;
+
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.Manifest.permission;
+import android.app.AppOpsManager;
+import android.app.admin.DeviceAdminInfo;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.telephony.TelephonyManager;
+
+import com.android.server.LocalServices;
+
+import junit.framework.TestCase;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class NetworkStatsAccessTest extends TestCase {
+ private static final String TEST_PKG = "com.example.test";
+ private static final int TEST_UID = 12345;
+
+ @Mock private Context mContext;
+ @Mock private DevicePolicyManagerInternal mDpmi;
+ @Mock private TelephonyManager mTm;
+ @Mock private AppOpsManager mAppOps;
+
+ // Hold the real service so we can restore it when tearing down the test.
+ private DevicePolicyManagerInternal mSystemDpmi;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+
+ mSystemDpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
+ LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+ LocalServices.addService(DevicePolicyManagerInternal.class, mDpmi);
+
+ when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTm);
+ when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOps);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+ LocalServices.addService(DevicePolicyManagerInternal.class, mSystemDpmi);
+ super.tearDown();
+ }
+
+ public void testCheckAccessLevel_hasCarrierPrivileges() throws Exception {
+ setHasCarrierPrivileges(true);
+ setIsDeviceOwner(false);
+ setIsProfileOwner(false);
+ setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
+ setHasReadHistoryPermission(false);
+ assertEquals(NetworkStatsAccess.Level.DEVICE,
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+ }
+
+ public void testCheckAccessLevel_isDeviceOwner() throws Exception {
+ setHasCarrierPrivileges(false);
+ setIsDeviceOwner(true);
+ setIsProfileOwner(false);
+ setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
+ setHasReadHistoryPermission(false);
+ assertEquals(NetworkStatsAccess.Level.DEVICE,
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+ }
+
+ public void testCheckAccessLevel_isProfileOwner() throws Exception {
+ setHasCarrierPrivileges(false);
+ setIsDeviceOwner(false);
+ setIsProfileOwner(true);
+ setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
+ setHasReadHistoryPermission(false);
+ assertEquals(NetworkStatsAccess.Level.USER,
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+ }
+
+ public void testCheckAccessLevel_hasAppOpsBitAllowed() throws Exception {
+ setHasCarrierPrivileges(false);
+ setIsDeviceOwner(false);
+ setIsProfileOwner(true);
+ setHasAppOpsPermission(AppOpsManager.MODE_ALLOWED, false);
+ setHasReadHistoryPermission(false);
+ assertEquals(NetworkStatsAccess.Level.USER,
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+ }
+
+ public void testCheckAccessLevel_hasAppOpsBitDefault_grantedPermission() throws Exception {
+ setHasCarrierPrivileges(false);
+ setIsDeviceOwner(false);
+ setIsProfileOwner(true);
+ setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, true);
+ setHasReadHistoryPermission(false);
+ assertEquals(NetworkStatsAccess.Level.USER,
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+ }
+
+ public void testCheckAccessLevel_hasReadHistoryPermission() throws Exception {
+ setHasCarrierPrivileges(false);
+ setIsDeviceOwner(false);
+ setIsProfileOwner(true);
+ setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
+ setHasReadHistoryPermission(true);
+ assertEquals(NetworkStatsAccess.Level.USER,
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+ }
+
+ public void testCheckAccessLevel_deniedAppOpsBit() throws Exception {
+ setHasCarrierPrivileges(false);
+ setIsDeviceOwner(false);
+ setIsProfileOwner(false);
+ setHasAppOpsPermission(AppOpsManager.MODE_ERRORED, true);
+ setHasReadHistoryPermission(false);
+ assertEquals(NetworkStatsAccess.Level.DEFAULT,
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+ }
+
+ public void testCheckAccessLevel_deniedAppOpsBit_deniedPermission() throws Exception {
+ setHasCarrierPrivileges(false);
+ setIsDeviceOwner(false);
+ setIsProfileOwner(false);
+ setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
+ setHasReadHistoryPermission(false);
+ assertEquals(NetworkStatsAccess.Level.DEFAULT,
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+ }
+
+ private void setHasCarrierPrivileges(boolean hasPrivileges) {
+ when(mTm.checkCarrierPrivilegesForPackage(TEST_PKG)).thenReturn(
+ hasPrivileges ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
+ : TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+ }
+
+ private void setIsDeviceOwner(boolean isOwner) {
+ when(mDpmi.isActiveAdminWithPolicy(TEST_UID, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER))
+ .thenReturn(isOwner);
+ }
+
+ private void setIsProfileOwner(boolean isOwner) {
+ when(mDpmi.isActiveAdminWithPolicy(TEST_UID, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER))
+ .thenReturn(isOwner);
+ }
+
+ private void setHasAppOpsPermission(int appOpsMode, boolean hasPermission) {
+ when(mAppOps.checkOp(AppOpsManager.OP_GET_USAGE_STATS, TEST_UID, TEST_PKG))
+ .thenReturn(appOpsMode);
+ when(mContext.checkCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS)).thenReturn(
+ hasPermission ? PackageManager.PERMISSION_GRANTED
+ : PackageManager.PERMISSION_DENIED);
+ }
+
+ private void setHasReadHistoryPermission(boolean hasPermission) {
+ when(mContext.checkCallingOrSelfPermission(permission.READ_NETWORK_USAGE_HISTORY))
+ .thenReturn(hasPermission ? PackageManager.PERMISSION_GRANTED
+ : PackageManager.PERMISSION_DENIED);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java
index 1a6c289..6026644 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java
@@ -16,6 +16,7 @@
package com.android.server.net;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.NetworkStats.SET_DEFAULT;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
@@ -24,9 +25,14 @@
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import android.content.res.Resources;
+import android.net.NetworkIdentity;
import android.net.NetworkStats;
import android.net.NetworkTemplate;
+import android.os.Process;
+import android.os.UserHandle;
+import android.telephony.TelephonyManager;
import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
import android.test.suitebuilder.annotation.MediumTest;
import com.android.frameworks.servicestests.R;
@@ -68,7 +74,7 @@
// verify that history read correctly
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
- 636016770L, 709306L, 88038768L, 518836L);
+ 636016770L, 709306L, 88038768L, 518836L, NetworkStatsAccess.Level.DEVICE);
// now export into a unified format
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
@@ -77,12 +83,12 @@
// clear structure completely
collection.reset();
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
- 0L, 0L, 0L, 0L);
+ 0L, 0L, 0L, 0L, NetworkStatsAccess.Level.DEVICE);
// and read back into structure, verifying that totals are same
collection.read(new ByteArrayInputStream(bos.toByteArray()));
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
- 636016770L, 709306L, 88038768L, 518836L);
+ 636016770L, 709306L, 88038768L, 518836L, NetworkStatsAccess.Level.DEVICE);
}
public void testReadLegacyUid() throws Exception {
@@ -94,7 +100,7 @@
// verify that history read correctly
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
- 637076152L, 711413L, 88343717L, 521022L);
+ 637076152L, 711413L, 88343717L, 521022L, NetworkStatsAccess.Level.DEVICE);
// now export into a unified format
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
@@ -103,12 +109,12 @@
// clear structure completely
collection.reset();
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
- 0L, 0L, 0L, 0L);
+ 0L, 0L, 0L, 0L, NetworkStatsAccess.Level.DEVICE);
// and read back into structure, verifying that totals are same
collection.read(new ByteArrayInputStream(bos.toByteArray()));
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
- 637076152L, 711413L, 88343717L, 521022L);
+ 637076152L, 711413L, 88343717L, 521022L, NetworkStatsAccess.Level.DEVICE);
}
public void testReadLegacyUidTags() throws Exception {
@@ -151,6 +157,66 @@
assertEquals(2 * HOUR_IN_MILLIS, collection.getEndMillis());
}
+ public void testAccessLevels() throws Exception {
+ final NetworkStatsCollection collection = new NetworkStatsCollection(HOUR_IN_MILLIS);
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+ final NetworkIdentitySet identSet = new NetworkIdentitySet();
+ identSet.add(new NetworkIdentity(TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ TEST_IMSI, null, false));
+
+ int myUid = Process.myUid();
+ int otherUidInSameUser = Process.myUid() + 1;
+ int uidInDifferentUser = Process.myUid() + UserHandle.PER_USER_RANGE;
+
+ // Record one entry for the current UID.
+ entry.rxBytes = 32;
+ collection.recordData(identSet, myUid, SET_DEFAULT, TAG_NONE, 0, 60 * MINUTE_IN_MILLIS,
+ entry);
+
+ // Record one entry for another UID in this user.
+ entry.rxBytes = 64;
+ collection.recordData(identSet, otherUidInSameUser, SET_DEFAULT, TAG_NONE, 0,
+ 60 * MINUTE_IN_MILLIS, entry);
+
+ // Record one entry for the system UID.
+ entry.rxBytes = 128;
+ collection.recordData(identSet, Process.SYSTEM_UID, SET_DEFAULT, TAG_NONE, 0,
+ 60 * MINUTE_IN_MILLIS, entry);
+
+ // Record one entry for a UID in a different user.
+ entry.rxBytes = 256;
+ collection.recordData(identSet, uidInDifferentUser, SET_DEFAULT, TAG_NONE, 0,
+ 60 * MINUTE_IN_MILLIS, entry);
+
+ // Verify the set of relevant UIDs for each access level.
+ MoreAsserts.assertEquals(new int[] { myUid },
+ collection.getRelevantUids(NetworkStatsAccess.Level.DEFAULT));
+ MoreAsserts.assertEquals(new int[] { Process.SYSTEM_UID, myUid, otherUidInSameUser },
+ collection.getRelevantUids(NetworkStatsAccess.Level.USER));
+ MoreAsserts.assertEquals(
+ new int[] { Process.SYSTEM_UID, myUid, otherUidInSameUser, uidInDifferentUser },
+ collection.getRelevantUids(NetworkStatsAccess.Level.DEVICE));
+
+ // Verify security check in getHistory.
+ assertNotNull(collection.getHistory(buildTemplateMobileAll(TEST_IMSI), myUid, SET_DEFAULT,
+ TAG_NONE, 0, NetworkStatsAccess.Level.DEFAULT));
+ try {
+ collection.getHistory(buildTemplateMobileAll(TEST_IMSI), otherUidInSameUser,
+ SET_DEFAULT, TAG_NONE, 0, NetworkStatsAccess.Level.DEFAULT);
+ fail("Should have thrown SecurityException for accessing different UID");
+ } catch (SecurityException e) {
+ // expected
+ }
+
+ // Verify appropriate aggregation in getSummary.
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), 32, 0, 0, 0,
+ NetworkStatsAccess.Level.DEFAULT);
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), 32 + 64 + 128, 0, 0, 0,
+ NetworkStatsAccess.Level.USER);
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), 32 + 64 + 128 + 256, 0, 0,
+ 0, NetworkStatsAccess.Level.DEVICE);
+ }
+
/**
* Copy a {@link Resources#openRawResource(int)} into {@link File} for
* testing purposes.
@@ -170,16 +236,19 @@
}
private static void assertSummaryTotal(NetworkStatsCollection collection,
- NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets) {
+ NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets,
+ @NetworkStatsAccess.Level int accessLevel) {
final NetworkStats.Entry entry = collection.getSummary(
- template, Long.MIN_VALUE, Long.MAX_VALUE).getTotal(null);
+ template, Long.MIN_VALUE, Long.MAX_VALUE, accessLevel)
+ .getTotal(null);
assertEntry(entry, rxBytes, rxPackets, txBytes, txPackets);
}
private static void assertSummaryTotalIncludingTags(NetworkStatsCollection collection,
NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets) {
final NetworkStats.Entry entry = collection.getSummary(
- template, Long.MIN_VALUE, Long.MAX_VALUE).getTotalIncludingTags(null);
+ template, Long.MIN_VALUE, Long.MAX_VALUE, NetworkStatsAccess.Level.DEVICE)
+ .getTotalIncludingTags(null);
assertEntry(entry, rxBytes, rxPackets, txBytes, txPackets);
}