diff options
Diffstat (limited to 'framework-t/src/android/net/NetworkStatsAccess.java')
| -rw-r--r-- | framework-t/src/android/net/NetworkStatsAccess.java | 203 |
1 files changed, 203 insertions, 0 deletions
diff --git a/framework-t/src/android/net/NetworkStatsAccess.java b/framework-t/src/android/net/NetworkStatsAccess.java new file mode 100644 index 0000000000..3885a9e6d5 --- /dev/null +++ b/framework-t/src/android/net/NetworkStatsAccess.java @@ -0,0 +1,203 @@ +/* + * 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 android.net; + +import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY; +import static android.net.NetworkStats.UID_ALL; +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.DevicePolicyManagerInternal; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Binder; +import android.os.Process; +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. + * + * @hide + */ +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.DEVICESUMMARY, + 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>Profile owners. + * </ul> + */ + int USER = 1; + + /** + * Access level for apps which can access usage summary of device. Device summary includes + * usage by apps running in any profiles/users, however this access level does not + * allow querying usage of individual apps running in other profiles/users. + * + * <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. + * </ul> + */ + int DEVICESUMMARY = 2; + + /** + * 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 = 3; + } + + /** 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; + final long token = Binder.clearCallingIdentity(); + try { + hasCarrierPrivileges = tm != null + && tm.checkCarrierPrivilegesForPackageAnyPhone(callingPackage) + == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; + } finally { + Binder.restoreCallingIdentity(token); + } + + final boolean isDeviceOwner = dpmi != null && dpmi.isActiveDeviceOwner(callingUid); + final int appId = UserHandle.getAppId(callingUid); + if (hasCarrierPrivileges || isDeviceOwner + || appId == Process.SYSTEM_UID || appId == Process.NETWORK_STACK_UID) { + // Carrier-privileged apps and device owners, and the system (including the + // network stack) can access data usage for all apps on the device. + return NetworkStatsAccess.Level.DEVICE; + } + + boolean hasAppOpsPermission = hasAppOpsPermission(context, callingUid, callingPackage); + if (hasAppOpsPermission || context.checkCallingOrSelfPermission( + READ_NETWORK_USAGE_HISTORY) == PackageManager.PERMISSION_GRANTED) { + return NetworkStatsAccess.Level.DEVICESUMMARY; + } + + //TODO(b/169395065) Figure out if this flow makes sense in Device Owner mode. + boolean isProfileOwner = dpmi != null && (dpmi.isActiveProfileOwner(callingUid) + || dpmi.isActiveDeviceOwner(callingUid)); + if (isProfileOwner) { + // 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.DEVICESUMMARY: + // Can access usage for any app running in the same user, along + // with some special uids (system, removed, or tethering) and + // anonymized uids + return uid == android.os.Process.SYSTEM_UID || uid == UID_REMOVED + || uid == UID_TETHERING || uid == UID_ALL + || UserHandle.getUserId(uid) == UserHandle.getUserId(callerUid); + 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.noteOp(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; + } +} |
