Merge "Relax permissions around NetworkStatsManager APIs."

This commit is contained in:
Jeff Davidson
2015-12-15 19:04:25 +00:00
committed by Android (Google) Code Review
7 changed files with 491 additions and 111 deletions

View File

@@ -21,6 +21,7 @@ import android.content.Context;
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 @@ import android.util.Log;
* 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 @@ import android.util.Log;
* 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";

View File

@@ -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;
}
}

View File

@@ -22,24 +22,18 @@ import static android.net.NetworkStats.SET_DEFAULT;
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.android.internal.util.IndentingPrintWriter;
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 @@ public class NetworkStatsCollection implements FileRotator.Reader {
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 @@ public class NetworkStatsCollection implements FileRotator.Reader {
* 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 @@ public class NetworkStatsCollection implements FileRotator.Reader {
* 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 @@ public class NetworkStatsCollection implements FileRotator.Reader {
* 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 @@ public class NetworkStatsCollection implements FileRotator.Reader {
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 @@ public class NetworkStatsCollection implements FileRotator.Reader {
}
}
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}.

View File

@@ -116,7 +116,8 @@ public class NetworkStatsRecorder {
}
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);
}
/**

View File

@@ -62,13 +62,9 @@ import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
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.HandlerThread;
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.ArrayUtils;
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 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
@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 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
@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 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
@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 class NetworkStatsService extends INetworkStatsService.Stub {
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 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
};
}
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

View File

@@ -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);
}
}

View File

@@ -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.HOUR_IN_MILLIS;
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 @@ public class NetworkStatsCollectionTest extends AndroidTestCase {
// 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 @@ public class NetworkStatsCollectionTest extends AndroidTestCase {
// 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 @@ public class NetworkStatsCollectionTest extends AndroidTestCase {
// 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 @@ public class NetworkStatsCollectionTest extends AndroidTestCase {
// 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 @@ public class NetworkStatsCollectionTest extends AndroidTestCase {
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 @@ public class NetworkStatsCollectionTest extends AndroidTestCase {
}
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);
}