1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package android.telephony; 18 19 import android.Manifest; 20 import android.annotation.NonNull; 21 import android.annotation.UserIdInt; 22 import android.app.ActivityManager; 23 import android.app.AppOpsManager; 24 import android.content.Context; 25 import android.content.pm.PackageManager; 26 import android.content.pm.UserInfo; 27 import android.location.LocationManager; 28 import android.os.Binder; 29 import android.os.Process; 30 import android.os.Trace; 31 import android.os.UserHandle; 32 import android.os.UserManager; 33 import android.util.Log; 34 35 import java.util.List; 36 37 /** 38 * Helper for performing location access checks. 39 * @hide 40 */ 41 public final class LocationAccessPolicy { 42 private static final String LOG_TAG = LocationAccessPolicy.class.getSimpleName(); 43 44 /** 45 * API to determine if the caller has permissions to get cell location. 46 * 47 * @param pkgName Package name of the application requesting access 48 * @param uid The uid of the package 49 * @param pid The pid of the package 50 * @param throwOnDeniedPermission Whether to throw if the location permission is denied. 51 * @return boolean true or false if permissions is granted 52 */ 53 public static boolean canAccessCellLocation(@NonNull Context context, @NonNull String pkgName, 54 int uid, int pid, boolean throwOnDeniedPermission) throws SecurityException { 55 Trace.beginSection("TelephonyLohcationCheck"); 56 try { 57 // Always allow the phone process to access location. This avoid breaking legacy code 58 // that rely on public-facing APIs to access cell location, and it doesn't create a 59 // info leak risk because the cell location is stored in the phone process anyway. 60 if (uid == Process.PHONE_UID) { 61 return true; 62 } 63 64 // We always require the location permission and also require the 65 // location mode to be on for non-legacy apps. Legacy apps are 66 // required to be in the foreground to at least mitigate the case 67 // where a legacy app the user is not using tracks their location. 68 // Granting ACCESS_FINE_LOCATION to an app automatically grants it 69 // ACCESS_COARSE_LOCATION. 70 if (throwOnDeniedPermission) { 71 context.enforcePermission(Manifest.permission.ACCESS_COARSE_LOCATION, 72 pid, uid, "canAccessCellLocation"); 73 } else if (context.checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION, 74 pid, uid) == PackageManager.PERMISSION_DENIED) { 75 return false; 76 } 77 final int opCode = AppOpsManager.permissionToOpCode( 78 Manifest.permission.ACCESS_COARSE_LOCATION); 79 if (opCode != AppOpsManager.OP_NONE && context.getSystemService(AppOpsManager.class) 80 .noteOpNoThrow(opCode, uid, pkgName) != AppOpsManager.MODE_ALLOWED) { 81 return false; 82 } 83 if (!isLocationModeEnabled(context, UserHandle.getUserId(uid))) { 84 return false; 85 } 86 // If the user or profile is current, permission is granted. 87 // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission. 88 return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context); 89 } finally { 90 Trace.endSection(); 91 } 92 } 93 94 private static boolean isLocationModeEnabled(@NonNull Context context, @UserIdInt int userId) { 95 LocationManager locationManager = context.getSystemService(LocationManager.class); 96 if (locationManager == null) { 97 Log.w(LOG_TAG, "Couldn't get location manager, denying location access"); 98 return false; 99 } 100 return locationManager.isLocationEnabledForUser(UserHandle.of(userId)); 101 } 102 103 private static boolean checkInteractAcrossUsersFull(@NonNull Context context) { 104 return context.checkCallingOrSelfPermission( 105 android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) 106 == PackageManager.PERMISSION_GRANTED; 107 } 108 109 private static boolean isCurrentProfile(@NonNull Context context, int uid) { 110 long token = Binder.clearCallingIdentity(); 111 try { 112 final int currentUser = ActivityManager.getCurrentUser(); 113 final int callingUserId = UserHandle.getUserId(uid); 114 if (callingUserId == currentUser) { 115 return true; 116 } else { 117 List<UserInfo> userProfiles = context.getSystemService( 118 UserManager.class).getProfiles(currentUser); 119 for (UserInfo user : userProfiles) { 120 if (user.id == callingUserId) { 121 return true; 122 } 123 } 124 } 125 return false; 126 } finally { 127 Binder.restoreCallingIdentity(token); 128 } 129 } 130 } 131